[PATCH] Support opening files in exclusive mode

Michael Santos <>
Fri Apr 30 18:22:11 CEST 2010


Add an option that atomically tests for the existence of a file and
creates it if the file does not exist, by passing the O_EXCL flag
to open() on Unix and CREATE_NEW flag on Windows. Support for O_EXCL
varies across platforms and filesystems.

{ok, Fd} = file:open("/tmp/foo", [write,exclusive]),
{error, eexist} = file:open("/tmp/foo", [write,exclusive]).
---
 erts/emulator/drivers/common/erl_efile.h |    3 ++-
 erts/emulator/drivers/unix/unix_efile.c  |    3 +++
 erts/emulator/drivers/win32/win_efile.c  |    3 +++
 erts/preloaded/src/prim_file.erl         |    5 ++++-
 lib/kernel/doc/src/file.xml              |    8 +++++++-
 lib/kernel/src/file.erl                  |    2 +-
 lib/kernel/test/file_SUITE.erl           |   20 ++++++++++++++++++--
 lib/kernel/test/prim_file_SUITE.erl      |   20 ++++++++++++++++++--
 8 files changed, 56 insertions(+), 8 deletions(-)

diff --git a/erts/emulator/drivers/common/erl_efile.h b/erts/emulator/drivers/common/erl_efile.h
index 9aa941e..d2ef9c0 100644
--- a/erts/emulator/drivers/common/erl_efile.h
+++ b/erts/emulator/drivers/common/erl_efile.h
@@ -32,7 +32,8 @@
 #define EFILE_MODE_READ_WRITE 	3
 #define EFILE_MODE_APPEND	4
 #define EFILE_COMPRESSED 	8
-#define EFILE_NO_TRUNCATE      16 /* Special for reopening on VxWorks */
+#define EFILE_MODE_EXCL        16
+#define EFILE_NO_TRUNCATE      32 /* Special for reopening on VxWorks */
 
 /*
  * Seek modes for efile_seek().
diff --git a/erts/emulator/drivers/unix/unix_efile.c b/erts/emulator/drivers/unix/unix_efile.c
index d395b68..6171272 100644
--- a/erts/emulator/drivers/unix/unix_efile.c
+++ b/erts/emulator/drivers/unix/unix_efile.c
@@ -751,6 +751,9 @@ efile_openfile(Efile_error* errInfo,	/* Where to return error codes. */
 #endif
     }
 
