fs: add O_EXCL support, exclusive open file
authorBen Noordhuis <info@bnoordhuis.nl>
Tue, 31 Jan 2012 00:36:57 +0000 (01:36 +0100)
committerBen Noordhuis <info@bnoordhuis.nl>
Tue, 31 Jan 2012 13:29:32 +0000 (14:29 +0100)
doc/api/fs.markdown
lib/fs.js
test/simple/test-fs-open-flags.js

index 62c40b3d91d683e09cbd7fe6fef613a7a0ecc3cb..16f8fbb35a2bb252bebd9acdc6af8f1abbec606a 100644 (file)
@@ -259,17 +259,29 @@ An exception occurs if the file does not exist.
 * `'w'` - Open file for writing.
 The file is created (if it does not exist) or truncated (if it exists).
 
+* `'wx'` - Like `'w'` but opens the file in exclusive mode.
+
 * `'w+'` - Open file for reading and writing.
 The file is created (if it does not exist) or truncated (if it exists).
 
+* `'wx+'` - Like `'w+'` but opens the file in exclusive mode.
+
 * `'a'` - Open file for appending.
 The file is created if it does not exist.
 
+* `'ax'` - Like `'a'` but opens the file in exclusive mode.
+
 * `'a+'` - Open file for reading and appending.
 The file is created if it does not exist.
 
+* `'ax+'` - Like `'a+'` but opens the file in exclusive mode.
+
 `mode` defaults to `0666`. The callback gets two arguments `(err, fd)`.
 
+Exclusive mode (`O_EXCL`) ensures that `path` is newly created. `fs.open()`
+fails if a file by that name already exists. On POSIX systems, symlinks are
+not followed. Exclusive mode may or may not work with network file systems.
+
 ### fs.openSync(path, flags, [mode])
 
 Synchronous open(2).
index a44654c8027c6433b54c0a47b03e3b3c7befda41..1c8f975504cf1c3112a167a9ef547574a9a9ab0a 100644 (file)
--- a/lib/fs.js
+++ b/lib/fs.js
@@ -37,6 +37,19 @@ var EventEmitter = require('events').EventEmitter;
 var kMinPoolSpace = 128;
 var kPoolSize = 40 * 1024;
 
+var O_APPEND    = constants.O_APPEND    || 0;
+var O_CREAT     = constants.O_CREAT     || 0;
+var O_DIRECTORY = constants.O_DIRECTORY || 0;
+var O_EXCL      = constants.O_EXCL      || 0;
+var O_NOCTTY    = constants.O_NOCTTY    || 0;
+var O_NOFOLLOW  = constants.O_NOFOLLOW  || 0;
+var O_RDONLY    = constants.O_RDONLY    || 0;
+var O_RDWR      = constants.O_RDWR      || 0;
+var O_SYMLINK   = constants.O_SYMLINK   || 0;
+var O_SYNC      = constants.O_SYNC      || 0;
+var O_TRUNC     = constants.O_TRUNC     || 0;
+var O_WRONLY    = constants.O_WRONLY    || 0;
+
 fs.Stats = binding.Stats;
 
 fs.Stats.prototype._checkModeProperty = function(property) {
@@ -178,28 +191,35 @@ function stringToFlags(flag) {
   if (typeof flag !== 'string') {
     return flag;
   }
-  switch (flag) {
-    case 'r':
-      return constants.O_RDONLY;
 
-    case 'r+':
-      return constants.O_RDWR;
+  // O_EXCL is mandated by POSIX, Windows supports it too.
+  // Let's add a check anyway, just in case.
+  if (!O_EXCL && ~flag.indexOf('x')) {
+    throw errnoException('ENOSYS', 'fs.open(O_EXCL)');
+  }
 
-    case 'w':
-      return constants.O_CREAT | constants.O_TRUNC | constants.O_WRONLY;
+  switch (flag) {
+    case 'r'  : return O_RDONLY;
+    case 'r+' : return O_RDWR;
 
-    case 'w+':
-      return constants.O_CREAT | constants.O_TRUNC | constants.O_RDWR;
+    case 'w'  : return O_TRUNC | O_CREAT | O_WRONLY;
+    case 'wx' : // fall through
+    case 'xw' : return O_TRUNC | O_CREAT | O_WRONLY | O_EXCL;
 
-    case 'a':
-      return constants.O_APPEND | constants.O_CREAT | constants.O_WRONLY;
+    case 'w+' : return O_TRUNC | O_CREAT | O_RDWR;
+    case 'wx+': // fall through
+    case 'xw+': return O_TRUNC | O_CREAT | O_RDWR | O_EXCL;
 
-    case 'a+':
-      return constants.O_APPEND | constants.O_CREAT | constants.O_RDWR;
+    case 'a'  : return O_APPEND | O_CREAT | O_WRONLY;
+    case 'ax' : // fall through
+    case 'xa' : return O_APPEND | O_CREAT | O_WRONLY | O_EXCL;
 
-    default:
-      throw new Error('Unknown file open flag: ' + flag);
+    case 'a+' : return O_APPEND | O_CREAT | O_RDWR;
+    case 'ax+': // fall through
+    case 'xa+': return O_APPEND | O_CREAT | O_RDWR | O_EXCL;
   }
+
+  throw new Error('Unknown file open flag: ' + flag);
 }
 
 // exported but hidden, only used by test/simple/test-fs-open-flags.js
index 4e01b11fa72f86a057af3cbc10423db40aec8313..7d51cc9a4c1b897b42f3e66b55a452a7de40c1b4 100644 (file)
@@ -45,6 +45,16 @@ assert.equal(fs._stringToFlags('w+'), O_TRUNC|O_CREAT|O_RDWR);
 assert.equal(fs._stringToFlags('a'),  O_APPEND|O_CREAT|O_WRONLY);
 assert.equal(fs._stringToFlags('a+'), O_APPEND|O_CREAT|O_RDWR);
 
-'+ +a +r +w rw wa war raw r++ a++ w++'.split(' ').forEach(function(flags) {
+assert.equal(fs._stringToFlags('wx'),  O_TRUNC|O_CREAT|O_WRONLY|O_EXCL);
+assert.equal(fs._stringToFlags('xw'),  O_TRUNC|O_CREAT|O_WRONLY|O_EXCL);
+assert.equal(fs._stringToFlags('wx+'), O_TRUNC|O_CREAT|O_RDWR|O_EXCL);
+assert.equal(fs._stringToFlags('xw+'), O_TRUNC|O_CREAT|O_RDWR|O_EXCL);
+assert.equal(fs._stringToFlags('ax'),  O_APPEND|O_CREAT|O_WRONLY|O_EXCL);
+assert.equal(fs._stringToFlags('xa'),  O_APPEND|O_CREAT|O_WRONLY|O_EXCL);
+assert.equal(fs._stringToFlags('ax+'), O_APPEND|O_CREAT|O_RDWR|O_EXCL);
+assert.equal(fs._stringToFlags('xa+'), O_APPEND|O_CREAT|O_RDWR|O_EXCL);
+
+('+ +a +r +w rw wa war raw r++ a++ w++' +
+ 'x +x x+ rx rx+ wxx wax xwx xxx').split(' ').forEach(function(flags) {
   assert.throws(function() { fs._stringToFlags(flags); });
 });