diff --git a/runtime/bin/file.cc b/runtime/bin/file.cc index 15088caab93c..59f8300a266f 100644 --- a/runtime/bin/file.cc +++ b/runtime/bin/file.cc @@ -59,13 +59,21 @@ bool File::WriteFully(const void* buffer, int64_t num_bytes) { File::FileOpenMode File::DartModeToFileMode(DartFileOpenMode mode) { ASSERT(mode == File::kDartRead || mode == File::kDartWrite || - mode == File::kDartAppend); + mode == File::kDartAppend || + mode == File::kDartWriteOnly || + mode == File::kDartWriteOnlyAppend); if (mode == File::kDartWrite) { return File::kWriteTruncate; } if (mode == File::kDartAppend) { return File::kWrite; } + if (mode == File::kDartWriteOnly) { + return File::kWriteOnlyTruncate; + } + if (mode == File::kDartWriteOnlyAppend) { + return File::kWriteOnly; + } return File::kRead; } diff --git a/runtime/bin/file.h b/runtime/bin/file.h index 7a8107fd91f4..633c006175fa 100644 --- a/runtime/bin/file.h +++ b/runtime/bin/file.h @@ -26,15 +26,20 @@ class File { kRead = 0, kWrite = 1, kTruncate = 1 << 2, - kWriteTruncate = kWrite | kTruncate + kWriteOnly = 1 << 3, + kWriteTruncate = kWrite | kTruncate, + kWriteOnlyTruncate = kWriteOnly | kTruncate }; // These values have to be kept in sync with the mode values of - // FileMode.READ, FileMode.WRITE and FileMode.APPEND in file.dart. + // FileMode.READ, FileMode.WRITE, FileMode.APPEND, + // FileMode.WRITE_ONLY and FileMode.WRITE_ONLY_APPEND in file.dart. enum DartFileOpenMode { kDartRead = 0, kDartWrite = 1, - kDartAppend = 2 + kDartAppend = 2, + kDartWriteOnly = 3, + kDartWriteOnlyAppend = 4 }; enum Type { diff --git a/runtime/bin/file_android.cc b/runtime/bin/file_android.cc index 015fc0ad53c0..35b6388b6e5c 100644 --- a/runtime/bin/file_android.cc +++ b/runtime/bin/file_android.cc @@ -158,8 +158,13 @@ File* File::Open(const char* name, FileOpenMode mode) { } int flags = O_RDONLY; if ((mode & kWrite) != 0) { + ASSERT((mode & kWriteOnly) == 0); flags = (O_RDWR | O_CREAT); } + if ((mode & kWriteOnly) != 0) { + ASSERT((mode & kWrite) == 0); + flags = (O_WRONLY | O_CREAT); + } if ((mode & kTruncate) != 0) { flags = flags | O_TRUNC; } @@ -168,7 +173,8 @@ File* File::Open(const char* name, FileOpenMode mode) { if (fd < 0) { return NULL; } - if (((mode & kWrite) != 0) && ((mode & kTruncate) == 0)) { + if ((((mode & kWrite) != 0) && ((mode & kTruncate) == 0)) || + (((mode & kWriteOnly) != 0) && ((mode & kTruncate) == 0))) { int64_t position = lseek64(fd, 0, SEEK_END); if (position < 0) { return NULL; diff --git a/runtime/bin/file_linux.cc b/runtime/bin/file_linux.cc index 0a437f14874f..ff801adf8fe7 100644 --- a/runtime/bin/file_linux.cc +++ b/runtime/bin/file_linux.cc @@ -157,8 +157,13 @@ File* File::Open(const char* name, FileOpenMode mode) { } int flags = O_RDONLY; if ((mode & kWrite) != 0) { + ASSERT((mode & kWriteOnly) == 0); flags = (O_RDWR | O_CREAT); } + if ((mode & kWriteOnly) != 0) { + ASSERT((mode & kWrite) == 0); + flags = (O_WRONLY | O_CREAT); + } if ((mode & kTruncate) != 0) { flags = flags | O_TRUNC; } @@ -167,7 +172,8 @@ File* File::Open(const char* name, FileOpenMode mode) { if (fd < 0) { return NULL; } - if (((mode & kWrite) != 0) && ((mode & kTruncate) == 0)) { + if ((((mode & kWrite) != 0) && ((mode & kTruncate) == 0)) || + (((mode & kWriteOnly) != 0) && ((mode & kTruncate) == 0))) { int64_t position = NO_RETRY_EXPECTED(lseek64(fd, 0, SEEK_END)); if (position < 0) { return NULL; diff --git a/runtime/bin/file_macos.cc b/runtime/bin/file_macos.cc index 9fc33c5b4936..ddc19cc02979 100644 --- a/runtime/bin/file_macos.cc +++ b/runtime/bin/file_macos.cc @@ -159,8 +159,13 @@ File* File::Open(const char* name, FileOpenMode mode) { } int flags = O_RDONLY; if ((mode & kWrite) != 0) { + ASSERT((mode & kWriteOnly) == 0); flags = (O_RDWR | O_CREAT); } + if ((mode & kWriteOnly) != 0) { + ASSERT((mode & kWrite) == 0); + flags = (O_WRONLY | O_CREAT); + } if ((mode & kTruncate) != 0) { flags = flags | O_TRUNC; } @@ -169,7 +174,8 @@ File* File::Open(const char* name, FileOpenMode mode) { return NULL; } FDUtils::SetCloseOnExec(fd); - if (((mode & kWrite) != 0) && ((mode & kTruncate) == 0)) { + if ((((mode & kWrite) != 0) && ((mode & kTruncate) == 0)) || + (((mode & kWriteOnly) != 0) && ((mode & kTruncate) == 0))) { int64_t position = lseek(fd, 0, SEEK_END); if (position < 0) { return NULL; diff --git a/runtime/bin/file_win.cc b/runtime/bin/file_win.cc index 67b97f51af16..0fde43360385 100644 --- a/runtime/bin/file_win.cc +++ b/runtime/bin/file_win.cc @@ -157,8 +157,13 @@ int64_t File::Length() { File* File::Open(const char* name, FileOpenMode mode) { int flags = O_RDONLY | O_BINARY | O_NOINHERIT; if ((mode & kWrite) != 0) { + ASSERT((mode & kWriteOnly) == 0); flags = (O_RDWR | O_CREAT | O_BINARY | O_NOINHERIT); } + if ((mode & kWriteOnly) != 0) { + ASSERT((mode & kWrite) == 0); + flags = (O_WRONLY | O_CREAT | O_BINARY | O_NOINHERIT); + } if ((mode & kTruncate) != 0) { flags = flags | O_TRUNC; } @@ -168,7 +173,8 @@ File* File::Open(const char* name, FileOpenMode mode) { if (fd < 0) { return NULL; } - if (((mode & kWrite) != 0) && ((mode & kTruncate) == 0)) { + if ((((mode & kWrite) != 0) && ((mode & kTruncate) == 0)) || + (((mode & kWriteOnly) != 0) && ((mode & kTruncate) == 0))) { int64_t position = _lseeki64(fd, 0, SEEK_END); if (position < 0) { return NULL; diff --git a/sdk/lib/io/file.dart b/sdk/lib/io/file.dart index 879915825fa5..691d7837445f 100644 --- a/sdk/lib/io/file.dart +++ b/sdk/lib/io/file.dart @@ -10,13 +10,20 @@ part of dart.io; class FileMode { /// The mode for opening a file only for reading. static const READ = const FileMode._internal(0); - /// The mode for opening a file for reading and writing. The file is + /// Mode for opening a file for reading and writing. The file is /// overwritten if it already exists. The file is created if it does not /// already exist. static const WRITE = const FileMode._internal(1); - /// The mode for opening a file for reading and writing to the + /// Mode for opening a file for reading and writing to the /// end of it. The file is created if it does not already exist. static const APPEND = const FileMode._internal(2); + /// Mode for opening a file for writing *only*. The file is + /// overwritten if it already exists. The file is created if it does not + /// already exist. + static const WRITE_ONLY = const FileMode._internal(3); + /// Mode for opening a file for writing *only* to the + /// end of it. The file is created if it does not already exist. + static const WRITE_ONLY_APPEND = const FileMode._internal(4); final int _mode; const FileMode._internal(this._mode); @@ -31,6 +38,13 @@ const WRITE = FileMode.WRITE; /// The mode for opening a file for reading and writing to the /// end of it. The file is created if it does not already exist. const APPEND = FileMode.APPEND; +/// Mode for opening a file for writing *only*. The file is +/// overwritten if it already exists. The file is created if it does not +/// already exist. +const WRITE_ONLY = FileMode.WRITE_ONLY; +/// Mode for opening a file for writing *only* to the +/// end of it. The file is created if it does not already exist. +const WRITE_ONLY_APPEND = FileMode.WRITE_ONLY_APPEND; /// Type of lock when requesting a lock on a file. diff --git a/sdk/lib/io/file_impl.dart b/sdk/lib/io/file_impl.dart index a98d94826716..8ed6c58b89b8 100644 --- a/sdk/lib/io/file_impl.dart +++ b/sdk/lib/io/file_impl.dart @@ -344,8 +344,11 @@ class _File extends FileSystemEntity implements File { Future open({FileMode mode: FileMode.READ}) { if (mode != FileMode.READ && mode != FileMode.WRITE && - mode != FileMode.APPEND) { - return new Future.error(new ArgumentError()); + mode != FileMode.APPEND && + mode != FileMode.WRITE_ONLY && + mode != FileMode.WRITE_ONLY_APPEND) { + return new Future.error( + new ArgumentError('Invalid file mode for this operation')); } return _IOService._dispatch(_FILE_OPEN, [path, mode._mode]) .then((response) { @@ -401,10 +404,10 @@ class _File extends FileSystemEntity implements File { RandomAccessFile openSync({FileMode mode: FileMode.READ}) { if (mode != FileMode.READ && mode != FileMode.WRITE && - mode != FileMode.APPEND) { - throw new FileSystemException("Unknown file mode. Use FileMode.READ, " - "FileMode.WRITE or FileMode.APPEND.", - path); + mode != FileMode.APPEND && + mode != FileMode.WRITE_ONLY && + mode != FileMode.WRITE_ONLY_APPEND) { + throw new ArgumentError('Invalid file mode for this operation'); } var id = _open(path, mode._mode); throwIfError(id, "Cannot open file", path); @@ -428,9 +431,10 @@ class _File extends FileSystemEntity implements File { IOSink openWrite({FileMode mode: FileMode.WRITE, Encoding encoding: UTF8}) { if (mode != FileMode.WRITE && - mode != FileMode.APPEND) { - throw new ArgumentError( - "Wrong FileMode. Use FileMode.WRITE or FileMode.APPEND"); + mode != FileMode.APPEND && + mode != FileMode.WRITE_ONLY && + mode != FileMode.WRITE_ONLY_APPEND) { + throw new ArgumentError('Invalid file mode for this operation'); } var consumer = new _FileStreamConsumer(this, mode); return new IOSink(consumer, encoding: encoding); diff --git a/tests/standalone/io/file_write_only_test.dart b/tests/standalone/io/file_write_only_test.dart new file mode 100644 index 000000000000..c06be4741ef8 --- /dev/null +++ b/tests/standalone/io/file_write_only_test.dart @@ -0,0 +1,84 @@ +// Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. +// +// Dart test program for testing file I/O. + +import 'dart:async'; +import 'dart:io'; + +import "package:async_helper/async_helper.dart"; +import "package:expect/expect.dart"; + +Future withTempDir(String prefix, void test(Directory dir)) async { + var tempDir = Directory.systemTemp.createTempSync(prefix); + try { + await test(tempDir); + } finally { + tempDir.deleteSync(recursive: true); + } +} + +void withTempDirSync(String prefix, void test(Directory dir)) async { + var tempDir = Directory.systemTemp.createTempSync(prefix); + try { + test(tempDir); + } finally { + tempDir.deleteSync(recursive: true); + } +} + +Futire expectThrowsAsync(Future future, String message) { + return future.then((r) => Expect.fail(message)) + .catchError((e) {}); +} + + +Future write(Directory dir) async { + var f = new File('x'); + var raf = await f.open(mode: WRITE_ONLY); + await raf.writeString('Hello'); + await raf.setPosition(0); + await raf.writeString('Hello'); + await raf.setPosition(0); + await expectThrowsAsync( + raf.readByte(), 'Read from write only file succeeded'); + await raf.close(); + raf = await f.open(mode: WRITE_ONLY_APPEND); + await raf.writeString('Hello'); + await expectThrowsAsync( + raf.readByte(), 'Read from write only file succeeded'); + await raf.setPosition(0); + await raf.writeString('Hello'); + await raf.close(); + Expect.equals(f.lengthSync(), 10); +} + +Future writeSync(Directory dir) { + var f = new File('x'); + var raf = f.openSync(mode: WRITE_ONLY); + raf.writeStringSync('Hello'); + raf.setPositionSync(0); + raf.writeStringSync('Hello'); + raf.setPositionSync(0); + Expect.throws(() => raf.readByteSync()); +} + +Future openWrite(Directory dir) async { + var f = new File('x'); + var sink = f.openWrite(mode: WRITE_ONLY); + sink.write('Hello'); + await sink.close(); + sink = await f.openWrite(mode: WRITE_ONLY_APPEND); + sink.write('Hello'); + await sink.close(); + Expect.equals(f.lengthSync(), 10); +} + +main() async { + asyncStart(); + await withTempDir('file_write_only_test', write); + withTempDirSync('file_write_only_test', writeSync); + await withTempDir('file_write_only_test', openWrite); + asyncEnd(); +}