+    if (flags & EFILE_MODE_EXCL) {
+	mode |= O_EXCL;
+    }
 
 #ifdef VXWORKS
     if (*name != '/') {
diff --git a/erts/emulator/drivers/win32/win_efile.c b/erts/emulator/drivers/win32/win_efile.c
index 89aaad3..de5cd21 100644
--- a/erts/emulator/drivers/win32/win_efile.c
+++ b/erts/emulator/drivers/win32/win_efile.c
@@ -689,6 +689,9 @@ Sint64* pSize;			/* Where to store the size of the file. */
     if (flags & EFILE_MODE_APPEND) {
 	crFlags = OPEN_ALWAYS;
     }
+    if (flags & EFILE_MODE_EXCL) {
+	crFlags = CREATE_NEW;
+    }
     fd = CreateFile(name, access, FILE_SHARE_READ | FILE_SHARE_WRITE,
 		    NULL, crFlags, FILE_ATTRIBUTE_NORMAL, NULL);
 
diff --git a/erts/preloaded/src/prim_file.erl b/erts/preloaded/src/prim_file.erl
index 43e6f6c..035ff3e 100644
--- a/erts/preloaded/src/prim_file.erl
+++ b/erts/preloaded/src/prim_file.erl
@@ -114,9 +114,10 @@
 -define(EFILE_MODE_READ_WRITE, 3).  
 -define(EFILE_MODE_APPEND,     4).
 -define(EFILE_COMPRESSED,      8).
+-define(EFILE_MODE_EXCL,       16).
 
 %% Use this mask to get just the mode bits to be passed to the driver.
--define(EFILE_MODE_MASK, 15).
+-define(EFILE_MODE_MASK, 31).
 
 %% Seek modes for the driver's seek function.
 -define(EFILE_SEEK_SET, 0).
@@ -918,6 +919,8 @@ open_mode([compressed|Rest], Mode, Portopts, Setopts) ->
 open_mode([append|Rest], Mode, Portopts, Setopts) ->
     open_mode(Rest, Mode bor ?EFILE_MODE_APPEND bor ?EFILE_MODE_WRITE, 
 	      Portopts, Setopts);
+open_mode([exclusive|Rest], Mode, Portopts, Setopts) ->
+    open_mode(Rest, Mode bor ?EFILE_MODE_EXCL, Portopts, Setopts);
 open_mode([delayed_write|Rest], Mode, Portopts, Setopts) ->
     open_mode([{delayed_write, 64*1024, 2000}|Rest], Mode,
 	      Portopts, Setopts);
diff --git a/lib/kernel/doc/src/file.xml b/lib/kernel/doc/src/file.xml
index 50f9722..eed92d2 100644
--- a/lib/kernel/doc/src/file.xml
+++ b/lib/kernel/doc/src/file.xml
@@ -584,7 +584,7 @@ f.txt:  {person, "kalle", 25}.
       <type>
         <v>Filename = name()</v>
         <v>Modes = [Mode]</v>
-        <v> Mode = read | write | append | raw | binary | {delayed_write, Size, Delay} | delayed_write | {read_ahead, Size} | read_ahead | compressed</v>
+        <v> Mode = read | write | append | exclusive | raw | binary | {delayed_write, Size, Delay} | delayed_write | {read_ahead, Size} | read_ahead | compressed</v>
         <v>  Size = Delay = int()</v>
         <v>IoDevice = io_device()</v>
         <v>Reason = ext_posix() | system_limit</v>
@@ -611,6 +611,12 @@ f.txt:  {person, "kalle", 25}.
               file opened with <c>append</c> will take place at
               the end of the file.</p>
           </item>
+          <tag><c>exclusive</c></tag>
+          <item>
+            <p>The file, when opened for writing, is created if it
+              does not exist. If the file exists, open will return
+              <c>{error, eexist}</c>.</p>
+          </item>
           <tag><c>raw</c></tag>
           <item>
             <p>The <c>raw</c> option allows faster access to a file,
diff --git a/lib/kernel/src/file.erl b/lib/kernel/src/file.erl
index 46ffa9d..419d8db 100644
--- a/lib/kernel/src/file.erl
+++ b/lib/kernel/src/file.erl
@@ -81,7 +81,7 @@
 -type mode()      :: 'read' | 'write' | 'append' | 'raw' | 'binary' | 
 		     {'delayed_write', non_neg_integer(), non_neg_integer()} | 
 		     'delayed_write' | {'read_ahead', pos_integer()} | 
-		     'read_ahead' | 'compressed'.
+		     'read_ahead' | 'compressed' | 'exclusive'.
 -type name()      :: string() | atom() | [name()].
 -type posix()     :: atom().
 -type bindings()  :: any().
diff --git a/lib/kernel/test/file_SUITE.erl b/lib/kernel/test/file_SUITE.erl
index d01e1f1..b9d57ce 100644
--- a/lib/kernel/test/file_SUITE.erl
+++ b/lib/kernel/test/file_SUITE.erl
@@ -53,7 +53,7 @@
 -export([file_info/1, file_info_basic_file/1, file_info_basic_directory/1,
 	 file_info_bad/1, file_info_times/1, file_write_file_info/1]).
 -export([rename/1, access/1, truncate/1, sync/1,
-	 read_write/1, pread_write/1, append/1]).
+	 read_write/1, pread_write/1, append/1, exclusive/1]).
 -export([errors/1, e_delete/1, e_rename/1, e_make_dir/1, e_del_dir/1]).
 -export([otp_5814/1]).
 
