fs: Support mode/flag options to read/append/writeFile
authorisaacs <i@izs.me>
Fri, 1 Mar 2013 17:10:26 +0000 (09:10 -0800)
committerisaacs <i@izs.me>
Fri, 1 Mar 2013 17:48:57 +0000 (09:48 -0800)
Fix #4841

doc/api/fs.markdown
lib/fs.js
test/simple/test-fs-append-file-sync.js
test/simple/test-fs-append-file.js
test/simple/test-fs-write-file.js

index 100bcc1..29d3aa3 100644 (file)
@@ -410,7 +410,12 @@ The callback is given the three arguments, `(err, bytesRead, buffer)`.
 
 Synchronous version of `fs.read`. Returns the number of `bytesRead`.
 
-## fs.readFile(filename, [encoding], [callback])
+## fs.readFile(filename, [options], [callback])
+
+* `filename` {String}
+* `options` {Object}
+  * `encoding` {String | Null} default = `null`
+  * `flag` {String} default = `'r'`
 
 Asynchronously reads the entire contents of a file. Example:
 
@@ -425,19 +430,28 @@ contents of the file.
 If no encoding is specified, then the raw buffer is returned.
 
 
-## fs.readFileSync(filename, [encoding])
+## fs.readFileSync(filename, [options])
 
 Synchronous version of `fs.readFile`. Returns the contents of the `filename`.
 
-If `encoding` is specified then this function returns a string. Otherwise it
-returns a buffer.
+If the `encoding` option is specified then this function returns a
+string. Otherwise it returns a buffer.
+
 
+## fs.writeFile(filename, data, [options], [callback])
 
-## fs.writeFile(filename, data, [encoding], [callback])
+* `filename` {String}
+* `data` {String | Buffer}
+* `options` {Object}
+  * `encoding` {String | Null} default = `'utf8'`
+  * `mode` {Number} default = `438` (aka `0666` in Octal)
+  * `flag` {String} default = `'w'`
 
 Asynchronously writes data to a file, replacing the file if it already exists.
-`data` can be a string or a buffer. The `encoding` argument is ignored if
-`data` is a buffer. It defaults to `'utf8'`.
+`data` can be a string or a buffer.
+
+The `encoding` option is ignored if `data` is a buffer. It defaults
+to `'utf8'`.
 
 Example:
 
@@ -446,15 +460,21 @@ Example:
       console.log('It\'s saved!');
     });
 
-## fs.writeFileSync(filename, data, [encoding])
+## fs.writeFileSync(filename, data, [options])
 
 The synchronous version of `fs.writeFile`.
 
-## fs.appendFile(filename, data, encoding='utf8', [callback])
+## fs.appendFile(filename, data, [options], [callback])
+
+* `filename` {String}
+* `data` {String | Buffer}
+* `options` {Object}
+  * `encoding` {String | Null} default = `'utf8'`
+  * `mode` {Number} default = `438` (aka `0666` in Octal)
+  * `flag` {String} default = `'a'`
 
 Asynchronously append data to a file, creating the file if it not yet exists.
-`data` can be a string or a buffer. The `encoding` argument is ignored if
-`data` is a buffer.
+`data` can be a string or a buffer.
 
 Example:
 
@@ -463,7 +483,7 @@ Example:
       console.log('The "data to append" was appended to file!');
     });
 
-## fs.appendFileSync(filename, data, encoding='utf8')
+## fs.appendFileSync(filename, data, [options])
 
 The synchronous version of `fs.appendFile`.
 
index a1bc487..1ad0b2e 100644 (file)
--- a/lib/fs.js
+++ b/lib/fs.js
@@ -165,10 +165,20 @@ fs.existsSync = function(path) {
   }
 };
 
