Skip to content

Commit

Permalink
react to ENAMETOOLONG errors
Browse files Browse the repository at this point in the history
  • Loading branch information
flotwig committed Aug 6, 2020
1 parent cc519a4 commit b7302c7
Showing 1 changed file with 26 additions and 11 deletions.
37 changes: 26 additions & 11 deletions packages/server/lib/screenshots.js
Expand Up @@ -18,6 +18,14 @@ const pathSeparatorRe = /[\\\/]/g
// internal id incrementor
let __ID__ = null

// many filesystems limit filename length to 255 bytes/characters, so truncate the filename to
// the smallest common denominator of safe filenames, which is 255 bytes. when ENAMETOOLONG
// errors are encountered, `maxSafeBytes` will be decremented to at most `MIN_PREFIX_BYTES`, at
// which point the latest ENAMTOOLONG error will be emitted.
// @see https://en.wikipedia.org/wiki/Comparison_of_file_systems#Limits
let maxSafeBytes = 255
const MIN_PREFIX_BYTES = 64

// TODO: when we parallelize these builds we'll need
// a semaphore to access the file system when we write
// screenshots since its possible two screenshots with
Expand Down Expand Up @@ -288,17 +296,13 @@ const getDimensions = function (details) {
return pick(details.image.bitmap)
}

const ensureUniquePath = function (withoutExt, extension, num = 0) {
const ensureSafePath = function (withoutExt, extension, num = 0) {
const suffix = `${num ? ` (${num})` : ''}.${extension}`
// many filesystems limit filename length to 255 bytes/characters, so truncate the filename to
// the smallest common denominator of safe filenames, which is 255 bytes
// @see https://github.com/cypress-io/cypress/issues/2403
// @see https://en.wikipedia.org/wiki/Comparison_of_file_systems#Limits
const maxSafeBytes = 255 - suffix.length
const maxSafePrefixBytes = maxSafeBytes - suffix.length
const filenameBuf = Buffer.from(path.basename(withoutExt))

if (filenameBuf.byteLength > maxSafeBytes) {
const truncated = filenameBuf.slice(0, maxSafeBytes).toString()
if (filenameBuf.byteLength > maxSafePrefixBytes) {
const truncated = filenameBuf.slice(0, maxSafePrefixBytes).toString()

withoutExt = path.join(path.dirname(withoutExt), truncated)
}
Expand All @@ -308,10 +312,21 @@ const ensureUniquePath = function (withoutExt, extension, num = 0) {
return fs.pathExists(fullPath)
.then((found) => {
if (found) {
return ensureUniquePath(withoutExt, extension, num + 1)
return ensureSafePath(withoutExt, extension, num + 1)
}

return fullPath
// path does not exist, attempt to create it to check for an ENAMETOOLONG error
return fs.outputFileAsync(fullPath, '')
.then(() => fullPath)
.catch((err) => {
if (err.code === 'ENAMETOOLONG' && maxSafePrefixBytes >= MIN_PREFIX_BYTES) {
maxSafeBytes -= 1

return ensureSafePath(withoutExt, extension, num)
}

throw err
})
})
}

Expand Down Expand Up @@ -346,7 +361,7 @@ const getPath = function (data, ext, screenshotsFolder) {

const withoutExt = path.join(screenshotsFolder, ...specNames, ...names)

return ensureUniquePath(withoutExt, ext)
return ensureSafePath(withoutExt, ext)
}

const getPathToScreenshot = function (data, details, screenshotsFolder) {
Expand Down

0 comments on commit b7302c7

Please sign in to comment.