@@ -377,7 +377,7 @@ win_cur_dir_1(_Config) ->
 files(suite) -> [open,pos,file_info,consult,eval,script,truncate,sync].
 
 open(suite) -> [open1,old_modes,new_modes,path_open,close,access,read_write,
-	       pread_write,append,open_errors].
+	       pread_write,append,open_errors,exclusive].
 
 open1(suite) -> [];
 open1(doc) -> [];
@@ -751,6 +751,22 @@ open_errors(Config) when is_list(Config) ->
     ?line test_server:timetrap_cancel(Dog),
     ok.
 
+exclusive(suite) -> [];
+exclusive(doc) -> "Test exclusive access to a file.";
+exclusive(Config) when is_list(Config) ->
+    ?line Dog = test_server:timetrap(test_server:seconds(5)),
+    ?line RootDir = ?config(priv_dir,Config),
+    ?line NewDir = filename:join(RootDir,
+				 atom_to_list(?MODULE)
+				 ++"_exclusive"),
+    ?line ok = ?FILE_MODULE:make_dir(NewDir),
+    ?line Name = filename:join(NewDir, "ex_file.txt"),
+    ?line {ok, Fd} = ?FILE_MODULE:open(Name, [write, exclusive]),
+    ?line {error, eexist} = ?FILE_MODULE:open(Name, [write, exclusive]),
+    ?line ok = ?FILE_MODULE:close(Fd),
+    ?line test_server:timetrap_cancel(Dog),
+    ok.
+
 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
 
 pos(suite) -> [pos1,pos2].
diff --git a/lib/kernel/test/prim_file_SUITE.erl b/lib/kernel/test/prim_file_SUITE.erl
index 860aeec..9f6dd78 100644
--- a/lib/kernel/test/prim_file_SUITE.erl
+++ b/lib/kernel/test/prim_file_SUITE.erl
@@ -35,7 +35,7 @@
 	 file_write_file_info_a/1, file_write_file_info_b/1]).
 -export([rename_a/1, rename_b/1, 
 	 access/1, truncate/1, sync/1,
-	 read_write/1, pread_write/1, append/1]).
+	 read_write/1, pread_write/1, append/1, exclusive/1]).
 -export([errors/1, e_delete/1, e_rename/1, e_make_dir/1, e_del_dir/1]).
 
 -export([compression/1, read_not_really_compressed/1,
@@ -380,7 +380,7 @@ win_cur_dir_1(_Config, Handle) ->
 files(suite) -> [open,pos,file_info,truncate,sync].
 
 open(suite) -> [open1,modes,close,access,read_write,
-	       pread_write,append].
+	       pread_write,append,exclusive].
 
 open1(suite) -> [];
 open1(doc) -> [];
@@ -605,6 +605,22 @@ append(Config) when is_list(Config) ->
     ?line test_server:timetrap_cancel(Dog),
     ok.
 
+exclusive(suite) -> [];
+exclusive(doc) -> "Test exclusive access to a file.";
+exclusive(Config) when is_list(Config) ->
+    ?line Dog = test_server:timetrap(test_server:seconds(5)),
+    ?line RootDir = ?config(priv_dir,Config),
+    ?line NewDir = filename:join(RootDir,
+				 atom_to_list(?MODULE)
+				 ++"_exclusive"),
+    ?line ok = ?PRIM_FILE:make_dir(NewDir),
+    ?line Name = filename:join(NewDir, "ex_file.txt"),
+    ?line {ok,Fd} = ?PRIM_FILE:open(Name, [write, exclusive]),
+    ?line {error, eexist} = ?PRIM_FILE:open(Name, [write, exclusive]),
+    ?line ok = ?PRIM_FILE:close(Fd),
+    ?line test_server:timetrap_cancel(Dog),
+    ok.
+
 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
 
 pos(suite) -> [pos1,pos2].
-- 
1.5.6.4



More information about the erlang-patches mailing list