Skip to content

Commit 5e0759f

Browse files
jwuellerrvagg
authored andcommitted
fs: add file descriptor support to *File() funcs
These changes affect the following functions and their synchronous counterparts: * fs.readFile() * fs.writeFile() * fs.appendFile() If the first parameter is a uint32, it is treated as a file descriptor. In all other cases, the original implementation is used to ensure backwards compatibility. File descriptor ownership is never taken from the user. The documentation was adjusted to reflect these API changes. A note was added to make the user aware of file descriptor ownership and the conditions under which a file descriptor can be used by each of these functions. Tests were extended to test for file descriptor parameters under the conditions noted in the relevant documentation. PR-URL: #3163 Reviewed-By: Trevor Norris <[email protected]>
1 parent ee2e641 commit 5e0759f

7 files changed

+230
-29
lines changed

doc/api/fs.markdown

+25-11
Original file line numberDiff line numberDiff line change
@@ -468,9 +468,9 @@ The callback is given the three arguments, `(err, bytesRead, buffer)`.
468468

469469
Synchronous version of `fs.read`. Returns the number of `bytesRead`.
470470

471-
## fs.readFile(filename[, options], callback)
471+
## fs.readFile(file[, options], callback)
472472

473-
* `filename` {String}
473+
* `file` {String | Integer} filename or file descriptor
474474
* `options` {Object | String}
475475
* `encoding` {String | Null} default = `null`
476476
* `flag` {String} default = `'r'`
@@ -492,18 +492,20 @@ If `options` is a string, then it specifies the encoding. Example:
492492

493493
fs.readFile('/etc/passwd', 'utf8', callback);
494494

495+
Any specified file descriptor has to support reading.
495496

496-
## fs.readFileSync(filename[, options])
497+
_Note: Specified file descriptors will not be closed automatically._
497498

498-
Synchronous version of `fs.readFile`. Returns the contents of the `filename`.
499+
## fs.readFileSync(file[, options])
500+
501+
Synchronous version of `fs.readFile`. Returns the contents of the `file`.
499502

500503
If the `encoding` option is specified then this function returns a
501504
string. Otherwise it returns a buffer.
502505

506+
## fs.writeFile(file, data[, options], callback)
503507

504-
## fs.writeFile(filename, data[, options], callback)
505-
506-
* `filename` {String}
508+
* `file` {String | Integer} filename or file descriptor
507509
* `data` {String | Buffer}
508510
* `options` {Object | String}
509511
* `encoding` {String | Null} default = `'utf8'`
@@ -528,13 +530,21 @@ If `options` is a string, then it specifies the encoding. Example:
528530

529531
fs.writeFile('message.txt', 'Hello Node.js', 'utf8', callback);
530532

531-
## fs.writeFileSync(filename, data[, options])
533+
Any specified file descriptor has to support writing.
534+
535+
Note that it is unsafe to use `fs.writeFile` multiple times on the same file
536+
without waiting for the callback. For this scenario,
537+
`fs.createWriteStream` is strongly recommended.
538+
539+
_Note: Specified file descriptors will not be closed automatically._
540+
541+
## fs.writeFileSync(file, data[, options])
532542

533543
The synchronous version of `fs.writeFile`. Returns `undefined`.
534544

535-
## fs.appendFile(filename, data[, options], callback)
545+
## fs.appendFile(file, data[, options], callback)
536546

537-
* `filename` {String}
547+
* `file` {String | Integer} filename or file descriptor
538548
* `data` {String | Buffer}
539549
* `options` {Object | String}
540550
* `encoding` {String | Null} default = `'utf8'`
@@ -556,7 +566,11 @@ If `options` is a string, then it specifies the encoding. Example:
556566

557567
fs.appendFile('message.txt', 'data to append', 'utf8', callback);
558568

559-
## fs.appendFileSync(filename, data[, options])
569+
Any specified file descriptor has to have been opened for appending.
570+
571+
_Note: Specified file descriptors will not be closed automatically._
572+
573+
## fs.appendFileSync(file, data[, options])
560574

561575
The synchronous version of `fs.appendFile`. Returns `undefined`.
562576

lib/fs.js

+70-16
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,10 @@ function nullCheck(path, callback) {
101101
return true;
102102
}
103103

