-
Notifications
You must be signed in to change notification settings - Fork 7.3k
fs: Add file descriptor support to *File() funcs #8522
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -121,6 +121,10 @@ function nullCheck(path, callback) { | |
return true; | ||
} | ||
|
||
function isFd(path) { | ||
return (path >>> 0) === path && path >= 0; | ||
} | ||
|
||
// Static method to set the stats properties on a Stats object. | ||
fs.Stats = function( | ||
dev, | ||
|
@@ -263,17 +267,23 @@ fs.readFile = function(path, options, callback_) { | |
var buffers; // list for when size is unknown | ||
var pos = 0; | ||
var fd; | ||
var isUserFd = isFd(path); // file descriptor ownership | ||
|
||
if (isUserFd) { | ||
readFd(null, path); | ||
} else { | ||
var flag = options.flag || 'r'; | ||
fs.open(path, flag, 438 /*=0666*/, readFd); | ||
} | ||
|
||
function readFd(err_, fd_) { | ||
if (err_) return callback(err_); | ||
|
||
var flag = options.flag || 'r'; | ||
fs.open(path, flag, 438 /*=0666*/, function(er, fd_) { | ||
if (er) return callback(er); | ||
fd = fd_; | ||
|
||
fs.fstat(fd, function(er, st) { | ||
if (er) { | ||
return fs.close(fd, function() { | ||
callback(er); | ||
}); | ||
return error(er); | ||
} | ||
|
||
size = st.size; | ||
|
@@ -287,14 +297,13 @@ fs.readFile = function(path, options, callback_) { | |
if (size > kMaxLength) { | ||
var err = new RangeError('File size is greater than possible Buffer: ' + | ||
'0x3FFFFFFF bytes'); | ||
return fs.close(fd, function() { | ||
callback(err); | ||
}); | ||
|
||
return error(err); | ||
} | ||
buffer = new Buffer(size); | ||
read(); | ||
}); | ||
}); | ||
} | ||
|
||
function read() { | ||
if (size === 0) { | ||
|
@@ -307,9 +316,7 @@ fs.readFile = function(path, options, callback_) { | |
|
||
function afterRead(er, bytesRead) { | ||
if (er) { | ||
return fs.close(fd, function(er2) { | ||
return callback(er); | ||
}); | ||
return error(er); | ||
} | ||
|
||
if (bytesRead === 0) { | ||
|
@@ -328,7 +335,12 @@ fs.readFile = function(path, options, callback_) { | |
} | ||
|
||
function close() { | ||
fs.close(fd, function(er) { | ||
if (isUserFd) | ||
afterClose(null); | ||
else | ||
fs.close(fd, afterClose); | ||
|
||
function afterClose(er) { | ||
if (size === 0) { | ||
// collected the data into the buffers list. | ||
buffer = Buffer.concat(buffers, pos); | ||
|
@@ -338,7 +350,17 @@ fs.readFile = function(path, options, callback_) { | |
|
||
if (encoding) buffer = buffer.toString(encoding); | ||
return callback(er, buffer); | ||
}); | ||
} | ||
} | ||
|
||
function error(er) { | ||
if (isUserFd) { | ||
callback(er); | ||
} else { | ||
fs.close(fd, function() { | ||
callback(er); | ||
}); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Potential problem is that the @bnoordhuis Thoughts on how to deal w/ this? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @trevnorris Can one even recover from a file failing to close? It appears that the state of a file descriptor is unspecified if it fails:
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Assuming fs.close() calls close(2), it cannot fail to release the fd. The error returns from close are purely advisory. Linux documents only EBADF (which isn't a leak, obviously), EIO (which is a delayed error from a prev i/o operation, interesting, but not a leak, and only NFS, I think), and EINTR, of which the following note applies:
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @sam-github According to the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Hm, looks like the open group might've had to align to solaris: https://docs.oracle.com/cd/E23824_01/html/821-1463/close-2.html#scrolltoc which allows the fd to be left in an unspecified condition. Wtf? Anyhow, no recover possible, indeed. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Wow. WTF are you supposed to do w/ a fd in an "unspecified condition"? Either way, should we report this to the user, and if so then how? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Reporting it would probably not be very helpful, since we are already in an error state at this point. The first failure is likely more interesting to the user, as he might be able to do something about it. There is nothing a user can do about There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I second @jwueller's suggestion. It's what libuv does. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Sounds good. |
||
} | ||
} | ||
}; | ||
|
||
|
@@ -355,15 +377,16 @@ fs.readFileSync = function(path, options) { | |
assertEncoding(encoding); | ||
|
||
var flag = options.flag || 'r'; | ||
var fd = fs.openSync(path, flag, 438 /*=0666*/); | ||
var isUserFd = isFd(path); // file descriptor ownership | ||
var fd = isUserFd ? path : fs.openSync(path, flag, 438 /*=0666*/); | ||
|
||
var size; | ||
var threw = true; | ||
try { | ||
size = fs.fstatSync(fd).size; | ||
threw = false; | ||
} finally { | ||
if (threw) fs.closeSync(fd); | ||
if (threw && !isUserFd) fs.closeSync(fd); | ||
} | ||
|
||
var pos = 0; | ||
|
@@ -378,7 +401,7 @@ fs.readFileSync = function(path, options) { | |
buffer = new Buffer(size); | ||
threw = false; | ||
} finally { | ||
if (threw) fs.closeSync(fd); | ||
if (threw && !isUserFd) fs.closeSync(fd); | ||
} | ||
} | ||
|
||
|
@@ -399,14 +422,15 @@ fs.readFileSync = function(path, options) { | |
} | ||
threw = false; | ||
} finally { | ||
if (threw) fs.closeSync(fd); | ||
if (threw && !isUserFd) fs.closeSync(fd); | ||
} | ||
|
||
pos += bytesRead; | ||
done = (bytesRead === 0) || (size !== 0 && pos >= size); | ||
} | ||
|
||
fs.closeSync(fd); | ||
if (!isUserFd) | ||
fs.closeSync(fd); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. No need for braces. |
||
|
||
if (size === 0) { | ||
// data was collected into the buffers list. | ||
|
@@ -1048,12 +1072,10 @@ function writeAll(fd, buffer, offset, length, position, callback) { | |
// write(fd, buffer, offset, length, position, callback) | ||
fs.write(fd, buffer, offset, length, position, function(writeErr, written) { | ||
if (writeErr) { | ||
fs.close(fd, function() { | ||
if (callback) callback(writeErr); | ||
}); | ||
callback(writeErr); | ||
} else { | ||
if (written === length) { | ||
fs.close(fd, callback); | ||
callback(null); | ||
} else { | ||
offset += written; | ||
length -= written; | ||
|
@@ -1078,16 +1100,41 @@ fs.writeFile = function(path, data, options, callback) { | |
assertEncoding(options.encoding); | ||
|
||
var flag = options.flag || 'w'; | ||
fs.open(path, flag, options.mode, function(openErr, fd) { | ||
var fd; | ||
var isUserFd; // file descriptor ownership | ||
|
||
if (isFd(path)) { | ||
fd = path; | ||
isUserFd = true; | ||
writeFd(); | ||
return; | ||
} | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. nit: save a level of indentation and just return at the end of the first |
||
|
||
fs.open(path, flag, options.mode, function(openErr, fd_) { | ||
if (openErr) { | ||
if (callback) callback(openErr); | ||
callback(openErr); | ||
} else { | ||
var buffer = util.isBuffer(data) ? data : new Buffer('' + data, | ||
options.encoding || 'utf8'); | ||
var position = /a/.test(flag) ? null : 0; | ||
writeAll(fd, buffer, 0, buffer.length, position, callback); | ||
fd = fd_; | ||
isUserFd = false; | ||
writeFd(); | ||
} | ||
}); | ||
|
||
function writeFd() { | ||
var buffer = util.isBuffer(data) ? data : new Buffer('' + data, | ||
options.encoding || 'utf8'); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Did you get the Just curious because doing There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @trevnorris I did. It's from line 981 of the original version. Should I fix it in this PR or move it into a new one? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. go ahead and fix in this PR. I'll make a commit to fix it elsewhere. Thanks for letting me know. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. What happens if There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @trevnorris Pinging again; did you see my previous comment on this? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
var position = /a/.test(flag) ? null : 0; | ||
|
||
writeAll(fd, buffer, 0, buffer.length, position, function(writeErr) { | ||
if (isUserFd) { | ||
callback(writeErr); | ||
} else { | ||
fs.close(fd, function() { | ||
callback(writeErr); | ||
}); | ||
} | ||
}); | ||
} | ||
}; | ||
|
||
fs.writeFileSync = function(path, data, options) { | ||
|
@@ -1102,7 +1149,9 @@ fs.writeFileSync = function(path, data, options) { | |
assertEncoding(options.encoding); | ||
|
||
var flag = options.flag || 'w'; | ||
var fd = fs.openSync(path, flag, options.mode); | ||
var isUserFd = isFd(path); | ||
var fd = isUserFd ? path : fs.openSync(path, flag, options.mode); | ||
|
||
if (!util.isBuffer(data)) { | ||
data = new Buffer('' + data, options.encoding || 'utf8'); | ||
} | ||
|
@@ -1115,7 +1164,7 @@ fs.writeFileSync = function(path, data, options) { | |
position += written; | ||
} | ||
} finally { | ||
fs.closeSync(fd); | ||
if (!isUserFd) fs.closeSync(fd); | ||
} | ||
}; | ||
|
||
|
@@ -1132,6 +1181,11 @@ fs.appendFile = function(path, data, options, callback_) { | |
|
||
if (!options.flag) | ||
options = util._extend({ flag: 'a' }, options); | ||
|
||
// force append behavior when using a supplied file descriptor | ||
if (isFd(path)) | ||
options.flag = 'a'; | ||
|
||
fs.writeFile(path, data, options, callback); | ||
}; | ||
|
||
|
@@ -1143,9 +1197,14 @@ fs.appendFileSync = function(path, data, options) { | |
} else if (!util.isObject(options)) { | ||
throw new TypeError('Bad arguments'); | ||
} | ||
|
||
if (!options.flag) | ||
options = util._extend({ flag: 'a' }, options); | ||
|
||
// force append behavior when using a supplied file descriptor | ||
if (isFd(path)) | ||
options.flag = 'a'; | ||
|
||
fs.writeFileSync(path, data, options); | ||
}; | ||
|
||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why remove these two lines?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
A similar note is part of the
fs.readFile(file[, options], callback)
documentation, which is referenced byfs.readFileSync(file[, options])
. No other synchronous method has any additional notes aside from referencing the asynchronous version. I tried to make the documentation more uniform, despite the note being accurate. Less duplication, less potential for errors.Would you prefer me putting it back in?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Go ahead and leave it in. Most other functions
*Sync()
functions can refer the user to aman
page. This is a special case sense the return value is not always the same (i.e. Buffer or String).