[VM/io] - Reland : Set correct file type for files backing unix domain sockets

The file type of file backing unix domain sockets was being incorrectly
set as kDoesNotExist resulting in errors when operations like delete
on the file was done. File::Exists on the other hand returned true.
File rename and copyfile functionality have been fixed too.

TEST=new tests added

Please see https://github.com/dart-lang/sdk/issues/48569 for the original issue.

Change-Id: I28505236fbad2ea86ad65065e753b13fb7ff655a
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/240300
Reviewed-by: Ryan Macnak <rmacnak@google.com>
Commit-Queue: Siva Annamalai <asiva@google.com>
diff --git a/runtime/bin/file.h b/runtime/bin/file.h
index f2d45e9..802cf05 100644
--- a/runtime/bin/file.h
+++ b/runtime/bin/file.h
@@ -70,7 +70,14 @@
     kDartWriteOnlyAppend = 4
   };
 
-  enum Type { kIsFile = 0, kIsDirectory = 1, kIsLink = 2, kDoesNotExist = 3 };
+  enum Type {
+    kIsFile = 0,
+    kIsDirectory = 1,
+    kIsLink = 2,
+    kIsSock = 3,  // Unix Domain Socket.
+    kIsPipe = 4,  // FIFO/Pipe.
+    kDoesNotExist = 5
+  };
 
   enum Identical { kIdentical = 0, kDifferent = 1, kError = 2 };
 
diff --git a/runtime/bin/file_android.cc b/runtime/bin/file_android.cc
index d766192..a93c1fb 100644
--- a/runtime/bin/file_android.cc
+++ b/runtime/bin/file_android.cc
@@ -356,18 +356,17 @@
   if (S_ISLNK(entry_info.st_mode)) {
     return File::kIsLink;
   }
+  if (S_ISSOCK(entry_info.st_mode)) {
+    return File::kIsSock;
+  }
+  if (S_ISFIFO(entry_info.st_mode)) {
+    return File::kIsPipe;
+  }
   return File::kDoesNotExist;
 }
 