-fs.readFile = function(path, encoding_) {
-  var encoding = typeof(encoding_) === 'string' ? encoding_ : null;
+fs.readFile = function(path, options, callback_) {
   var callback = maybeCallback(arguments[arguments.length - 1]);
 
+  if (typeof options === 'function' || !options) {
+    options = { encoding: null, flag: 'r' };
+  } else if (typeof options === 'string') {
+    options = { encoding: options, flag: 'r' };
+  } else if (!options) {
+    options = { encoding: null, flag: 'r' };
+  } else if (typeof options !== 'object') {
+    throw new TypeError('Bad arguments');
+  }
+
+  var encoding = options.encoding;
   assertEncoding(encoding);
 
   // first, stat the file, so we know the size.
@@ -178,7 +188,8 @@ fs.readFile = function(path, encoding_) {
   var pos = 0;
   var fd;
 
-  fs.open(path, constants.O_RDONLY, 438 /*=0666*/, function(er, fd_) {
+  var flag = options.flag || 'r';
+  fs.open(path, flag, 438 /*=0666*/, function(er, fd_) {
     if (er) return callback(er);
     fd = fd_;
 
@@ -243,10 +254,20 @@ fs.readFile = function(path, encoding_) {
   }
 };
 
-fs.readFileSync = function(path, encoding) {
+fs.readFileSync = function(path, options) {
+  if (!options) {
+    options = { encoding: null, flag: 'r' };
+  } else if (typeof options === 'string') {
+    options = { encoding: options, flag: 'r' };
+  } else if (typeof options !== 'object') {
+    throw new TypeError('Bad arguments');
+  }
+
+  var encoding = options.encoding;
   assertEncoding(encoding);
 
-  var fd = fs.openSync(path, constants.O_RDONLY, 438 /*=0666*/);
+  var flag = options.flag || 'r';
+  var fd = fs.openSync(path, flag, 438 /*=0666*/);
 
   var size;
   var threw = true;
@@ -888,72 +909,93 @@ function writeAll(fd, buffer, offset, length, position, callback) {
   });
 }
 
-fs.writeFile = function(path, data, encoding_, callback) {
-  var encoding = (typeof(encoding_) == 'string' ? encoding_ : 'utf8');
-  assertEncoding(encoding);
+fs.writeFile = function(path, data, options, callback) {
+  var callback = maybeCallback(arguments[arguments.length - 1]);
 
-  callback = maybeCallback(arguments[arguments.length - 1]);
-  fs.open(path, 'w', 438 /*=0666*/, function(openErr, fd) {
+  if (typeof options === 'function' || !options) {
+    options = { encoding: 'utf8', mode: 438 /*=0666*/, flag: 'w' };
+  } else if (typeof options === 'string') {
+    options = { encoding: options, mode: 438, flag: 'w' };
+  } else if (!options) {
+    options = { encoding: 'utf8', mode: 438 /*=0666*/, flag: 'w' };
+  } else if (typeof options !== 'object') {
+    throw new TypeError('Bad arguments');
+  }
+
+  assertEncoding(options.encoding);
+
+  var flag = options.flag || 'w';
+  fs.open(path, options.flag || 'w', options.mode, function(openErr, fd) {
     if (openErr) {
       if (callback) callback(openErr);
     } else {
       var buffer = Buffer.isBuffer(data) ? data : new Buffer('' + data,
-          encoding);
-      writeAll(fd, buffer, 0, buffer.length, 0, callback);
+          options.encoding || 'utf8');
+      var position = /a/.test(flag) ? null : 0;
+      writeAll(fd, buffer, 0, buffer.length, position, callback);
     }
   });
 };
 
-fs.writeFileSync = function(path, data, encoding) {
-  assertEncoding(encoding);
+fs.writeFileSync = function(path, data, options) {
+  if (!options) {
+    options = { encoding: 'utf8', mode: 438 /*=0666*/, flag: 'w' };
+  } else if (typeof options === 'string') {
+    options = { encoding: options, mode: 438, flag: 'w' };
+  } else if (typeof options !== 'object') {
+    throw new TypeError('Bad arguments');
+  }
 
-  var fd = fs.openSync(path, 'w');
+  assertEncoding(options.encoding);
+
+  var flag = options.flag || 'w';
+  var fd = fs.openSync(path, flag);
   if (!Buffer.isBuffer(data)) {
-    data = new Buffer('' + data, encoding || 'utf8');
+    data = new Buffer('' + data, options.encoding || 'utf8');
   }
   var written = 0;
   var length = data.length;
+  var position = /a/.test(flag) ? null : 0;
   try {
     while (written < length) {
-      written += fs.writeSync(fd, data, written, length - written, written);
+      written += fs.writeSync(fd, data, written, length - written, position);
+      position += written;
     }
   } finally {
     fs.closeSync(fd);
   }
 };
 
-fs.appendFile = function(path, data, encoding_, callback) {
-  var encoding = (typeof(encoding_) == 'string' ? encoding_ : 'utf8');
-  assertEncoding(encoding);
+fs.appendFile = function(path, data, options, callback_) {
+  var callback = maybeCallback(arguments[arguments.length - 1]);
 
-  callback = maybeCallback(arguments[arguments.length - 1]);
+  if (typeof options === 'function' || !options) {
+    options = { encoding: 'utf8', mode: 438 /*=0666*/, flag: 'a' };
+  } else if (typeof options === 'string') {
+    options = { encoding: options, mode: 438, flag: 'a' };
+  } else if (!options) {
+    options = { encoding: 'utf8', mode: 438 /*=0666*/, flag: 'a' };
+  } else if (typeof options !== 'object') {
+    throw new TypeError('Bad arguments');
+  }
 
-  fs.open(path, 'a', 438 /*=0666*/, function(err, fd) {
-    if (err) return callback(err);
-    var buffer = Buffer.isBuffer(data) ? data : new Buffer('' + data, encoding);
-    writeAll(fd, buffer, 0, buffer.length, null, callback);
-  });
+  if (!options.flag)
+    options = util._extend({ flag: 'a' }, options);
+  fs.writeFile(path, data, options, callback);
 };
 
-fs.appendFileSync = function(path, data, encoding) {
-  assertEncoding(encoding);
-
-  var fd = fs.openSync(path, 'a');
-  if (!Buffer.isBuffer(data)) {
-    data = new Buffer('' + data, encoding || 'utf8');
+fs.appendFileSync = function(path, data, options) {
+  if (!options) {
+    options = { encoding: 'utf8', mode: 438 /*=0666*/, flag: 'a' };
+  } else if (typeof options === 'string') {
+    options = { encoding: options, mode: 438, flag: 'a' };
+  } else if (typeof options !== 'object') {
+    throw new TypeError('Bad arguments');
   }
-  var written = 0;
-  var position = null;
-  var length = data.length;
+  if (!options.flag)
+    options = util._extend({ flag: 'a' }, options);
 
-  try {
-    while (written < length) {
-      written += fs.writeSync(fd, data, written, length - written, position);
-      position += written; // XXX not safe with multiple concurrent writers?
-    }
-  } finally {
-    fs.closeSync(fd);
-  }
+  fs.writeFileSync(path, data, options);
 };
 
 function errnoException(errorno, syscall) {
index 4e95c67..7bc6a01 100644 (file)
@@ -73,10 +73,17 @@ assert.equal(buf.length + currentFileData.length, fileData3.length);
 
 // test that appendFile accepts numbers.
 var filename4 = join(common.tmpDir, 'append-sync4.txt');
-fs.writeFileSync(filename4, currentFileData);
+fs.writeFileSync(filename4, currentFileData, { mode: m });
 
 common.error('appending to ' + filename4);
-fs.appendFileSync(filename4, num);
+var m = 0600;
+fs.appendFileSync(filename4, num, { mode: m });
+
+// windows permissions aren't unix
+if (process.platform !== 'win32') {
+  var st = fs.statSync(filename4);
+  assert.equal(st.mode & 0700, m);
+}
 
 var fileData4 = fs.readFileSync(filename4);
 
index ad32508..450c8d3 100644 (file)
@@ -101,12 +101,19 @@ fs.writeFileSync(filename4, currentFileData);
 
 common.error('appending to ' + filename4);
 
-fs.appendFile(filename4, n, function(e) {
+var m = 0600;
+fs.appendFile(filename4, n, { mode: m }, function(e) {
   if (e) throw e;
 
   ncallbacks++;
   common.error('appended to file4');
 
+  // windows permissions aren't unix
+  if (process.platform !== 'win32') {
+    var st = fs.statSync(filename4);
+    assert.equal(st.mode & 0700, m);
+  }
+
   fs.readFile(filename4, function(e, buffer) {
     if (e) throw e;
     common.error('file4 read');
index 00704ed..b70ea44 100644 (file)
@@ -76,9 +76,16 @@ fs.writeFile(filename2, buf, function(e) {
 var filename3 = join(common.tmpDir, 'test3.txt');
 common.error('writing to ' + filename3);
 
-fs.writeFile(filename3, n, function(e) {
+var m = 0600;
+fs.writeFile(filename3, n, { mode: m }, function(e) {
   if (e) throw e;
 
+  // windows permissions aren't unix
+  if (process.platform !== 'win32') {
+    var st = fs.statSync(filename3);
+    assert.equal(st.mode & 0700, m);
+  }
+
   ncallbacks++;
   common.error('file3 written');