diff --git a/runtime/bin/directory_android.cc b/runtime/bin/directory_android.cc index 9a2d1662b91..1fb42cb1e70 100644 --- a/runtime/bin/directory_android.cc +++ b/runtime/bin/directory_android.cc @@ -169,6 +169,9 @@ static bool ListRecursively(PathBuffer* path, int stat_success; if (follow_links) { stat_success = TEMP_FAILURE_RETRY(stat(path->data, &entry_info)); + if (stat_success == -1) { + stat_success = TEMP_FAILURE_RETRY(lstat(path->data, &entry_info)); + } } else { stat_success = TEMP_FAILURE_RETRY(lstat(path->data, &entry_info)); } @@ -189,7 +192,6 @@ static bool ListRecursively(PathBuffer* path, path, listing) && success; } else if (S_ISLNK(entry_info.st_mode)) { - ASSERT(!follow_links); success = HandleLink(entry.d_name, path, listing) && success; @@ -232,7 +234,6 @@ static bool DeleteDir(char* dir_name, static bool DeleteRecursively(PathBuffer* path) { - if (!path->Add(File::PathSeparator())) return false; // Do not recurse into links for deletion. Instead delete the link. struct stat st; if (TEMP_FAILURE_RETRY(lstat(path->data, &st)) == -1) { @@ -241,6 +242,8 @@ static bool DeleteRecursively(PathBuffer* path) { return (remove(path->data) == 0); } + if (!path->Add(File::PathSeparator())) return false; + // Not a link. Attempt to open as a directory and recurse into the // directory. DIR* dir_pointer; diff --git a/runtime/bin/directory_linux.cc b/runtime/bin/directory_linux.cc index f6ad163905b..a316633d8e5 100644 --- a/runtime/bin/directory_linux.cc +++ b/runtime/bin/directory_linux.cc @@ -169,6 +169,9 @@ static bool ListRecursively(PathBuffer* path, int stat_success; if (follow_links) { stat_success = TEMP_FAILURE_RETRY(stat(path->data, &entry_info)); + if (stat_success == -1) { + stat_success = TEMP_FAILURE_RETRY(lstat(path->data, &entry_info)); + } } else { stat_success = TEMP_FAILURE_RETRY(lstat(path->data, &entry_info)); } @@ -189,7 +192,6 @@ static bool ListRecursively(PathBuffer* path, path, listing) && success; } else if (S_ISLNK(entry_info.st_mode)) { - ASSERT(!follow_links); success = HandleLink(entry.d_name, path, listing) && success; @@ -232,7 +234,6 @@ static bool DeleteDir(char* dir_name, static bool DeleteRecursively(PathBuffer* path) { - if (!path->Add(File::PathSeparator())) return false; // Do not recurse into links for deletion. Instead delete the link. struct stat st; if (TEMP_FAILURE_RETRY(lstat(path->data, &st)) == -1) { @@ -241,6 +242,8 @@ static bool DeleteRecursively(PathBuffer* path) { return (remove(path->data) == 0); } + if (!path->Add(File::PathSeparator())) return false; + // Not a link. Attempt to open as a directory and recurse into the // directory. DIR* dir_pointer; diff --git a/runtime/bin/directory_macos.cc b/runtime/bin/directory_macos.cc index d66a809ac1c..401f80cf94d 100644 --- a/runtime/bin/directory_macos.cc +++ b/runtime/bin/directory_macos.cc @@ -169,6 +169,9 @@ static bool ListRecursively(PathBuffer* path, int stat_success; if (follow_links) { stat_success = TEMP_FAILURE_RETRY(stat(path->data, &entry_info)); + if (stat_success == -1) { + stat_success = TEMP_FAILURE_RETRY(lstat(path->data, &entry_info)); + } } else { stat_success = TEMP_FAILURE_RETRY(lstat(path->data, &entry_info)); } @@ -189,7 +192,6 @@ static bool ListRecursively(PathBuffer* path, path, listing) && success; } else if (S_ISLNK(entry_info.st_mode)) { - ASSERT(!follow_links); success = HandleLink(entry.d_name, path, listing) && success; @@ -232,8 +234,7 @@ static bool DeleteDir(char* dir_name, static bool DeleteRecursively(PathBuffer* path) { - if (!path->Add(File::PathSeparator())) return false; - // Do not recurse into links for deletion. Instead delete the link. + // Do not recurse into links for deletion. Instead delete the link. struct stat st; if (TEMP_FAILURE_RETRY(lstat(path->data, &st)) == -1) { return false; @@ -241,6 +242,8 @@ static bool DeleteRecursively(PathBuffer* path) { return (remove(path->data) == 0); } + if (!path->Add(File::PathSeparator())) return false; + // Not a link. Attempt to open as a directory and recurse into the // directory. DIR* dir_pointer; diff --git a/runtime/bin/directory_win.cc b/runtime/bin/directory_win.cc index 857739ac872..d4b0aafa6d2 100644 --- a/runtime/bin/directory_win.cc +++ b/runtime/bin/directory_win.cc @@ -118,9 +118,25 @@ static bool HandleEntry(LPWIN32_FIND_DATAW find_file_data, bool follow_links, DirectoryListing* listing) { DWORD attributes = find_file_data->dwFileAttributes; - if (!follow_links && (attributes & FILE_ATTRIBUTE_REPARSE_POINT) != 0) { - return HandleLink(find_file_data->cFileName, path, listing); - } else if ((attributes & FILE_ATTRIBUTE_DIRECTORY) != 0) { + if ((attributes & FILE_ATTRIBUTE_REPARSE_POINT) != 0) { + if (!follow_links) { + return HandleLink(find_file_data->cFileName, path, listing); + } + // Attempt to list the directory, to see if it's a valid link or not. + int path_length = path->length; + if (!path->Add(find_file_data->cFileName)) return false; + if (!path->Add(L"\\*")) return false; + WIN32_FIND_DATAW tmp_file_data; + HANDLE find_handle = FindFirstFileW(path->data, &tmp_file_data); + path->Reset(path_length); + if (find_handle == INVALID_HANDLE_VALUE) { + // Invalid handle, report as (broken) link. + return HandleLink(find_file_data->cFileName, path, listing); + } else { + FindClose(find_handle); + } + } + if ((attributes & FILE_ATTRIBUTE_DIRECTORY) != 0) { return HandleDir(find_file_data->cFileName, path, recursive, diff --git a/runtime/bin/file_win.cc b/runtime/bin/file_win.cc index 8e618d310a6..3780fca056c 100644 --- a/runtime/bin/file_win.cc +++ b/runtime/bin/file_win.cc @@ -259,12 +259,19 @@ bool File::CreateLink(const char* utf8_name, const char* utf8_target) { bool File::Delete(const char* name) { const wchar_t* system_name = StringUtils::Utf8ToWide(name); - int status = _wremove(system_name); - free(const_cast(system_name)); - if (status == -1) { - return false; + DWORD attributes = GetFileAttributesW(system_name); + if ((attributes != INVALID_FILE_ATTRIBUTES) && + (attributes & FILE_ATTRIBUTE_REPARSE_POINT) != 0) { + // It's a junction(link), delete it. + return RemoveDirectoryW(system_name) != 0; + } else { + int status = _wremove(system_name); + free(const_cast(system_name)); + if (status == -1) { + return false; + } + return true; } - return true; } diff --git a/tests/standalone/io/directory_test.dart b/tests/standalone/io/directory_test.dart index d6eac48a03d..db5903d2390 100644 --- a/tests/standalone/io/directory_test.dart +++ b/tests/standalone/io/directory_test.dart @@ -244,6 +244,98 @@ class DirectoryTest { Expect.isFalse(d.existsSync()); } + static void testDeleteLinkSync() { + Directory tmp = new Directory("").createTempSync(); + var path = "${tmp.path}${Platform.pathSeparator}"; + Directory d = new Directory("${path}target"); + d.createSync(); + Link l = new Link("${path}symlink"); + l.createSync("${path}target"); + Expect.isTrue(d.existsSync()); + Expect.isTrue(l.existsSync()); + new Directory(l.path).deleteSync(recursive: true); + Expect.isTrue(d.existsSync()); + Expect.isFalse(l.existsSync()); + d.deleteSync(); + Expect.isFalse(d.existsSync()); + tmp.deleteSync(); + } + + static void testDeleteLinkAsFileSync() { + Directory tmp = new Directory("").createTempSync(); + var path = "${tmp.path}${Platform.pathSeparator}"; + Directory d = new Directory("${path}target"); + d.createSync(); + Link l = new Link("${path}symlink"); + l.createSync("${path}target"); + Expect.isTrue(d.existsSync()); + Expect.isTrue(l.existsSync()); + new Link(l.path).deleteSync(); + Expect.isTrue(d.existsSync()); + Expect.isFalse(l.existsSync()); + d.deleteSync(); + Expect.isFalse(d.existsSync()); + tmp.deleteSync(); + } + + static void testDeleteBrokenLinkAsFileSync() { + Directory tmp = new Directory("").createTempSync(); + var path = "${tmp.path}${Platform.pathSeparator}"; + Directory d = new Directory("${path}target"); + d.createSync(); + Link l = new Link("${path}symlink"); + l.createSync("${path}target"); + d.deleteSync(); + Expect.isFalse(d.existsSync()); + Expect.isTrue(l.existsSync()); + new Link(l.path).deleteSync(); + Expect.isFalse(l.existsSync()); + Expect.isFalse(d.existsSync()); + tmp.deleteSync(); + } + + static void testListBrokenLinkSync() { + Directory tmp = new Directory("").createTempSync(); + var path = "${tmp.path}${Platform.pathSeparator}"; + Directory d = new Directory("${path}target"); + d.createSync(); + Link l = new Link("${path}symlink"); + l.createSync("${path}target"); + d.deleteSync(); + int count = 0; + tmp.list(followLinks: true).listen( + (file) { + count++; + Expect.isTrue(file is Link); + }, + onDone: () { + Expect.equals(1, count); + l.deleteSync(); + tmp.deleteSync(); + }); + } + + static void testListLinkSync() { + Directory tmp = new Directory("").createTempSync(); + var path = "${tmp.path}${Platform.pathSeparator}"; + Directory d = new Directory("${path}target"); + d.createSync(); + Link l = new Link("${path}symlink"); + l.createSync("${path}target"); + int count = 0; + tmp.list(followLinks: true).listen( + (file) { + count++; + Expect.isTrue(file is Directory); + }, + onDone: () { + Expect.equals(2, count); + l.deleteSync(); + d.deleteSync(); + tmp.deleteSync(); + }); + } + static void testCreateTemp([String template = ""]) { var port = new ReceivePort(); Directory dir = new Directory(template); @@ -314,6 +406,11 @@ class DirectoryTest { testDeleteTooLongNameSync(); testExistsCreateDelete(); testExistsCreateDeleteSync(); + testDeleteLinkSync(); + testDeleteLinkAsFileSync(); + testDeleteBrokenLinkAsFileSync(); + testListBrokenLinkSync(); + testListLinkSync(); testCreateTemp(); testCreateDeleteTemp(); testCurrent(); diff --git a/tests/standalone/io/file_system_links_test.dart b/tests/standalone/io/file_system_links_test.dart index a1bd0af5480..7c3cdc7c320 100644 --- a/tests/standalone/io/file_system_links_test.dart +++ b/tests/standalone/io/file_system_links_test.dart @@ -206,15 +206,17 @@ testDirectoryListingBrokenLink() { var doesNotExist = 'this_thing_does_not_exist'; new File(x).createSync(); createLink(doesNotExist, link, () { - Expect.throws(() => temp.listSync(recursive: true), - (e) => e is DirectoryIOException); + temp.listSync(recursive: true); // No exceptions. var files = []; var dirs = []; + var links = []; var errors = []; temp.list(recursive: true).listen( (entity) { if (entity is File) { files.add(entity.path); + } else if (entity is Link) { + links.add(entity.path); } else { Expect.isTrue(entity is Directory); dirs.add(entity.path); @@ -224,9 +226,10 @@ testDirectoryListingBrokenLink() { onDone: () { Expect.equals(1, files.length); Expect.isTrue(files[0].endsWith(x)); + Expect.equals(1, links.length); + Expect.isTrue(links[0].endsWith(link)); Expect.equals(0, dirs.length); - Expect.equals(1, errors.length); - Expect.isTrue(errors[0].toString().contains(link)); + Expect.equals(0, errors.length); temp.deleteSync(recursive: true); keepAlive.close(); });