diff --git a/include/ruby/io.h b/include/ruby/io.h index 048090c..737c509 100644 --- a/include/ruby/io.h +++ b/include/ruby/io.h @@ -98,7 +98,6 @@ typedef struct rb_io_t { } rb_io_t; #define HAVE_RB_IO_T 1 - #define FMODE_READABLE 0x00000001 #define FMODE_WRITABLE 0x00000002 #define FMODE_READWRITE (FMODE_READABLE|FMODE_WRITABLE) @@ -109,6 +108,7 @@ typedef struct rb_io_t { #define FMODE_APPEND 0x00000040 #define FMODE_CREATE 0x00000080 /* #define FMODE_NOREVLOOKUP 0x00000100 */ +#define FMODE_EXCL 0x00000400 #define FMODE_TRUNC 0x00000800 #define FMODE_TEXTMODE 0x00001000 /* #define FMODE_PREP 0x00010000 */ diff --git a/io.c b/io.c index fcccc71..8ae6bf4 100644 --- a/io.c +++ b/io.c @@ -4879,9 +4879,13 @@ rb_io_fmode_modestr(int fmode) case FMODE_READABLE: return MODE_BTMODE("r", "rb", "rt"); case FMODE_WRITABLE: + if (fmode & FMODE_EXCL) + return MODE_BTMODE("wx", "wbx", "wtx"); return MODE_BTMODE("w", "wb", "wt"); case FMODE_READWRITE: if (fmode & FMODE_CREATE) { + if (fmode & FMODE_EXCL) + return MODE_BTMODE("w+x", "wb+x", "wt+x"); return MODE_BTMODE("w+", "wb+", "wt+"); } return MODE_BTMODE("r+", "rb+", "rt+"); @@ -4928,6 +4932,11 @@ rb_io_modestr_fmode(const char *modestr) case '+': fmode |= FMODE_READWRITE; break; + case 'x': + if (!(fmode & FMODE_TRUNC)) + goto error; + fmode |= FMODE_EXCL; + break; default: goto error; case ':': @@ -4971,6 +4980,9 @@ rb_io_oflags_fmode(int oflags) if (oflags & O_CREAT) { fmode |= FMODE_CREATE; } + if (oflags & O_EXCL) { + fmode |= FMODE_EXCL; + } #ifdef O_BINARY if (oflags & O_BINARY) { fmode |= FMODE_BINMODE; @@ -5006,6 +5018,9 @@ rb_io_fmode_oflags(int fmode) if (fmode & FMODE_CREATE) { oflags |= O_CREAT; } + if (fmode & FMODE_EXCL) { + oflags |= O_EXCL; + } #ifdef O_BINARY if (fmode & FMODE_BINMODE) { oflags |= O_BINARY; @@ -5029,6 +5044,8 @@ rb_io_oflags_modestr(int oflags) #else # define MODE_BINARY(a,b) (a) #endif +#define MODE_BINARY_EXCL(a,b,c,d) \ + ((oflags & O_EXCL) ? MODE_BINARY(d, c) : MODE_BINARY(b, a)) int accmode = oflags & (O_RDONLY|O_WRONLY|O_RDWR); if (oflags & O_APPEND) { if (accmode == O_WRONLY) { @@ -5044,8 +5061,10 @@ rb_io_oflags_modestr(int oflags) case O_RDONLY: return MODE_BINARY("r", "rb"); case O_WRONLY: - return MODE_BINARY("w", "wb"); + return MODE_BINARY_EXCL("w", "wb", "wx", "wbx"); case O_RDWR: + if (oflags & O_CREAT) + return MODE_BINARY_EXCL("w+", "wb+", "w+x", "wb+x"); return MODE_BINARY("r+", "rb+"); } } diff --git a/test/ruby/test_file.rb b/test/ruby/test_file.rb index a700527..66a521f 100644 --- a/test/ruby/test_file.rb +++ b/test/ruby/test_file.rb @@ -437,4 +437,16 @@ class TestFile < Test::Unit::TestCase assert_file.not_exist?(path) end end + + def test_exclusive_mode + Dir.mktmpdir('feature11258') do |tmpdir| + fname = "#{tmpdir}/tmp.txt" + # file must be created and not just opened + assert_nothing_raised { File.new(fname, 'wx').close } + assert_raise(Errno::EEXIST) { File.new(fname, 'wx') } + # mode must not be read or append mode + assert_raise(ArgumentError) { File.new(fname, 'rx') } + assert_raise(ArgumentError) { File.new(fname, 'ax') } + end + end end