-static bool CheckTypeAndSetErrno(Namespace* namespc,
-                                 const char* name,
-                                 File::Type expected,
-                                 bool follow_links) {
-  File::Type actual = File::GetType(namespc, name, follow_links);
-  if (actual == expected) {
-    return true;
-  }
-  switch (actual) {
+static void SetErrno(File::Type type) {
+  switch (type) {
     case File::kIsDirectory:
       errno = EISDIR;
       break;
@@ -378,13 +377,28 @@
       errno = EINVAL;
       break;
   }
+}
+
+static bool CheckTypeAndSetErrno(Namespace* namespc,
+                                 const char* name,
+                                 File::Type expected,
+                                 bool follow_links) {
+  File::Type actual = File::GetType(namespc, name, follow_links);
+  if (actual == expected) {
+    return true;
+  }
+  SetErrno(actual);
   return false;
 }
 
 bool File::Delete(Namespace* namespc, const char* name) {
   NamespaceScope ns(namespc, name);
-  return CheckTypeAndSetErrno(namespc, name, kIsFile, true) &&
-         (NO_RETRY_EXPECTED(unlinkat(ns.fd(), ns.path(), 0)) == 0);
+  File::Type type = File::GetType(namespc, name, true);
+  if (type == kIsFile || type == kIsSock || type == kIsPipe) {
+    return (NO_RETRY_EXPECTED(unlinkat(ns.fd(), ns.path(), 0)) == 0);
+  }
+  SetErrno(type);
+  return false;
 }
 
 bool File::DeleteLink(Namespace* namespc, const char* name) {
@@ -396,11 +410,15 @@
 bool File::Rename(Namespace* namespc,
                   const char* old_path,
                   const char* new_path) {
-  NamespaceScope oldns(namespc, old_path);
-  NamespaceScope newns(namespc, new_path);
-  return CheckTypeAndSetErrno(namespc, old_path, kIsFile, true) &&
-         (NO_RETRY_EXPECTED(renameat(oldns.fd(), oldns.path(), newns.fd(),
-                                     newns.path())) == 0);
+  File::Type type = File::GetType(namespc, old_path, true);
+  if (type == kIsFile || type == kIsSock || type == kIsPipe) {
+    NamespaceScope oldns(namespc, old_path);
+    NamespaceScope newns(namespc, new_path);
+    return (NO_RETRY_EXPECTED(renameat(oldns.fd(), oldns.path(), newns.fd(),
+                                       newns.path())) == 0);
+  }
+  SetErrno(type);
+  return false;
 }
 
 bool File::RenameLink(Namespace* namespc,
@@ -416,7 +434,9 @@
 bool File::Copy(Namespace* namespc,
                 const char* old_path,
                 const char* new_path) {
-  if (!CheckTypeAndSetErrno(namespc, old_path, kIsFile, true)) {
+  File::Type type = File::GetType(namespc, old_path, true);
+  if (type != kIsFile && type != kIsSock && type != kIsPipe) {
+    SetErrno(type);
     return false;
   }
   NamespaceScope oldns(namespc, old_path);
@@ -508,6 +528,10 @@
       data[kType] = kIsDirectory;
     } else if (S_ISLNK(st.st_mode)) {
       data[kType] = kIsLink;
+    } else if (S_ISSOCK(st.st_mode)) {
+      data[kType] = kIsSock;
+    } else if (S_ISFIFO(st.st_mode)) {
+      data[kType] = kIsPipe;
     } else {
       data[kType] = kDoesNotExist;
     }
diff --git a/runtime/bin/file_fuchsia.cc b/runtime/bin/file_fuchsia.cc
index aa7f882..57765f0 100644
--- a/runtime/bin/file_fuchsia.cc
+++ b/runtime/bin/file_fuchsia.cc
@@ -354,18 +354,17 @@
   if (S_ISLNK(entry_info.st_mode)) {
     return File::kIsLink;
   }
+  if (S_ISSOCK(entry_info.st_mode)) {
+    return File::kIsSock;
+  }
+  if (S_ISFIFO(entry_info.st_mode)) {
+    return File::kIsPipe;
+  }
   return File::kDoesNotExist;
 }
 
-static bool CheckTypeAndSetErrno(Namespace* namespc,
-                                 const char* name,
-                                 File::Type expected,
-                                 bool follow_links) {
-  File::Type actual = File::GetType(namespc, name, follow_links);
-  if (actual == expected) {
-    return true;
-  }
-  switch (actual) {
+static void SetErrno(File::Type type) {
+  switch (type) {
     case File::kIsDirectory:
       errno = EISDIR;
       break;
@@ -376,13 +375,28 @@
       errno = EINVAL;
       break;
   }
+}
+
+static bool CheckTypeAndSetErrno(Namespace* namespc,
+                                 const char* name,
+                                 File::Type expected,
+                                 bool follow_links) {
+  File::Type actual = File::GetType(namespc, name, follow_links);
+  if (actual == expected) {
+    return true;
+  }
+  SetErrno(actual);
   return false;
 }
 
 bool File::Delete(Namespace* namespc, const char* name) {
-  NamespaceScope ns(namespc, name);
-  return CheckTypeAndSetErrno(namespc, name, kIsFile, true) &&
-         (NO_RETRY_EXPECTED(unlinkat(ns.fd(), ns.path(), 0)) == 0);
+  File::Type type = File::GetType(namespc, name, true);
+  if (type == kIsFile || type == kIsSock || type == kIsPipe) {
+    NamespaceScope ns(namespc, name);
+    return (NO_RETRY_EXPECTED(unlinkat(ns.fd(), ns.path(), 0)) == 0);
+  }
+  SetErrno(type);
+  return false;
 }
 
 bool File::DeleteLink(Namespace* namespc, const char* name) {
@@ -394,11 +408,15 @@
 bool File::Rename(Namespace* namespc,
                   const char* old_path,
                   const char* new_path) {
-  NamespaceScope oldns(namespc, old_path);
-  NamespaceScope newns(namespc, new_path);
-  return CheckTypeAndSetErrno(namespc, old_path, kIsFile, true) &&
-         (NO_RETRY_EXPECTED(renameat(oldns.fd(), oldns.path(), newns.fd(),
-                                     newns.path())) == 0);
+  File::Type type = File::GetType(namespc, old_path, true);
+  if (type == kIsFile || type == kIsSock || type == kIsPipe) {
+    NamespaceScope oldns(namespc, old_path);
+    NamespaceScope newns(namespc, new_path);
+    return (NO_RETRY_EXPECTED(renameat(oldns.fd(), oldns.path(), newns.fd(),
+                                       newns.path())) == 0);
+  }
+  SetErrno(type);
+  return false;
 }
 
 bool File::RenameLink(Namespace* namespc,
@@ -414,7 +432,9 @@
 bool File::Copy(Namespace* namespc,
                 const char* old_path,
                 const char* new_path) {
-  if (!CheckTypeAndSetErrno(namespc, old_path, kIsFile, true)) {
+  File::Type type = File::GetType(namespc, old_path, true);
+  if (type != kIsFile && type != kIsSock && type != kIsPipe) {
+    SetErrno(type);
     return false;
   }
   NamespaceScope oldns(namespc, old_path);
@@ -503,6 +523,10 @@
       data[kType] = kIsDirectory;
     } else if (S_ISLNK(st.st_mode)) {
       data[kType] = kIsLink;
+    } else if (S_ISSOCK(st.st_mode)) {
+      data[kType] = kIsSock;
+    } else if (S_ISFIFO(st.st_mode)) {
+      data[kType] = kIsPipe;
     } else {
       data[kType] = kDoesNotExist;
     }
diff --git a/runtime/bin/file_linux.cc b/runtime/bin/file_linux.cc
index 625f054..994bcbd 100644
--- a/runtime/bin/file_linux.cc
+++ b/runtime/bin/file_linux.cc
@@ -350,18 +350,17 @@
   if (S_ISLNK(entry_info.st_mode)) {
     return File::kIsLink;
   }
+  if (S_ISSOCK(entry_info.st_mode)) {
+    return File::kIsSock;
+  }
+  if (S_ISFIFO(entry_info.st_mode)) {
+    return File::kIsPipe;
+  }
   return File::kDoesNotExist;
 }
 
-static bool CheckTypeAndSetErrno(Namespace* namespc,
-                                 const char* name,
-                                 File::Type expected,
-                                 bool follow_links) {
-  File::Type actual = File::GetType(namespc, name, follow_links);
-  if (actual == expected) {
-    return true;
-  }
-  switch (actual) {
+static void SetErrno(File::Type type) {
+  switch (type) {
     case File::kIsDirectory:
       errno = EISDIR;
       break;
@@ -372,13 +371,28 @@
       errno = EINVAL;
       break;
   }
+}
+
+static bool CheckTypeAndSetErrno(Namespace* namespc,
+                                 const char* name,
+                                 File::Type expected,
+                                 bool follow_links) {
+  File::Type actual = File::GetType(namespc, name, follow_links);
+  if (actual == expected) {
+    return true;
+  }
+  SetErrno(actual);
   return false;
 }
 
 bool File::Delete(Namespace* namespc, const char* name) {
-  NamespaceScope ns(namespc, name);
-  return CheckTypeAndSetErrno(namespc, name, kIsFile, true) &&
-         (NO_RETRY_EXPECTED(unlinkat(ns.fd(), ns.path(), 0)) == 0);
+  File::Type type = File::GetType(namespc, name, true);
+  if (type == kIsFile || type == kIsSock || type == kIsPipe) {
+    NamespaceScope ns(namespc, name);
+    return (NO_RETRY_EXPECTED(unlinkat(ns.fd(), ns.path(), 0)) == 0);
+  }
+  SetErrno(type);
+  return false;
 }
 
 bool File::DeleteLink(Namespace* namespc, const char* name) {
@@ -390,11 +404,15 @@
 bool File::Rename(Namespace* namespc,
                   const char* old_path,
                   const char* new_path) {
-  NamespaceScope oldns(namespc, old_path);
-  NamespaceScope newns(namespc, new_path);
-  return CheckTypeAndSetErrno(namespc, old_path, kIsFile, true) &&
-         (NO_RETRY_EXPECTED(renameat(oldns.fd(), oldns.path(), newns.fd(),
-                                     newns.path())) == 0);
+  File::Type type = File::GetType(namespc, old_path, true);
+  if (type == kIsFile || type == kIsSock || type == kIsPipe) {
+    NamespaceScope oldns(namespc, old_path);
+    NamespaceScope newns(namespc, new_path);
+    return (NO_RETRY_EXPECTED(renameat(oldns.fd(), oldns.path(), newns.fd(),
+                                       newns.path())) == 0);
+  }
+  SetErrno(type);
+  return false;
 }
 
 bool File::RenameLink(Namespace* namespc,
@@ -410,7 +428,9 @@
 bool File::Copy(Namespace* namespc,
                 const char* old_path,
                 const char* new_path) {
-  if (!CheckTypeAndSetErrno(namespc, old_path, kIsFile, true)) {
+  File::Type type = File::GetType(namespc, old_path, true);
+  if (type != kIsFile && type != kIsSock && type != kIsPipe) {
+    SetErrno(type);
     return false;
   }
   NamespaceScope oldns(namespc, old_path);
@@ -509,6 +529,10 @@
       data[kType] = kIsDirectory;
     } else if (S_ISLNK(st.st_mode)) {
       data[kType] = kIsLink;
+    } else if (S_ISSOCK(st.st_mode)) {
+      data[kType] = kIsSock;
+    } else if (S_ISFIFO(st.st_mode)) {
+      data[kType] = kIsPipe;
     } else {
       data[kType] = kDoesNotExist;
     }
diff --git a/runtime/bin/file_macos.cc b/runtime/bin/file_macos.cc
index 92ca3a4..93065b0 100644
--- a/runtime/bin/file_macos.cc
+++ b/runtime/bin/file_macos.cc
@@ -389,18 +389,17 @@
   if (S_ISLNK(entry_info.st_mode)) {
     return File::kIsLink;
   }
+  if (S_ISSOCK(entry_info.st_mode)) {
+    return File::kIsSock;
+  }
+  if (S_ISFIFO(entry_info.st_mode)) {
+    return File::kIsPipe;
+  }
   return File::kDoesNotExist;
 }
 
-static bool CheckTypeAndSetErrno(Namespace* namespc,
-                                 const char* name,
-                                 File::Type expected,
-                                 bool follow_links) {
-  File::Type actual = File::GetType(namespc, name, follow_links);
-  if (actual == expected) {
-    return true;
-  }
-  switch (actual) {
+static void SetErrno(File::Type type) {
+  switch (type) {
     case File::kIsDirectory:
       errno = EISDIR;
       break;
@@ -411,12 +410,27 @@
       errno = EINVAL;
       break;
   }
+}
+
+static bool CheckTypeAndSetErrno(Namespace* namespc,
+                                 const char* name,
+                                 File::Type expected,
+                                 bool follow_links) {
+  File::Type actual = File::GetType(namespc, name, follow_links);
+  if (actual == expected) {
+    return true;
+  }
+  SetErrno(actual);
   return false;
 }
 
 bool File::Delete(Namespace* namespc, const char* name) {
-  return CheckTypeAndSetErrno(namespc, name, kIsFile, true) &&
-         (NO_RETRY_EXPECTED(unlink(name)) == 0);
+  File::Type type = File::GetType(namespc, name, true);
+  if (type == kIsFile || type == kIsSock || type == kIsPipe) {
+    return (NO_RETRY_EXPECTED(unlink(name)) == 0);
+  }
+  SetErrno(type);
+  return false;
 }
 
 bool File::DeleteLink(Namespace* namespc, const char* name) {
@@ -427,8 +441,12 @@
 bool File::Rename(Namespace* namespc,
                   const char* old_path,
                   const char* new_path) {
-  return CheckTypeAndSetErrno(namespc, old_path, kIsFile, true) &&
-         (NO_RETRY_EXPECTED(rename(old_path, new_path)) == 0);
+  File::Type type = File::GetType(namespc, old_path, true);
+  if (type == kIsFile || type == kIsSock || type == kIsPipe) {
+    return (NO_RETRY_EXPECTED(rename(old_path, new_path)) == 0);
+  }
+  SetErrno(type);
+  return false;
 }
 
 bool File::RenameLink(Namespace* namespc,
@@ -441,8 +459,12 @@
 bool File::Copy(Namespace* namespc,
                 const char* old_path,
                 const char* new_path) {
-  return CheckTypeAndSetErrno(namespc, old_path, kIsFile, true) &&
-         (copyfile(old_path, new_path, NULL, COPYFILE_ALL) == 0);
+  File::Type type = File::GetType(namespc, old_path, true);
+  if (type == kIsFile || type == kIsSock || type == kIsPipe) {
+    return (copyfile(old_path, new_path, NULL, COPYFILE_ALL) == 0);
+  }
+  SetErrno(type);
+  return false;
 }
 
 static bool StatHelper(Namespace* namespc, const char* name, struct stat* st) {
@@ -480,6 +502,10 @@
       data[kType] = kIsDirectory;
     } else if (S_ISLNK(st.st_mode)) {
       data[kType] = kIsLink;
+    } else if (S_ISSOCK(st.st_mode)) {
+      data[kType] = kIsSock;
+    } else if (S_ISFIFO(st.st_mode)) {
+      data[kType] = kIsPipe;
     } else {
       data[kType] = kDoesNotExist;
     }
diff --git a/sdk/lib/io/file_system_entity.dart b/sdk/lib/io/file_system_entity.dart
index 0b9ae80..e3b9075 100644
--- a/sdk/lib/io/file_system_entity.dart
+++ b/sdk/lib/io/file_system_entity.dart
@@ -16,12 +16,20 @@
 
   static const link = const FileSystemEntityType._internal(2);
 
-  static const notFound = const FileSystemEntityType._internal(3);
+  static const unixDomainSock = const FileSystemEntityType._internal(3);
+
+  static const pipe = const FileSystemEntityType._internal(4);
+
+  static const notFound = const FileSystemEntityType._internal(5);
+  @Deprecated("Use notFound instead")
+  static const NOT_FOUND = notFound;
 
   static const _typeList = const [
     FileSystemEntityType.file,
     FileSystemEntityType.directory,
     FileSystemEntityType.link,
+    FileSystemEntityType.unixDomainSock,
+    FileSystemEntityType.pipe,
     FileSystemEntityType.notFound,
   ];
   final int _type;
@@ -29,7 +37,14 @@
   const FileSystemEntityType._internal(this._type);
 
   static FileSystemEntityType _lookup(int type) => _typeList[type];
-  String toString() => const ['file', 'directory', 'link', 'notFound'][_type];
+  String toString() => const [
+        'file',
+        'directory',
+        'link',
+        'unixDomainSock',
+        'pipe',
+        'notFound'
+      ][_type];
 }
 
 /// The result of calling the POSIX `stat()` function on a file system object.
diff --git a/tests/standalone/io/named_pipe_operations_test.dart b/tests/standalone/io/named_pipe_operations_test.dart
new file mode 100644
index 0000000..1e17dab
--- /dev/null
+++ b/tests/standalone/io/named_pipe_operations_test.dart
@@ -0,0 +1,118 @@
+// Copyright (c) 2022, 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.
+// Testing file input stream, VM-only, standalone test.
+
+import "dart:convert";
+import "dart:io";
+
+import "package:async_helper/async_helper.dart";
+import "package:expect/expect.dart";
+import "package:path/path.dart";
+
+final String stdinPipePath = '/dev/fd/0';
+
+startProcess(Directory dir, String fname, String script, String result) async {
+  File file = new File(join(dir.path, fname));
+  file.writeAsString(script);
+  StringBuffer output = new StringBuffer();
+  Process process = await Process.start(
+      Platform.executable,
+      []
+        ..addAll(Platform.executableArguments)
+        ..add('--sound-null-safety')
+        ..add('--verbosity=warning')
+        ..add(file.path));
+  bool stdinWriteFailed = false;
+  process.stdout.transform(utf8.decoder).listen(output.write);
+  process.stderr.transform(utf8.decoder).listen((data) {
+    if (!stdinWriteFailed) {
+      Expect.fail(data);
+      process.kill();
+    }
+  });
+  process.stdin.done.catchError((e) {
+    // If the write to stdin fails, then give up. We can't test the thing we
+    // wanted to test.
+    stdinWriteFailed = true;
+    process.kill();
+  });
+  await process.stdin.flush();
+  await process.stdin.close();
+
+  int status = await process.exitCode;
+  if (!stdinWriteFailed) {
+    Expect.equals(0, status);
+    Expect.contains(result, output.toString());
+  }
+}
+
+main() async {
+  asyncStart();
+  // Operations on a named pipe is only supported on Linux and MacOS.
+  if (!Platform.isLinux && !Platform.isMacOS) {
+    print("This test is only supported on Linux and MacOS.");
+    asyncEnd();
+    return;
+  }
+
+  Directory directory = Directory.systemTemp.createTempSync('named_pipe');
+
+  final String delScript = '''
+    import "dart:io";
+    main() {
+      try {
+        final file = File("$stdinPipePath");
+        if (file.existsSync()) print("Pipe Exists");
+        file.deleteSync();
+        if (!file.existsSync()) print("Pipe Deleted");
+      } catch (e) {
+        print(e);
+      }
+    }
+  ''';
+
+  final String renameScript = '''
+    import "dart:io";
+    main() {
+      try {
+        final file = File("$stdinPipePath");
+        if (file.existsSync()) print("Pipe Exists");
+        file.renameSync("junk");
+        if (!file.existsSync()) print("Pipe Renamed");
+      } catch (e) {
+        print(e);
+      }
+    }
+  ''';
+
+  final String copyScript = '''
+    import "dart:io";
+    main() {
+      try {
+        final file = File("$stdinPipePath");
+        if (file.existsSync()) print("Pipe Exists");
+        file.copySync("junk");
+      } catch (e) {
+        print(e);
+      }
+    }
+  ''';
+
+  // If there's no file system access to the pipe, then we can't do a meaningful
+  // test.
+  if (!await (new File(stdinPipePath).exists())) {
+    print("Couldn't find $stdinPipePath.");
+    directory.deleteSync(recursive: true);
+    asyncEnd();
+    return;
+  }
+
+  await startProcess(directory, 'delscript', delScript, "Cannot delete file");
+  await startProcess(
+      directory, 'renamescript', renameScript, "Cannot rename file");
+  await startProcess(directory, 'copyscript', copyScript, "Cannot copy file");
+
+  directory.deleteSync(recursive: true);
+  asyncEnd();
+}
diff --git a/tests/standalone/io/named_pipe_type_test.dart b/tests/standalone/io/named_pipe_type_test.dart
new file mode 100644
index 0000000..4069837
--- /dev/null
+++ b/tests/standalone/io/named_pipe_type_test.dart
@@ -0,0 +1,77 @@
+// Copyright (c) 2022, 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.
+// Testing file input stream, VM-only, standalone test.
+
+import "dart:convert";
+import "dart:io";
+
+import "package:async_helper/async_helper.dart";
+import "package:expect/expect.dart";
+import "package:path/path.dart";
+
+main() async {
+  asyncStart();
+  // Reading a script from a named pipe is only supported on Linux and MacOS.
+  if (!Platform.isLinux && !Platform.isMacOS) {
+    print("This test is only supported on Linux and MacOS.");
+    asyncEnd();
+    return;
+  }
+
+  Directory dir = Directory.systemTemp.createTempSync('named_pipe');
+  final String stdinPipePath = '/dev/fd/0';
+  final String script = '''
+    import "dart:io";
+    main() {
+      FileStat fileStat = FileStat.statSync("$stdinPipePath");
+      print(fileStat.type);
+    }
+  ''';
+  File file = new File(join(dir.path, "typeScript"));
+  file.writeAsString(script);
+
+  // If there's no file system access to the pipe, then we can't do a meaningful
+  // test.
+  if (!await (new File(stdinPipePath).exists())) {
+    print("Couldn't find $stdinPipePath.");
+    dir.deleteSync(recursive: true);
+    asyncEnd();
+    return;
+  }
+
+  StringBuffer output = new StringBuffer();
+  Process process = await Process.start(
+      Platform.executable,
+      []
+        ..addAll(Platform.executableArguments)
+        ..add('--sound-null-safety')
+        ..add('--verbosity=warning')
+        ..add(file.path));
+  bool stdinWriteFailed = false;
+  process.stdout.transform(utf8.decoder).listen(output.write);
+  process.stderr.transform(utf8.decoder).listen((data) {
+    if (!stdinWriteFailed) {
+      Expect.fail(data);
+      process.kill();
+    }
+  });
+  process.stdin.done.catchError((e) {
+    // If the write to stdin fails, then give up. We can't test the thing we
+    // wanted to test.
+    stdinWriteFailed = true;
+    process.kill();
+  });
+  // process.stdin.writeln(script);
+  await process.stdin.flush();
+  await process.stdin.close();
+
+  int status = await process.exitCode;
+  if (!stdinWriteFailed) {
+    Expect.equals(0, status);
+    Expect.equals("pipe\n", output.toString());
+  }
+
+  dir.deleteSync(recursive: true);
+  asyncEnd();
+}
diff --git a/tests/standalone/io/unix_socket_test.dart b/tests/standalone/io/unix_socket_test.dart
index d63011a..e0b80b6 100644
--- a/tests/standalone/io/unix_socket_test.dart
+++ b/tests/standalone/io/unix_socket_test.dart
@@ -553,7 +553,7 @@
               socket.close();
             }
           }), (e, st) {
-    print('Got expected unhandled exception $e $st');
+    // print('Got expected unhandled exception $e $st');
     Expect.equals(true, e is SocketException);
     completer.complete(true);
   });
@@ -823,6 +823,79 @@
   });
 }
 
+Future testDeleteFile(String tempDirPath) async {
+  if (!Platform.isLinux && !Platform.isAndroid) {
+    return;
+  }
+  final name = '$tempDirPath/sock';
+  final address = InternetAddress(name, type: InternetAddressType.unix);
+  var server = await RawServerSocket.bind(address, 0, shared: false);
+  final file = File(name);
+  Expect.isTrue(file.existsSync());
+  Expect.isTrue(await file.exists());
+  file.deleteSync();
+  Expect.isFalse(file.existsSync());
+  Expect.isFalse(await file.exists());
+  await server.close();
+
+  server = await RawServerSocket.bind(address, 0, shared: false);
+  Expect.isTrue(file.existsSync());
+  Expect.isTrue(await file.exists());
+  await file.delete();
+  Expect.isFalse(file.existsSync());
+  Expect.isFalse(await file.exists());
+  await server.close();
+}
+
+Future testFileStat(String tempDirPath) async {
+  if (!Platform.isLinux && !Platform.isAndroid) {
+    return;
+  }
+  final name = '$tempDirPath/sock';
+  final address = InternetAddress(name, type: InternetAddressType.unix);
+  var server = await RawServerSocket.bind(address, 0, shared: false);
+  FileStat fileStat = FileStat.statSync(name);
+  Expect.equals(FileSystemEntityType.unixDomainSock, fileStat.type);
+  await server.close();
+}
+
+Future testFileRename(String tempDirPath) async {
+  if (!Platform.isLinux && !Platform.isAndroid) {
+    return;
+  }
+  final name1 = '$tempDirPath/sock1';
+  final name2 = '$tempDirPath/sock2';
+  final address = InternetAddress(name1, type: InternetAddressType.unix);
+  var server = await RawServerSocket.bind(address, 0, shared: false);
+  final file1 = File(name1);
+  final file2 = file1.renameSync(name2);
+  Expect.isFalse(file1.existsSync());
+  Expect.isTrue(file2.existsSync());
+  await server.close();
+  file2.deleteSync();
+  Expect.isFalse(file1.existsSync());
+  Expect.isFalse(file2.existsSync());
+}
+
+Future testFileCopy(String tempDirPath) async {
+  if (!Platform.isLinux && !Platform.isAndroid) {
+    return;
+  }
+  final name1 = '$tempDirPath/sock1';
+  final name2 = '$tempDirPath/sock2';
+  final address = InternetAddress(name1, type: InternetAddressType.unix);
+  final file1 = File(name1);
+  var server = await RawServerSocket.bind(address, 0, shared: false);
+  try {
+    final file2 = file1.copySync(name2);
+    Expect.isFalse(true);
+  } catch (e) {
+    Expect.isTrue(e is FileSystemException);
+  }
+  await server.close();
+  Expect.isFalse(file1.existsSync());
+}
+
 void main(List<String> args) async {
   runZonedGuarded(() async {
     if (args.length > 0 && args[0] == '--start-stdio-message-test') {
@@ -831,7 +904,6 @@
       });
       return;
     }
-
     await withTempDir('unix_socket_test', (Directory dir) async {
       await testAddress('${dir.path}');
     });
@@ -877,6 +949,18 @@
     await withTempDir('unix_socket_test', (Directory dir) async {
       await testStdioMessage('${dir.path}', caller: true);
     });
+    await withTempDir('unix_socket_test', (Directory dir) async {
+      await testDeleteFile('${dir.path}');
+    });
+    await withTempDir('unix_socket_test', (Directory dir) async {
+      await testFileStat('${dir.path}');
+    });
+    await withTempDir('unix_socket_test', (Directory dir) async {
+      await testFileRename('${dir.path}');
+    });
+    await withTempDir('unix_socket_test', (Directory dir) async {
+      await testFileCopy('${dir.path}');
+    });
   }, (e, st) {
     if (Platform.isMacOS || Platform.isLinux || Platform.isAndroid) {
       Expect.fail("Unexpected exception $e is thrown");
diff --git a/tests/standalone/standalone_precompiled.status b/tests/standalone/standalone_precompiled.status
index 69da5e9..2e1b2fb 100644
--- a/tests/standalone/standalone_precompiled.status
+++ b/tests/standalone/standalone_precompiled.status
@@ -19,7 +19,9 @@
 io/http_response_deadline_test: Skip
 io/http_server_close_response_after_error_test: Skip
 io/https_unauthorized_test: Skip
+io/named_pipe_operations_test: Skip
 io/named_pipe_script_test: Skip
+io/named_pipe_type_test: Skip
 io/namespace_test: Skip # Issue 33168
 io/platform_resolved_executable_test: Skip
 io/print_sync_test: Skip
diff --git a/tests/standalone/standalone_vm.status b/tests/standalone/standalone_vm.status
index 067e3e05..5933938 100644
--- a/tests/standalone/standalone_vm.status
+++ b/tests/standalone/standalone_vm.status
@@ -56,7 +56,6 @@
 
 [ $mode == release && $runtime == vm && $system == macos ]
 io/http_server_close_response_after_error_test: Pass, Timeout # Issue 28370: timeout.
-io/named_pipe_script_test: Pass, RuntimeError # Issue 28737
 
 [ $mode == release && $runtime == vm && $system == windows ]
 io/http_server_close_response_after_error_test: Pass, Timeout # Issue 28370: timeout.
@@ -64,6 +63,9 @@
 [ $runtime == dart_precompiled && $system == linux && ($arch == simarm || $arch == simarm64 || $arch == simarm64c || $arch == simriscv32 || $arch == simriscv64 || $arch == x64) ]
 io/stdout_stderr_non_blocking_test: Pass, Timeout # Issue 35192
 
+[ $runtime == vm && $system == macos ]
+io/named_pipe_script_test: Pass, RuntimeError # Issue 28737
+
 [ $runtime == vm && ($arch == arm || $arch == arm64) ]
 io/dart_std_io_pipe_test: Timeout, Pass
 io/file_input_stream_test: Skip # Issue 26109
diff --git a/tests/standalone_2/io/named_pipe_operations_test.dart b/tests/standalone_2/io/named_pipe_operations_test.dart
new file mode 100644
index 0000000..1e17dab
--- /dev/null
+++ b/tests/standalone_2/io/named_pipe_operations_test.dart
@@ -0,0 +1,118 @@
+// Copyright (c) 2022, 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.
+// Testing file input stream, VM-only, standalone test.
+
+import "dart:convert";
+import "dart:io";
+
+import "package:async_helper/async_helper.dart";
+import "package:expect/expect.dart";
+import "package:path/path.dart";
+
+final String stdinPipePath = '/dev/fd/0';
+
+startProcess(Directory dir, String fname, String script, String result) async {
+  File file = new File(join(dir.path, fname));
+  file.writeAsString(script);
+  StringBuffer output = new StringBuffer();
+  Process process = await Process.start(
+      Platform.executable,
+      []
+        ..addAll(Platform.executableArguments)
+        ..add('--sound-null-safety')
+        ..add('--verbosity=warning')
+        ..add(file.path));
+  bool stdinWriteFailed = false;
+  process.stdout.transform(utf8.decoder).listen(output.write);
+  process.stderr.transform(utf8.decoder).listen((data) {
+    if (!stdinWriteFailed) {
+      Expect.fail(data);
+      process.kill();
+    }
+  });
+  process.stdin.done.catchError((e) {
+    // If the write to stdin fails, then give up. We can't test the thing we
+    // wanted to test.
+    stdinWriteFailed = true;
+    process.kill();
+  });
+  await process.stdin.flush();
+  await process.stdin.close();
+
+  int status = await process.exitCode;
+  if (!stdinWriteFailed) {
+    Expect.equals(0, status);
+    Expect.contains(result, output.toString());
+  }
+}
+
+main() async {
+  asyncStart();
+  // Operations on a named pipe is only supported on Linux and MacOS.
+  if (!Platform.isLinux && !Platform.isMacOS) {
+    print("This test is only supported on Linux and MacOS.");
+    asyncEnd();
+    return;
+  }
+
+  Directory directory = Directory.systemTemp.createTempSync('named_pipe');
+
+  final String delScript = '''
+    import "dart:io";
+    main() {
+      try {
+        final file = File("$stdinPipePath");
+        if (file.existsSync()) print("Pipe Exists");
+        file.deleteSync();
+        if (!file.existsSync()) print("Pipe Deleted");
+      } catch (e) {
+        print(e);
+      }
+    }
+  ''';
+
+  final String renameScript = '''
+    import "dart:io";
+    main() {
+      try {
+        final file = File("$stdinPipePath");
+        if (file.existsSync()) print("Pipe Exists");
+        file.renameSync("junk");
+        if (!file.existsSync()) print("Pipe Renamed");
+      } catch (e) {
+        print(e);
+      }
+    }
+  ''';
+
+  final String copyScript = '''
+    import "dart:io";
+    main() {
+      try {
+        final file = File("$stdinPipePath");
+        if (file.existsSync()) print("Pipe Exists");
+        file.copySync("junk");
+      } catch (e) {
+        print(e);
+      }
+    }
+  ''';
+
+  // If there's no file system access to the pipe, then we can't do a meaningful
+  // test.
+  if (!await (new File(stdinPipePath).exists())) {
+    print("Couldn't find $stdinPipePath.");
+    directory.deleteSync(recursive: true);
+    asyncEnd();
+    return;
+  }
+
+  await startProcess(directory, 'delscript', delScript, "Cannot delete file");
+  await startProcess(
+      directory, 'renamescript', renameScript, "Cannot rename file");
+  await startProcess(directory, 'copyscript', copyScript, "Cannot copy file");
+
+  directory.deleteSync(recursive: true);
+  asyncEnd();
+}
diff --git a/tests/standalone_2/io/named_pipe_type_test.dart b/tests/standalone_2/io/named_pipe_type_test.dart
new file mode 100644
index 0000000..4069837
--- /dev/null
+++ b/tests/standalone_2/io/named_pipe_type_test.dart
@@ -0,0 +1,77 @@
+// Copyright (c) 2022, 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.
+// Testing file input stream, VM-only, standalone test.
+
+import "dart:convert";
+import "dart:io";
+
+import "package:async_helper/async_helper.dart";
+import "package:expect/expect.dart";
+import "package:path/path.dart";
+
+main() async {
+  asyncStart();
+  // Reading a script from a named pipe is only supported on Linux and MacOS.
+  if (!Platform.isLinux && !Platform.isMacOS) {
+    print("This test is only supported on Linux and MacOS.");
+    asyncEnd();
+    return;
+  }
+
+  Directory dir = Directory.systemTemp.createTempSync('named_pipe');
+  final String stdinPipePath = '/dev/fd/0';
+  final String script = '''
+    import "dart:io";
+    main() {
+      FileStat fileStat = FileStat.statSync("$stdinPipePath");
+      print(fileStat.type);
+    }
+  ''';
+  File file = new File(join(dir.path, "typeScript"));
+  file.writeAsString(script);
+
+  // If there's no file system access to the pipe, then we can't do a meaningful
+  // test.
+  if (!await (new File(stdinPipePath).exists())) {
+    print("Couldn't find $stdinPipePath.");
+    dir.deleteSync(recursive: true);
+    asyncEnd();
+    return;
+  }
+
+  StringBuffer output = new StringBuffer();
+  Process process = await Process.start(
+      Platform.executable,
+      []
+        ..addAll(Platform.executableArguments)
+        ..add('--sound-null-safety')
+        ..add('--verbosity=warning')
+        ..add(file.path));
+  bool stdinWriteFailed = false;
+  process.stdout.transform(utf8.decoder).listen(output.write);
+  process.stderr.transform(utf8.decoder).listen((data) {
+    if (!stdinWriteFailed) {
+      Expect.fail(data);
+      process.kill();
+    }
+  });
+  process.stdin.done.catchError((e) {
+    // If the write to stdin fails, then give up. We can't test the thing we
+    // wanted to test.
+    stdinWriteFailed = true;
+    process.kill();
+  });
+  // process.stdin.writeln(script);
+  await process.stdin.flush();
+  await process.stdin.close();
+
+  int status = await process.exitCode;
+  if (!stdinWriteFailed) {
+    Expect.equals(0, status);
+    Expect.equals("pipe\n", output.toString());
+  }
+
+  dir.deleteSync(recursive: true);
+  asyncEnd();
+}
diff --git a/tests/standalone_2/io/unix_socket_test.dart b/tests/standalone_2/io/unix_socket_test.dart
index 228552a..7d8e7a5 100644
--- a/tests/standalone_2/io/unix_socket_test.dart
+++ b/tests/standalone_2/io/unix_socket_test.dart
@@ -552,7 +552,7 @@
               socket.close();
             }
           }), (e, st) {
-    print('Got expected unhandled exception $e $st');
+    // print('Got expected unhandled exception $e $st');
     Expect.equals(true, e is SocketException);
     completer.complete(true);
   });
@@ -825,6 +825,79 @@
   return completer.future;
 }
 
+Future testDeleteFile(String tempDirPath) async {
+  if (!Platform.isLinux && !Platform.isAndroid) {
+    return;
+  }
+  final address =
+      InternetAddress('$tempDirPath/sock', type: InternetAddressType.unix);
+  var server = await RawServerSocket.bind(address, 0, shared: false);
+  final file = File('$tempDirPath/sock');
+  Expect.isTrue(file.existsSync());
+  Expect.isTrue(await file.exists());
+  file.deleteSync();
+  Expect.isFalse(file.existsSync());
+  Expect.isFalse(await file.exists());
+  await server.close();
+
+  server = await RawServerSocket.bind(address, 0, shared: false);
+  Expect.isTrue(file.existsSync());
+  Expect.isTrue(await file.exists());
+  await file.delete();
+  Expect.isFalse(file.existsSync());
+  Expect.isFalse(await file.exists());
+  await server.close();
+}
+
+Future testFileStat(String tempDirPath) async {
+  if (!Platform.isLinux && !Platform.isAndroid) {
+    return;
+  }
+  final name = '$tempDirPath/sock';
+  final address = InternetAddress(name, type: InternetAddressType.unix);
+  var server = await RawServerSocket.bind(address, 0, shared: false);
+  FileStat fileStat = FileStat.statSync(name);
+  Expect.equals(FileSystemEntityType.unixDomainSock, fileStat.type);
+  server.close();
+}
+
+Future testFileRename(String tempDirPath) async {
+  if (!Platform.isLinux && !Platform.isAndroid) {
+    return;
+  }
+  final name1 = '$tempDirPath/sock1';
+  final name2 = '$tempDirPath/sock2';
+  final address = InternetAddress(name1, type: InternetAddressType.unix);
+  var server = await RawServerSocket.bind(address, 0, shared: false);
+  final file1 = File(name1);
+  final file2 = file1.renameSync(name2);
+  Expect.isFalse(file1.existsSync());
+  Expect.isTrue(file2.existsSync());
+  await server.close();
+  file2.deleteSync();
+  Expect.isFalse(file1.existsSync());
+  Expect.isFalse(file2.existsSync());
+}
+
+Future testFileCopy(String tempDirPath) async {
+  if (!Platform.isLinux && !Platform.isAndroid) {
+    return;
+  }
+  final name1 = '$tempDirPath/sock1';
+  final name2 = '$tempDirPath/sock2';
+  final address = InternetAddress(name1, type: InternetAddressType.unix);
+  final file1 = File(name1);
+  var server = await RawServerSocket.bind(address, 0, shared: false);
+  try {
+    final file2 = file1.copySync(name2);
+    Expect.isFalse(true);
+  } catch (e) {
+    Expect.isTrue(e is FileSystemException);
+  }
+  await server.close();
+  Expect.isFalse(file1.existsSync());
+}
+
 void main(List<String> args) async {
   runZonedGuarded(() async {
     if (args.length > 0 && args[0] == '--start-stdio-message-test') {
@@ -879,6 +952,18 @@
     await withTempDir('unix_socket_test', (Directory dir) async {
       await testStdioMessage('${dir.path}', caller: true);
     });
+    await withTempDir('unix_socket_test', (Directory dir) async {
+      await testDeleteFile('${dir.path}');
+    });
+    await withTempDir('unix_socket_test', (Directory dir) async {
+      await testFileStat('${dir.path}');
+    });
+    await withTempDir('unix_socket_test', (Directory dir) async {
+      await testFileRename('${dir.path}');
+    });
+    await withTempDir('unix_socket_test', (Directory dir) async {
+      await testFileCopy('${dir.path}');
+    });
   }, (e, st) {
     if (Platform.isMacOS || Platform.isLinux || Platform.isAndroid) {
       Expect.fail("Unexpected exception $e is thrown");
diff --git a/tests/standalone_2/standalone_2_precompiled.status b/tests/standalone_2/standalone_2_precompiled.status
index f7f7eb2..69f6510 100644
--- a/tests/standalone_2/standalone_2_precompiled.status
+++ b/tests/standalone_2/standalone_2_precompiled.status
@@ -16,7 +16,9 @@
 io/http_response_deadline_test: Skip
 io/http_server_close_response_after_error_test: Skip
 io/https_unauthorized_test: Skip
+io/named_pipe_operations_test: Skip
 io/named_pipe_script_test: Skip
+io/named_pipe_type_test: Skip
 io/namespace_test: Skip # Issue 33168
 io/platform_resolved_executable_test: Skip
 io/print_sync_test: Skip
diff --git a/tests/standalone_2/standalone_2_vm.status b/tests/standalone_2/standalone_2_vm.status
index 067e3e05..5933938 100644
--- a/tests/standalone_2/standalone_2_vm.status
+++ b/tests/standalone_2/standalone_2_vm.status
@@ -56,7 +56,6 @@
 
 [ $mode == release && $runtime == vm && $system == macos ]
 io/http_server_close_response_after_error_test: Pass, Timeout # Issue 28370: timeout.
-io/named_pipe_script_test: Pass, RuntimeError # Issue 28737
 
 [ $mode == release && $runtime == vm && $system == windows ]
 io/http_server_close_response_after_error_test: Pass, Timeout # Issue 28370: timeout.
@@ -64,6 +63,9 @@
 [ $runtime == dart_precompiled && $system == linux && ($arch == simarm || $arch == simarm64 || $arch == simarm64c || $arch == simriscv32 || $arch == simriscv64 || $arch == x64) ]
 io/stdout_stderr_non_blocking_test: Pass, Timeout # Issue 35192
 
+[ $runtime == vm && $system == macos ]
+io/named_pipe_script_test: Pass, RuntimeError # Issue 28737
+
 [ $runtime == vm && ($arch == arm || $arch == arm64) ]
 io/dart_std_io_pipe_test: Timeout, Pass
 io/file_input_stream_test: Skip # Issue 26109