Add PathExistsException and PathAccessException FileSystemException subclasses.

Bug:https://github.com/dart-lang/sdk/issues/50436
Change-Id: Ie2954f162c01189cd0d817f58606529acdc13416
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/269240
Reviewed-by: Alexander Aprelev <aam@google.com>
Commit-Queue: Brian Quinlan <bquinlan@google.com>
This commit is contained in:
Brian Quinlan 2022-11-30 04:54:52 +00:00 committed by Commit Queue
parent cef6e432eb
commit 8df8bbf0de
6 changed files with 115 additions and 11 deletions

View file

@ -124,11 +124,15 @@
[#49878]: https://github.com/dart-lang/sdk/issues/49878
- When a `dart:io` operation fails because a file is not found, throw
`PathNotFoundException`, a `FileSystemException` subclass, to make it
easier to handle "file not found" errors.
- Adds three new `FileSystemException` subclasses to handle common error
cases:
- `PathAccessException`: The necessary access rights are not available.
- `PathExistsException`: The path being created already exists.
- `PathNotFoundException`: The path being accessed does not exist.
[#12461]: https://github.com/dart-lang/sdk/issues/12461
[#50436]: https://github.com/dart-lang/sdk/issues/50436
#### `dart:isolate`

View file

@ -173,7 +173,7 @@ abstract class _FileSystemWatcher {
_newWatcher();
} on dynamic catch (e) {
_broadcastController.addError(FileSystemException._fromOSError(
e, "Failed to initialize file system entity watcher", null));
e, "Failed to initialize file system entity watcher", _path));
_broadcastController.close();
return;
}

View file

@ -17,17 +17,30 @@ const int _osErrorResponseMessage = 2;
// POSIX error codes.
// See https://pubs.opengroup.org/onlinepubs/9699919799/basedefs/errno.h.html
const _ePerm = 1;
const _eNoEnt = 2;
const _eAccess = 13;
const _eExist = 17;
// Windows error codes.
// See https://learn.microsoft.com/en-us/windows/win32/debug/system-error-codes--0-499-
const _errorFileNotFound = 2;
const _errorPathNotFound = 3;
const _errorAccessDenied = 5;
const _errorInvalidDrive = 15;
const _errorCurrentDirectory = 16;
const _errorNoMoreFiles = 18;
const _errorWriteProtect = 19;
const _errorBadLength = 24;
const _errorSharingViolation = 32;
const _errorLockViolation = 33;
const _errorBadNetpath = 53;
const _errorNetworkAccessDenied = 65;
const _errorBadNetName = 67;
const _errorFileExists = 80;
const _errorDriveLocked = 108;
const _errorBadPathName = 161;
const _errorAlreadyExists = 183;
const _errorFilenameExedRange = 206;
/// If the [response] is an error, throws an [Exception] or an [Error].

View file

@ -222,7 +222,7 @@ abstract class File implements FileSystemEntity {
/// non-existing parent paths are created first.
///
/// If [exclusive] is `true` and to-be-created file already exists, this
/// operation completes the future with a [FileSystemException].
/// operation completes the future with a [PathExistsException].
///
/// If [exclusive] is `false`, existing files are left untouched by [create].
/// Calling [create] on an existing file still might fail if there are
@ -900,9 +900,21 @@ class FileSystemException implements IOException {
/// will be returned.
@pragma("vm:entry-point")
factory FileSystemException._fromOSError(
OSError err, String message, String? path) {
OSError err, String message, String path) {
if (Platform.isWindows) {
switch (err.errorCode) {
case _errorAccessDenied:
case _errorCurrentDirectory:
case _errorWriteProtect:
case _errorBadLength:
case _errorSharingViolation:
case _errorLockViolation:
case _errorNetworkAccessDenied:
case _errorDriveLocked:
return PathAccessException(path, err, message);
case _errorFileExists:
case _errorAlreadyExists:
return PathExistsException(path, err, message);
case _errorFileNotFound:
case _errorPathNotFound:
case _errorInvalidDrive:
@ -911,14 +923,19 @@ class FileSystemException implements IOException {
case _errorBadNetName:
case _errorBadPathName:
case _errorFilenameExedRange:
return PathNotFoundException(path!, err, message);
return PathNotFoundException(path, err, message);
default:
return FileSystemException(message, path, err);
}
} else {
switch (err.errorCode) {
case _ePerm:
case _eAccess:
return PathAccessException(path, err, message);
case _eExist:
return PathExistsException(path, err, message);
case _eNoEnt:
return PathNotFoundException(path!, err, message);
return PathNotFoundException(path, err, message);
default:
return FileSystemException(message, path, err);
}
@ -952,6 +969,24 @@ class FileSystemException implements IOException {
}
}
/// Exception thrown when a file operation fails because the necessary access
/// rights are not available.
class PathAccessException extends FileSystemException {
const PathAccessException(String path, OSError osError, [String message = ""])
: super(message, path, osError);
String toString() => _toStringHelper("PathAccessException");
}
/// Exception thrown when a file operation fails because the target path
/// already exists.
class PathExistsException extends FileSystemException {
const PathExistsException(String path, OSError osError, [String message = ""])
: super(message, path, osError);
String toString() => _toStringHelper("PathExistsException");
}
/// Exception thrown when a file operation fails because a file or
/// directory does not exist.
class PathNotFoundException extends FileSystemException {
@ -959,9 +994,7 @@ class PathNotFoundException extends FileSystemException {
[String message = ""])
: super(message, path, osError);
String toString() {
return _toStringHelper("PathNotFoundException");
}
String toString() => _toStringHelper("PathNotFoundException");
}
/// The "read" end of an [Pipe] created by [Pipe.create].

View file

@ -436,6 +436,31 @@ testReadSyncClosedFile() {
});
}
void testCreateExistingFile() {
createTestFile((file, done) {
Expect.throws<PathExistsException>(() => file.createSync(exclusive: true));
done();
});
}
void testDeleteDirectoryWithoutPermissions() {
if (Platform.isMacOS) {
createTestFile((file, done) async {
Process.runSync('chflags', ['uchg', file.path]);
Expect.throws<PathAccessException>(file.deleteSync);
Process.runSync('chflags', ['nouchg', file.path]);
done();
});
}
if (Platform.isWindows) {
final oldCurrent = Directory.current;
Directory.current = tempDir();
// Cannot delete the current working directory in Windows.
Expect.throws<PathAccessException>(Directory.current.deleteSync);
Directory.current = oldCurrent;
}
}
main() {
testOpenBlankFilename();
testOpenNonExistent();
@ -453,4 +478,6 @@ main() {
testRepeatedlyCloseFile();
testRepeatedlyCloseFileSync();
testReadSyncClosedFile();
testCreateExistingFile();
testDeleteDirectoryWithoutPermissions();
}

View file

@ -438,6 +438,31 @@ testReadSyncClosedFile() {
});
}
void testCreateExistingFile() {
createTestFile((file, done) {
Expect.throws<PathExistsException>(() => file.createSync(exclusive: true));
done();
});
}
void testDeleteDirectoryWithoutPermissions() {
if (Platform.isMacOS) {
createTestFile((file, done) async {
Process.runSync('chflags', ['uchg', file.path]);
Expect.throws<PathAccessException>(file.deleteSync);
Process.runSync('chflags', ['nouchg', file.path]);
done();
});
}
if (Platform.isWindows) {
final oldCurrent = Directory.current;
Directory.current = tempDir();
// Cannot delete the current working directory in Windows.
Expect.throws<PathAccessException>(Directory.current.deleteSync);
Directory.current = oldCurrent;
}
}
main() {
testOpenBlankFilename();
testOpenNonExistent();
@ -455,4 +480,6 @@ main() {
testRepeatedlyCloseFile();
testRepeatedlyCloseFileSync();
testReadSyncClosedFile();
testCreateExistingFile();
testDeleteDirectoryWithoutPermissions();
}