104+
function isFd(path) {
105+
return (path >>> 0) === path;
106+
}
107+
104108
// Static method to set the stats properties on a Stats object.
105109
fs.Stats = function(
106110
dev,
@@ -243,10 +247,18 @@ fs.readFile = function(path, options, callback_) {
243247
return;
244248

245249
var context = new ReadFileContext(callback, encoding);
250+
context.isUserFd = isFd(path); // file descriptor ownership
246251
var req = new FSReqWrap();
247252
req.context = context;
248253
req.oncomplete = readFileAfterOpen;
249254

255+
if (context.isUserFd) {
256+
process.nextTick(function() {
257+
req.oncomplete(null, path);
258+
});
259+
return;
260+
}
261+
250262
binding.open(pathModule._makeLong(path),
251263
stringToFlags(flag),
252264
0o666,
@@ -257,6 +269,7 @@ const kReadFileBufferLength = 8 * 1024;
257269

258270
function ReadFileContext(callback, encoding) {
259271
this.fd = undefined;
272+
this.isUserFd = undefined;
260273
this.size = undefined;
261274
this.callback = callback;
262275
this.buffers = null;
@@ -293,6 +306,14 @@ ReadFileContext.prototype.close = function(err) {
293306
req.oncomplete = readFileAfterClose;
294307
req.context = this;
295308
this.err = err;
309+
310+
if (this.isUserFd) {
311+
process.nextTick(function() {
312+
req.oncomplete(null);
313+
});
314+
return;
315+
}
316+
296317
binding.close(this.fd, req);
297318
};
298319

@@ -394,7 +415,8 @@ fs.readFileSync = function(path, options) {
394415
assertEncoding(encoding);
395416

396417
var flag = options.flag || 'r';
397-
var fd = fs.openSync(path, flag, 0o666);
418+
var isUserFd = isFd(path); // file descriptor ownership
419+
var fd = isUserFd ? path : fs.openSync(path, flag, 0o666);
398420

399421
var st;
400422
var size;
@@ -404,7 +426,7 @@ fs.readFileSync = function(path, options) {
404426
size = st.isFile() ? st.size : 0;
405427
threw = false;
406428
} finally {
407-
if (threw) fs.closeSync(fd);
429+
if (threw && !isUserFd) fs.closeSync(fd);
408430
}
409431

410432
var pos = 0;
@@ -419,7 +441,7 @@ fs.readFileSync = function(path, options) {
419441
buffer = new Buffer(size);
420442
threw = false;
421443
} finally {
422-
if (threw) fs.closeSync(fd);
444+
if (threw && !isUserFd) fs.closeSync(fd);
423445
}
424446
}
425447

@@ -442,14 +464,15 @@ fs.readFileSync = function(path, options) {
442464
}
443465
threw = false;
444466
} finally {
445-
if (threw) fs.closeSync(fd);
467+
if (threw && !isUserFd) fs.closeSync(fd);
446468
}
447469

448470
pos += bytesRead;
449471
done = (bytesRead === 0) || (size !== 0 && pos >= size);
450472
}
451473

452-
fs.closeSync(fd);
474+
if (!isUserFd)
475+
fs.closeSync(fd);
453476

454477
if (size === 0) {
455478
// data was collected into the buffers list.
@@ -1096,25 +1119,33 @@ fs.futimesSync = function(fd, atime, mtime) {
10961119
binding.futimes(fd, atime, mtime);
10971120
};
10981121

1099-
function writeAll(fd, buffer, offset, length, position, callback_) {
1122+
function writeAll(fd, isUserFd, buffer, offset, length, position, callback_) {
11001123
var callback = maybeCallback(arguments[arguments.length - 1]);
11011124

11021125
// write(fd, buffer, offset, length, position, callback)
11031126
fs.write(fd, buffer, offset, length, position, function(writeErr, written) {
11041127
if (writeErr) {
1105-
fs.close(fd, function() {
1128+
if (isUserFd) {
11061129
if (callback) callback(writeErr);
1107-
});
1130+
} else {
1131+
fs.close(fd, function() {
1132+
if (callback) callback(writeErr);
1133+
});
1134+
}
11081135
} else {
11091136
if (written === length) {
1110-
fs.close(fd, callback);
1137+
if (isUserFd) {
1138+
if (callback) callback(null);
1139+
} else {
1140+
fs.close(fd, callback);
1141+
}
11111142
} else {
11121143
offset += written;
11131144
length -= written;
11141145
if (position !== null) {
11151146
position += written;
11161147
}
1117-
writeAll(fd, buffer, offset, length, position, callback);
1148+
writeAll(fd, isUserFd, buffer, offset, length, position, callback);
11181149
}
11191150
}
11201151
});
@@ -1134,16 +1165,27 @@ fs.writeFile = function(path, data, options, callback_) {
11341165
assertEncoding(options.encoding);
11351166

11361167
var flag = options.flag || 'w';
1168+
1169+
if (isFd(path)) {
1170+
writeFd(path, true);
1171+
return;
1172+
}
1173+
11371174
fs.open(path, flag, options.mode, function(openErr, fd) {
11381175
if (openErr) {
11391176
if (callback) callback(openErr);
11401177
} else {
1141-
var buffer = (data instanceof Buffer) ? data : new Buffer('' + data,
1142-
options.encoding || 'utf8');
1143-
var position = /a/.test(flag) ? null : 0;
1144-
writeAll(fd, buffer, 0, buffer.length, position, callback);
1178+
writeFd(fd, false);
11451179
}
11461180
});
1181+
1182+
function writeFd(fd, isUserFd) {
1183+
var buffer = (data instanceof Buffer) ? data : new Buffer('' + data,
1184+
options.encoding || 'utf8');
1185+
var position = /a/.test(flag) ? null : 0;
1186+
1187+
writeAll(fd, isUserFd, buffer, 0, buffer.length, position, callback);
1188+
}
11471189
};
11481190

11491191
fs.writeFileSync = function(path, data, options) {
@@ -1158,7 +1200,9 @@ fs.writeFileSync = function(path, data, options) {
11581200
assertEncoding(options.encoding);
11591201

11601202
var flag = options.flag || 'w';
1161-
var fd = fs.openSync(path, flag, options.mode);
1203+
var isUserFd = isFd(path); // file descriptor ownership
1204+
var fd = isUserFd ? path : fs.openSync(path, flag, options.mode);
1205+
11621206
if (!(data instanceof Buffer)) {
11631207
data = new Buffer('' + data, options.encoding || 'utf8');
11641208
}
@@ -1175,7 +1219,7 @@ fs.writeFileSync = function(path, data, options) {
11751219
}
11761220
}
11771221
} finally {
1178-
fs.closeSync(fd);
1222+
if (!isUserFd) fs.closeSync(fd);
11791223
}
11801224
};
11811225

@@ -1192,6 +1236,11 @@ fs.appendFile = function(path, data, options, callback_) {
11921236

11931237
if (!options.flag)
11941238
options = util._extend({ flag: 'a' }, options);
1239+
1240+
// force append behavior when using a supplied file descriptor
1241+
if (isFd(path))
1242+
options.flag = 'a';
1243+
11951244
fs.writeFile(path, data, options, callback);
11961245
};
11971246

@@ -1203,9 +1252,14 @@ fs.appendFileSync = function(path, data, options) {
12031252
} else if (typeof options !== 'object') {
12041253
throwOptionsError(options);
12051254
}
1255+
12061256
if (!options.flag)
12071257
options = util._extend({ flag: 'a' }, options);
12081258

1259+
// force append behavior when using a supplied file descriptor
1260+
if (isFd(path))
1261+
options.flag = 'a';
1262+
12091263
fs.writeFileSync(path, data, options);
12101264
};
12111265

test/parallel/test-fs-append-file-sync.js

+14
Original file line numberDiff line numberDiff line change
@@ -66,11 +66,25 @@ var fileData4 = fs.readFileSync(filename4);
6666
assert.equal(Buffer.byteLength('' + num) + currentFileData.length,
6767
fileData4.length);
6868

69+
// test that appendFile accepts file descriptors
70+
var filename5 = join(common.tmpDir, 'append-sync5.txt');
71+
fs.writeFileSync(filename5, currentFileData);
72+
73+
var filename5fd = fs.openSync(filename5, 'a+', 0o600);
74+
fs.appendFileSync(filename5fd, data);
75+
fs.closeSync(filename5fd);
76+
77+
var fileData5 = fs.readFileSync(filename5);
78+
79+
assert.equal(Buffer.byteLength(data) + currentFileData.length,
80+
fileData5.length);
81+
6982
//exit logic for cleanup
7083

7184
process.on('exit', function() {
7285
fs.unlinkSync(filename);
7386
fs.unlinkSync(filename2);
7487
fs.unlinkSync(filename3);
7588
fs.unlinkSync(filename4);
89+
fs.unlinkSync(filename5);
7690
});

test/parallel/test-fs-append-file.js

+32-1
Original file line numberDiff line numberDiff line change
@@ -92,11 +92,42 @@ fs.appendFile(filename4, n, { mode: m }, function(e) {
9292
});
9393
});
9494

95+
// test that appendFile accepts file descriptors
96+
var filename5 = join(common.tmpDir, 'append5.txt');
97+
fs.writeFileSync(filename5, currentFileData);
98+
99+
fs.open(filename5, 'a+', function(e, fd) {
100+
if (e) throw e;
101+
102+
ncallbacks++;
103+
104+
fs.appendFile(fd, s, function(e) {
105+
if (e) throw e;
106+
107+
ncallbacks++;
108+
109+
fs.close(fd, function(e) {
110+
if (e) throw e;
111+
112+
ncallbacks++;
113+
114+
fs.readFile(filename5, function(e, buffer) {
115+
if (e) throw e;
116+
117+
ncallbacks++;
118+
assert.equal(Buffer.byteLength(s) + currentFileData.length,
119+
buffer.length);
120+
});
121+
});
122+
});
123+
});
124+
95125
process.on('exit', function() {
96-
assert.equal(8, ncallbacks);
126+
assert.equal(12, ncallbacks);
97127

98128
fs.unlinkSync(filename);
99129
fs.unlinkSync(filename2);
100130
fs.unlinkSync(filename3);
101131
fs.unlinkSync(filename4);
132+
fs.unlinkSync(filename5);
102133
});

0 commit comments

Comments
 (0)