Introduce Path#isSpecialFile, FileSystem#isSpecialFile, and FileStatus#isSpecialFile to help disambiguate between a regular file and a special file, since the file size of a special file cannot be trusted.

--
MOS_MIGRATED_REVID=105903622
diff --git a/src/main/java/com/google/devtools/build/lib/actions/cache/InjectedStat.java b/src/main/java/com/google/devtools/build/lib/actions/cache/InjectedStat.java
index aa32304..858edba 100644
--- a/src/main/java/com/google/devtools/build/lib/actions/cache/InjectedStat.java
+++ b/src/main/java/com/google/devtools/build/lib/actions/cache/InjectedStat.java
@@ -36,6 +36,11 @@
   }
 
   @Override
+  public boolean isSpecialFile() {
+    return false;
+  }
+
+  @Override
   public boolean isDirectory() {
     return false;
   }
diff --git a/src/main/java/com/google/devtools/build/lib/skyframe/FileStateValue.java b/src/main/java/com/google/devtools/build/lib/skyframe/FileStateValue.java
index 1a75e3b..3fffcd9 100644
--- a/src/main/java/com/google/devtools/build/lib/skyframe/FileStateValue.java
+++ b/src/main/java/com/google/devtools/build/lib/skyframe/FileStateValue.java
@@ -41,7 +41,7 @@
  *   <li> For a symlink, the symlink target is noted.
  *   <li> For a directory, the existence is noted.
  *   <li> For a file, the existence is noted, along with metadata about the file (e.g.
- *        file digest). See {@link FileFileStateValue}.
+ *        file digest). See {@link RegularFileStateValue}.
  * <ul>
  *
  * <p>This class is an implementation detail of {@link FileValue} and should not be used outside of
@@ -59,7 +59,8 @@
       new NonexistentFileStateValue();
 
   enum Type {
-    FILE,
+    REGULAR_FILE,
+    SPECIAL_FILE,
     DIRECTORY,
     SYMLINK,
     NONEXISTENT,
@@ -86,7 +87,9 @@
           throws InconsistentFilesystemException, IOException {
     Path path = rootedPath.asPath();
     if (statNoFollow.isFile()) {
-      return FileFileStateValue.fromPath(path, statNoFollow, tsgm);
+      return statNoFollow.isSpecialFile()
+          ? SpecialFileStateValue.fromStat(statNoFollow, tsgm)
+          : RegularFileStateValue.fromPath(path, statNoFollow, tsgm);
     } else if (statNoFollow.isDirectory()) {
       return DIRECTORY_FILE_STATE_NODE;
     } else if (statNoFollow.isSymbolicLink()) {
@@ -125,7 +128,7 @@
   abstract String prettyPrint();
 
   /**
-   * Implementation of {@link FileStateValue} for files that exist.
+   * Implementation of {@link FileStateValue} for regular files that exist.
    *
    * <p>A union of (digest, mtime). We use digests only if a fast digest lookup is available from
    * the filesystem. If not, we fall back to mtime-based digests. This avoids the case where Blaze
@@ -133,7 +136,7 @@
    * where fast digest lookups are not available.
    */
   @ThreadSafe
-  public static final class FileFileStateValue extends FileStateValue {
+  public static final class RegularFileStateValue extends FileStateValue {
     private final long size;
     // Only needed for empty-file equality-checking. Otherwise is always -1.
     // TODO(bazel-team): Consider getting rid of this special case for empty files.
@@ -141,7 +144,7 @@
     @Nullable private final byte[] digest;
     @Nullable private final FileContentsProxy contentsProxy;
 
-    private FileFileStateValue(long size, long mtime, byte[] digest,
+    private RegularFileStateValue(long size, long mtime, byte[] digest,
         FileContentsProxy contentsProxy) {
       Preconditions.checkState((digest == null) != (contentsProxy == null));
       this.size = size;
@@ -156,7 +159,7 @@
      * Create a FileFileStateValue instance corresponding to the given existing file.
      * @param stat must be of type "File". (Not a symlink).
      */
-    private static FileFileStateValue fromPath(Path path, FileStatusWithDigest stat,
+    private static RegularFileStateValue fromPath(Path path, FileStatusWithDigest stat,
                                         @Nullable TimestampGranularityMonitor tsgm)
         throws InconsistentFilesystemException {
       Preconditions.checkState(stat.isFile(), path);
@@ -172,13 +175,13 @@
           if (tsgm != null) {
             tsgm.notifyDependenceOnFileTime(mtime);
           }
-          return new FileFileStateValue(stat.getSize(), stat.getLastModifiedTime(), null,
+          return new RegularFileStateValue(stat.getSize(), stat.getLastModifiedTime(), null,
               FileContentsProxy.create(mtime, stat.getNodeId()));
         } else {
           // We are careful here to avoid putting the value ID into FileMetadata if we already have
           // a digest. Arbitrary filesystems may do weird things with the value ID; a digest is more
           // robust.
-          return new FileFileStateValue(stat.getSize(), stat.getLastModifiedTime(), digest, null);
+          return new RegularFileStateValue(stat.getSize(), stat.getLastModifiedTime(), digest, null);
         }
       } catch (IOException e) {
         String errorMessage = e.getMessage() != null
@@ -191,7 +194,7 @@
 
     @Override
     Type getType() {
-      return Type.FILE;
+      return Type.REGULAR_FILE;
     }
 
     @Override
@@ -207,8 +210,8 @@
 
     @Override
     public boolean equals(Object obj) {
-      if (obj instanceof FileFileStateValue) {
-        FileFileStateValue other = (FileFileStateValue) obj;
+      if (obj instanceof RegularFileStateValue) {
+        RegularFileStateValue other = (RegularFileStateValue) obj;
         return size == other.size && mtime == other.mtime && Arrays.equals(digest, other.digest)
             && Objects.equals(contentsProxy, other.contentsProxy);
       }
@@ -230,6 +233,61 @@
     }
   }
 
+  /** Implementation of {@link FileStateValue} for special files that exist. */
+  public static final class SpecialFileStateValue extends FileStateValue {
+    private final FileContentsProxy contentsProxy;
+
+    private SpecialFileStateValue(FileContentsProxy contentsProxy) {
+      this.contentsProxy = contentsProxy;
+    }
+
+    static SpecialFileStateValue fromStat(FileStatusWithDigest stat,
+        @Nullable TimestampGranularityMonitor tsgm) throws IOException {
+      long mtime = stat.getLastModifiedTime();
+      // Note that TimestampGranularityMonitor#notifyDependenceOnFileTime is a thread-safe
+      // method.
+      if (tsgm != null) {
+        tsgm.notifyDependenceOnFileTime(mtime);
+      }
+      return new SpecialFileStateValue(FileContentsProxy.create(mtime, stat.getNodeId()));
+    }
+
+    @Override
+    Type getType() {
+      return Type.SPECIAL_FILE;
+    }
+
+    @Override
+    long getSize() {
+      return 0;
+    }
+
+    @Override
+    @Nullable
+    byte[] getDigest() {
+      return null;
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+      if (obj instanceof SpecialFileStateValue) {
+        SpecialFileStateValue other = (SpecialFileStateValue) obj;
+        return Objects.equals(contentsProxy, other.contentsProxy);
+      }
+      return false;
+    }
+
+    @Override
+    public int hashCode() {
+      return contentsProxy.hashCode();
+    }
+
+    @Override
+    public String prettyPrint() {
+      return String.format("special file with %s", contentsProxy.prettyPrint());
+    }
+  }
+
   /** Implementation of {@link FileStateValue} for directories that exist. */
   public static final class DirectoryFileStateValue extends FileStateValue {
 
diff --git a/src/main/java/com/google/devtools/build/lib/skyframe/FileValue.java b/src/main/java/com/google/devtools/build/lib/skyframe/FileValue.java
index 8d849e0..0c45cf1 100644
--- a/src/main/java/com/google/devtools/build/lib/skyframe/FileValue.java
+++ b/src/main/java/com/google/devtools/build/lib/skyframe/FileValue.java
@@ -59,11 +59,20 @@
   }
 
   /**
-   * Returns true if this value corresponds to a file or symlink to an existing file. If so, its
-   * parent directory is guaranteed to exist.
+   * Returns true if this value corresponds to a file or symlink to an existing regular or special
+   * file. If so, its parent directory is guaranteed to exist.
    */
   public boolean isFile() {
-    return realFileStateValue().getType() == Type.FILE;
+    return realFileStateValue().getType() == Type.REGULAR_FILE
+        || realFileStateValue().getType() == Type.SPECIAL_FILE;
+  }
+
+  /**
+   * Returns true if this value corresponds to a file or symlink to an existing special file. If so,
+   * its parent directory is guaranteed to exist.
+   */
+  public boolean isSpecialFile() {
+    return realFileStateValue().getType() == Type.SPECIAL_FILE;
   }
 
   /**
diff --git a/src/main/java/com/google/devtools/build/lib/skyframe/PackageFunction.java b/src/main/java/com/google/devtools/build/lib/skyframe/PackageFunction.java
index d8e8230..aa92291 100644
--- a/src/main/java/com/google/devtools/build/lib/skyframe/PackageFunction.java
+++ b/src/main/java/com/google/devtools/build/lib/skyframe/PackageFunction.java
@@ -845,7 +845,7 @@
    *
    * <p>May return null if the computation has to be restarted.
    *
-   * <p>Exactly one of {@code replacementContents} and {@link buildFileValue} will be
+   * <p>Exactly one of {@code replacementContents} and {@code buildFileValue} will be
    * non-{@code null}. The former indicates that we have a faux BUILD file with the given contents
    * and the latter indicates that we have a legitimate BUILD file and should actually do
    * preprocessing.
@@ -875,13 +875,15 @@
           }
           Preprocessor.Result preprocessingResult;
           if (replacementContents == null) {
-            long buildFileSize = Preconditions.checkNotNull(buildFileValue, packageId).getSize();
+            Preconditions.checkNotNull(buildFileValue, packageId);
             // Even though we only open and read the file on a cache miss, note that the BUILD is
             // still parsed two times. Also, the preprocessor may suboptimally open and read it
             // again anyway.
             ParserInputSource inputSource;
             try {
-              inputSource = ParserInputSource.create(buildFilePath, buildFileSize);
+              inputSource = buildFileValue.isSpecialFile()
+                  ? ParserInputSource.create(buildFilePath)
+                  : ParserInputSource.create(buildFilePath, buildFileValue.getSize());
             } catch (IOException e) {
               env.getListener().handle(Event.error(Location.fromFile(buildFilePath),
                   e.getMessage()));
diff --git a/src/main/java/com/google/devtools/build/lib/skyframe/PerBuildSyscallCache.java b/src/main/java/com/google/devtools/build/lib/skyframe/PerBuildSyscallCache.java
index b1265b4..51df367 100644
--- a/src/main/java/com/google/devtools/build/lib/skyframe/PerBuildSyscallCache.java
+++ b/src/main/java/com/google/devtools/build/lib/skyframe/PerBuildSyscallCache.java
@@ -102,6 +102,11 @@
     }
 
     @Override
+    public boolean isSpecialFile() {
+      throw new UnsupportedOperationException();
+    }
+
+    @Override
     public boolean isSymbolicLink() {
       throw new UnsupportedOperationException();
     }
diff --git a/src/main/java/com/google/devtools/build/lib/skyframe/SkyframeExecutor.java b/src/main/java/com/google/devtools/build/lib/skyframe/SkyframeExecutor.java
index cc80950..1b5dfd8 100644
--- a/src/main/java/com/google/devtools/build/lib/skyframe/SkyframeExecutor.java
+++ b/src/main/java/com/google/devtools/build/lib/skyframe/SkyframeExecutor.java
@@ -756,7 +756,9 @@
   protected abstract void invalidate(Predicate<SkyKey> pred);
 
   private static boolean compatibleFileTypes(Dirent.Type oldType, FileStateValue.Type newType) {
-    return (oldType.equals(Dirent.Type.FILE) && newType.equals(FileStateValue.Type.FILE))
+    return (oldType.equals(Dirent.Type.FILE) && newType.equals(FileStateValue.Type.REGULAR_FILE))
+        || (oldType.equals(Dirent.Type.UNKNOWN)
+            && newType.equals(FileStateValue.Type.SPECIAL_FILE))
         || (oldType.equals(Dirent.Type.DIRECTORY) && newType.equals(FileStateValue.Type.DIRECTORY))
         || (oldType.equals(Dirent.Type.SYMLINK) && newType.equals(FileStateValue.Type.SYMLINK));
   }
diff --git a/src/main/java/com/google/devtools/build/lib/unix/FileStatus.java b/src/main/java/com/google/devtools/build/lib/unix/FileStatus.java
index e227f95..9a63fee 100644
--- a/src/main/java/com/google/devtools/build/lib/unix/FileStatus.java
+++ b/src/main/java/com/google/devtools/build/lib/unix/FileStatus.java
@@ -259,4 +259,23 @@
     return (i & 0x7FFFFFFF) - (long) (i & 0x80000000);
   }
 
+  public static boolean isFile(int rawType) {
+    int type = rawType & S_IFMT;
+    return type == S_IFREG || isSpecialFile(rawType);
+  }
+
+  public static boolean isSpecialFile(int rawType) {
+    int type = rawType & S_IFMT;
+    return type == S_IFSOCK || type == S_IFBLK || type == S_IFCHR || type == S_IFIFO;
+  }
+
+  public static boolean isDirectory(int rawType) {
+    int type = rawType & S_IFMT;
+    return type == S_IFDIR;
+  }
+
+  public static boolean isSymbolicLink(int rawType) {
+    int type = rawType & S_IFMT;
+    return type == S_IFLNK;
+  }
 }
diff --git a/src/main/java/com/google/devtools/build/lib/unix/FilesystemUtils.java b/src/main/java/com/google/devtools/build/lib/unix/FilesystemUtils.java
index 525942f..0cab41c 100644
--- a/src/main/java/com/google/devtools/build/lib/unix/FilesystemUtils.java
+++ b/src/main/java/com/google/devtools/build/lib/unix/FilesystemUtils.java
@@ -14,6 +14,7 @@
 
 package com.google.devtools.build.lib.unix;
 
+import com.google.common.annotations.VisibleForTesting;
 import com.google.common.hash.HashCode;
 import com.google.devtools.build.lib.UnixJniLoader;
 
@@ -373,6 +374,16 @@
    */
   public static native boolean remove(String path) throws IOException;
 
+  /**
+   * Native wrapper around POSIX mkfifo(3) C library call.
+   *
+   * @param path the name of the pipe to create.
+   * @param mode the mode with which to create the pipe.
+   * @throws IOException if the mkfifo failed.
+   */
+  @VisibleForTesting
+  public static native void mkfifo(String path, int mode) throws IOException;
+
   /********************************************************************
    *                                                                  *
    *                  Linux extended file attributes                  *
diff --git a/src/main/java/com/google/devtools/build/lib/vfs/AbstractFileSystem.java b/src/main/java/com/google/devtools/build/lib/vfs/AbstractFileSystem.java
index de67af8..1d0b4dc 100644
--- a/src/main/java/com/google/devtools/build/lib/vfs/AbstractFileSystem.java
+++ b/src/main/java/com/google/devtools/build/lib/vfs/AbstractFileSystem.java
@@ -36,7 +36,7 @@
   protected static final Profiler profiler = Profiler.instance();
 
   @Override
-  protected InputStream getInputStream(Path path) throws FileNotFoundException {
+  protected InputStream getInputStream(Path path) throws IOException {
     // This loop is a workaround for an apparent bug in FileInputStrean.open, which delegates
     // ultimately to JVM_Open in the Hotspot JVM.  This call is not EINTR-safe, so we must do the
     // retry here.
@@ -144,4 +144,28 @@
       }
     }
   }
+
+  @Override
+  protected boolean isFile(Path path, boolean followSymlinks) {
+    FileStatus stat = statNullable(path, followSymlinks);
+    return stat != null ? stat.isFile() : false;
+  }
+
+  @Override
+  protected boolean isSpecialFile(Path path, boolean followSymlinks) {
+    FileStatus stat = statNullable(path, followSymlinks);
+    return stat != null ? stat.isSpecialFile() : false;
+  }
+
+  @Override
+  protected boolean isSymbolicLink(Path path) {
+    FileStatus stat = statNullable(path, false);
+    return stat != null ? stat.isSymbolicLink() : false;
+  }
+
+  @Override
+  protected boolean isDirectory(Path path, boolean followSymlinks) {
+    FileStatus stat = statNullable(path, followSymlinks);
+    return stat != null ? stat.isDirectory() : false;
+  }
 }
diff --git a/src/main/java/com/google/devtools/build/lib/vfs/Dirent.java b/src/main/java/com/google/devtools/build/lib/vfs/Dirent.java
index 49d41a1..2b81c83 100644
--- a/src/main/java/com/google/devtools/build/lib/vfs/Dirent.java
+++ b/src/main/java/com/google/devtools/build/lib/vfs/Dirent.java
@@ -25,9 +25,13 @@
 
   /** Type of the directory entry */
   public enum Type {
+    // A regular file.
     FILE,
+    // A directory.
     DIRECTORY,
+    // A symlink.
     SYMLINK,
+    // Not one of the above. For example, a special file.
     UNKNOWN;
   }
 
diff --git a/src/main/java/com/google/devtools/build/lib/vfs/FileStatus.java b/src/main/java/com/google/devtools/build/lib/vfs/FileStatus.java
index 4c7c206..712f96d 100644
--- a/src/main/java/com/google/devtools/build/lib/vfs/FileStatus.java
+++ b/src/main/java/com/google/devtools/build/lib/vfs/FileStatus.java
@@ -37,8 +37,7 @@
 public interface FileStatus {
 
   /**
-   * Returns true iff this file is a regular or special file (e.g. socket,
-   * fifo or device).
+   * Returns true iff this file is a regular file or {@code isSpecial()}.
    */
   boolean isFile();
 
@@ -53,6 +52,12 @@
   boolean isSymbolicLink();
 
   /**
+   * Returns true iff this file is a special file (e.g. socket, fifo or device). {@link #getSize()}
+   * can't be trusted for such files.
+   */
+  boolean isSpecialFile();
+
+  /**
    * Returns the total size, in bytes, of this file.
    */
   long getSize() throws IOException;
diff --git a/src/main/java/com/google/devtools/build/lib/vfs/FileStatusWithDigestAdapter.java b/src/main/java/com/google/devtools/build/lib/vfs/FileStatusWithDigestAdapter.java
index ab034b0..5daeaa6 100644
--- a/src/main/java/com/google/devtools/build/lib/vfs/FileStatusWithDigestAdapter.java
+++ b/src/main/java/com/google/devtools/build/lib/vfs/FileStatusWithDigestAdapter.java
@@ -45,6 +45,11 @@
   }
 
   @Override
+  public boolean isSpecialFile() {
+    return stat.isSpecialFile();
+  }
+
+  @Override
   public boolean isDirectory() {
     return stat.isDirectory();
   }
diff --git a/src/main/java/com/google/devtools/build/lib/vfs/FileSystem.java b/src/main/java/com/google/devtools/build/lib/vfs/FileSystem.java
index 82597e3..d396139 100644
--- a/src/main/java/com/google/devtools/build/lib/vfs/FileSystem.java
+++ b/src/main/java/com/google/devtools/build/lib/vfs/FileSystem.java
@@ -362,6 +362,7 @@
       volatile Boolean isFile;
       volatile Boolean isDirectory;
       volatile Boolean isSymbolicLink;
+      volatile Boolean isSpecial;
       volatile long size = -1;
       volatile long mtime = -1;
 
@@ -386,6 +387,12 @@
       }
 
       @Override
+      public boolean isSpecialFile() {
+        if (isSpecial == null)  { isSpecial = FileSystem.this.isSpecialFile(path, followSymlinks); }
+        return isSpecial;
+      }
+
+      @Override
       public long getSize() throws IOException {
         if (size == -1) { size = getFileSize(path, followSymlinks); }
         return size;
@@ -453,6 +460,12 @@
   protected abstract boolean isFile(Path path, boolean followSymlinks);
 
   /**
+   * Returns true iff {@code path} denotes a special file.
+   * See {@link Path#isSpecialFile(Symlinks)} for specification.
+   */
+  protected abstract boolean isSpecialFile(Path path, boolean followSymlinks);
+
+  /**
    * Creates a symbolic link. See {@link Path#createSymbolicLink(Path)} for
    * specification.
    *
@@ -505,6 +518,8 @@
   protected static Dirent.Type direntFromStat(FileStatus stat) {
     if (stat == null) {
       return Type.UNKNOWN;
+    } else if (stat.isSpecialFile()) {
+        return Type.UNKNOWN;
     } else if (stat.isFile()) {
       return Type.FILE;
     } else if (stat.isDirectory()) {
diff --git a/src/main/java/com/google/devtools/build/lib/vfs/JavaIoFileSystem.java b/src/main/java/com/google/devtools/build/lib/vfs/JavaIoFileSystem.java
index 3ae4e96..9d9f16e 100644
--- a/src/main/java/com/google/devtools/build/lib/vfs/JavaIoFileSystem.java
+++ b/src/main/java/com/google/devtools/build/lib/vfs/JavaIoFileSystem.java
@@ -94,34 +94,6 @@
   }
 
   @Override
-  protected boolean isDirectory(Path path, boolean followSymlinks) {
-    File file = getIoFile(path);
-    long startTime = Profiler.nanoTimeMaybe();
-    try {
-      if (!followSymlinks && fileIsSymbolicLink(file)) {
-        return false;
-      }
-      return file.isDirectory();
-    } finally {
-      profiler.logSimpleTask(startTime, ProfilerTask.VFS_STAT, path.toString());
-    }
-  }
-
-  @Override
-  protected boolean isFile(Path path, boolean followSymlinks) {
-    File file = getIoFile(path);
-    long startTime = Profiler.nanoTimeMaybe();
-    try {
-      if (!followSymlinks && fileIsSymbolicLink(file)) {
-        return false;
-      }
-      return file.isFile();
-    } finally {
-      profiler.logSimpleTask(startTime, ProfilerTask.VFS_STAT, path.toString());
-    }
-  }
-
-  @Override
   protected boolean isReadable(Path path) throws IOException {
     File file = getIoFile(path);
     long startTime = Profiler.nanoTimeMaybe();
@@ -365,17 +337,6 @@
     }
   }
 
-  @Override
-  protected boolean isSymbolicLink(Path path) {
-    File file = getIoFile(path);
-    long startTime = Profiler.nanoTimeMaybe();
-    try {
-      return fileIsSymbolicLink(file);
-    } finally {
-      profiler.logSimpleTask(startTime, ProfilerTask.VFS_STAT, file.getPath());
-    }
-  }
-
   private boolean fileIsSymbolicLink(File file) {
     return Files.isSymbolicLink(file.toPath());
   }
@@ -428,7 +389,12 @@
     FileStatus status =  new FileStatus() {
       @Override
       public boolean isFile() {
-        return attributes.isRegularFile();
+        return attributes.isRegularFile() || isSpecialFile();
+      }
+
+      @Override
+      public boolean isSpecialFile() {
+        return attributes.isOther();
       }
 
       @Override
diff --git a/src/main/java/com/google/devtools/build/lib/vfs/Path.java b/src/main/java/com/google/devtools/build/lib/vfs/Path.java
index aac68ee..c7c5471 100644
--- a/src/main/java/com/google/devtools/build/lib/vfs/Path.java
+++ b/src/main/java/com/google/devtools/build/lib/vfs/Path.java
@@ -533,6 +533,25 @@
   }
 
   /**
+   * Returns true iff this path denotes an existing special file (e.g. fifo).
+   * Follows symbolic links.
+   */
+  public boolean isSpecialFile() {
+    return fileSystem.isSpecialFile(this, true);
+  }
+
+  /**
+   * Returns true iff this path denotes an existing special file (e.g. fifo).
+   *
+   * @param followSymlinks if {@link Symlinks#FOLLOW}, and this path denotes a
+   *        symbolic link, the link is dereferenced until a path other than a
+   *        symbolic link is found.
+   */
+  public boolean isSpecialFile(Symlinks followSymlinks) {
+    return fileSystem.isSpecialFile(this, followSymlinks.toBoolean());
+  }
+
+  /**
    * Returns true iff this path denotes an existing symbolic link. Does not
    * follow symbolic links.
    */
@@ -814,7 +833,7 @@
    * Returns the size in bytes of the file denoted by the current path,
    * following symbolic links.
    *
-   * <p>The size of directory or special file is undefined.
+   * <p>The size of a directory or special file is undefined and should not be used.
    *
    * @throws FileNotFoundException if the file denoted by the current path does
    *         not exist
diff --git a/src/main/java/com/google/devtools/build/lib/vfs/ReadonlyFileSystem.java b/src/main/java/com/google/devtools/build/lib/vfs/ReadonlyFileSystem.java
index 821e470..e3cf56f 100644
--- a/src/main/java/com/google/devtools/build/lib/vfs/ReadonlyFileSystem.java
+++ b/src/main/java/com/google/devtools/build/lib/vfs/ReadonlyFileSystem.java
@@ -33,7 +33,7 @@
  * </ul>
  * The above calls will always result in an {@link IOException}.
  */
-public abstract class ReadonlyFileSystem extends FileSystem {
+public abstract class ReadonlyFileSystem extends AbstractFileSystem {
 
   protected ReadonlyFileSystem() {
   }
diff --git a/src/main/java/com/google/devtools/build/lib/vfs/UnionFileSystem.java b/src/main/java/com/google/devtools/build/lib/vfs/UnionFileSystem.java
index b774543..a1ef71f 100644
--- a/src/main/java/com/google/devtools/build/lib/vfs/UnionFileSystem.java
+++ b/src/main/java/com/google/devtools/build/lib/vfs/UnionFileSystem.java
@@ -248,6 +248,12 @@
   }
 
   @Override
+  protected boolean isSpecialFile(Path path, boolean followSymlinks) {
+    FileSystem delegate = getDelegate(path);
+    return delegate.isSpecialFile(adjustPath(path, delegate), followSymlinks);
+  }
+
+  @Override
   protected void createSymbolicLink(Path linkPath, PathFragment targetFragment) throws IOException {
     checkModifiable();
     if (!supportsSymbolicLinks()) {
diff --git a/src/main/java/com/google/devtools/build/lib/vfs/UnixFileSystem.java b/src/main/java/com/google/devtools/build/lib/vfs/UnixFileSystem.java
index d730cab..29a8636 100644
--- a/src/main/java/com/google/devtools/build/lib/vfs/UnixFileSystem.java
+++ b/src/main/java/com/google/devtools/build/lib/vfs/UnixFileSystem.java
@@ -63,6 +63,9 @@
     public boolean isSymbolicLink() { return status.isSymbolicLink(); }
 
     @Override
+    public boolean isSpecialFile() { return isFile() && !status.isRegularFile(); }
+
+    @Override
     public long getSize() { return status.getSize(); }
 
     @Override
@@ -225,20 +228,6 @@
   }
 
   @Override
-  protected boolean isDirectory(Path path, boolean followSymlinks) {
-    FileStatus stat = statNullable(path, followSymlinks);
-    return stat != null && stat.isDirectory();
-  }
-
-  @Override
-  protected boolean isFile(Path path, boolean followSymlinks) {
-    // Note, FileStatus.isFile means *regular* file whereas Path.isFile may
-    // mean special file too, so we don't return FileStatus.isFile here.
-    FileStatus status = statNullable(path, followSymlinks);
-    return status != null && !(status.isSymbolicLink() || status.isDirectory());
-  }
-
-  @Override
   protected boolean isReadable(Path path) throws IOException {
     return (statInternal(path, true).getPermissions() & 0400) != 0;
   }
@@ -374,12 +363,6 @@
   }
 
   @Override
-  protected boolean isSymbolicLink(Path path) {
-    FileStatus stat = statNullable(path, false);
-    return stat != null && stat.isSymbolicLink();
-  }
-
-  @Override
   protected void setLastModifiedTime(Path path, long newTime) throws IOException {
     synchronized (path) {
       if (newTime == -1L) { // "now"
diff --git a/src/main/java/com/google/devtools/build/lib/vfs/ZipFileSystem.java b/src/main/java/com/google/devtools/build/lib/vfs/ZipFileSystem.java
index 78961c8..393110d 100644
--- a/src/main/java/com/google/devtools/build/lib/vfs/ZipFileSystem.java
+++ b/src/main/java/com/google/devtools/build/lib/vfs/ZipFileSystem.java
@@ -198,6 +198,11 @@
   }
 
   @Override
+  protected boolean isSpecialFile(Path path, boolean followSymlinks) {
+    return false;
+  }
+
+  @Override
   protected boolean isReadable(Path path) throws IOException {
     zipEntryNonNull(path);
     return true;
diff --git a/src/main/java/com/google/devtools/build/lib/vfs/inmemoryfs/FileInfo.java b/src/main/java/com/google/devtools/build/lib/vfs/inmemoryfs/FileInfo.java
index 2c578d1..3c5d55c 100644
--- a/src/main/java/com/google/devtools/build/lib/vfs/inmemoryfs/FileInfo.java
+++ b/src/main/java/com/google/devtools/build/lib/vfs/inmemoryfs/FileInfo.java
@@ -43,6 +43,11 @@
     return true;
   }
 
+  @Override
+  public boolean isSpecialFile() {
+    return false;
+  }
+
   protected abstract byte[] readContent() throws IOException;
 
   protected abstract OutputStream getOutputStream(boolean append) throws IOException;
diff --git a/src/main/java/com/google/devtools/build/lib/vfs/inmemoryfs/InMemoryContentInfo.java b/src/main/java/com/google/devtools/build/lib/vfs/inmemoryfs/InMemoryContentInfo.java
index 6a721f1..d0044cc 100644
--- a/src/main/java/com/google/devtools/build/lib/vfs/inmemoryfs/InMemoryContentInfo.java
+++ b/src/main/java/com/google/devtools/build/lib/vfs/inmemoryfs/InMemoryContentInfo.java
@@ -80,12 +80,18 @@
   public abstract boolean isSymbolicLink();
 
   /**
-   * Returns true if the current object is a regular file.
+   * Returns true if the current object is a regular or special file.
    */
   @Override
   public abstract boolean isFile();
 
   /**
+   * Returns true if the current object is a special file.
+   */
+  @Override
+  public abstract boolean isSpecialFile();
+
+  /**
    * Returns the size of the entity denoted by the current object. For files,
    * this is the length in bytes, for directories the number of children. The
    * size of links is unspecified.
diff --git a/src/main/java/com/google/devtools/build/lib/vfs/inmemoryfs/InMemoryDirectoryInfo.java b/src/main/java/com/google/devtools/build/lib/vfs/inmemoryfs/InMemoryDirectoryInfo.java
index d7a6cf5..66d7523 100644
--- a/src/main/java/com/google/devtools/build/lib/vfs/inmemoryfs/InMemoryDirectoryInfo.java
+++ b/src/main/java/com/google/devtools/build/lib/vfs/inmemoryfs/InMemoryDirectoryInfo.java
@@ -96,6 +96,11 @@
     return false;
   }
 
+  @Override
+  public boolean isSpecialFile() {
+    return false;
+  }
+
   /**
    * In the InMemory hierarchy, the getSize on a directory always returns the
    * number of children in the directory.
diff --git a/src/main/java/com/google/devtools/build/lib/vfs/inmemoryfs/InMemoryFileSystem.java b/src/main/java/com/google/devtools/build/lib/vfs/inmemoryfs/InMemoryFileSystem.java
index 2138af1..fe3731e 100644
--- a/src/main/java/com/google/devtools/build/lib/vfs/inmemoryfs/InMemoryFileSystem.java
+++ b/src/main/java/com/google/devtools/build/lib/vfs/inmemoryfs/InMemoryFileSystem.java
@@ -537,6 +537,15 @@
   }
 
   @Override
+  protected boolean isSpecialFile(Path path, boolean followSymlinks) {
+    try {
+      return stat(path, followSymlinks).isSpecialFile();
+    } catch (IOException e) {
+      return false;
+    }
+  }
+
+  @Override
   protected boolean isSymbolicLink(Path path) {
     try {
       return stat(path, false).isSymbolicLink();
diff --git a/src/main/java/com/google/devtools/build/lib/vfs/inmemoryfs/InMemoryLinkInfo.java b/src/main/java/com/google/devtools/build/lib/vfs/inmemoryfs/InMemoryLinkInfo.java
index 5d55fb5..5620492 100644
--- a/src/main/java/com/google/devtools/build/lib/vfs/inmemoryfs/InMemoryLinkInfo.java
+++ b/src/main/java/com/google/devtools/build/lib/vfs/inmemoryfs/InMemoryLinkInfo.java
@@ -50,6 +50,11 @@
   }
 
   @Override
+  public boolean isSpecialFile() {
+    return false;
+  }
+
+  @Override
   public long getSize() {
     return linkContent.toString().length();
   }
diff --git a/src/main/java/com/google/devtools/build/lib/vfs/inmemoryfs/OutOfScopeFileStatus.java b/src/main/java/com/google/devtools/build/lib/vfs/inmemoryfs/OutOfScopeFileStatus.java
index 6a71c5b..287d97d 100644
--- a/src/main/java/com/google/devtools/build/lib/vfs/inmemoryfs/OutOfScopeFileStatus.java
+++ b/src/main/java/com/google/devtools/build/lib/vfs/inmemoryfs/OutOfScopeFileStatus.java
@@ -51,6 +51,7 @@
   @Override public boolean isDirectory() { throw failure(); }
   @Override public boolean isSymbolicLink() { throw failure(); }
   @Override public boolean isFile() { throw failure(); }
+  @Override public boolean isSpecialFile() { throw failure(); }
   @Override public long getSize() { throw failure(); }
   @Override protected void markModificationTime() { throw failure(); }
   @Override public synchronized long getLastModifiedTime() { throw failure(); }
diff --git a/src/main/native/unix_jni.cc b/src/main/native/unix_jni.cc
index bc6a14e..eed3092 100644
--- a/src/main/native/unix_jni.cc
+++ b/src/main/native/unix_jni.cc
@@ -726,6 +726,23 @@
   return ::delete_common(env, path, ::remove, ::remove_err);
 }
 
+/*
+ * Class:     com.google.devtools.build.lib.unix.FilesystemUtils
+ * Method:    mkfifo
+ * Signature: (Ljava/lang/String;I)V
+ * Throws:    java.io.IOException
+ */
+extern "C" JNIEXPORT void JNICALL
+Java_com_google_devtools_build_lib_unix_FilesystemUtils_mkfifo(JNIEnv *env,
+                                                   jclass clazz,
+                                                   jstring path,
+                                                   jint mode) {
+  const char *path_chars = GetStringLatin1Chars(env, path);
+  if (mkfifo(path_chars, mode) == -1) {
+    ::PostFileException(env, errno, path_chars);
+  }
+  ReleaseStringLatin1Chars(path_chars);
+}
 
 ////////////////////////////////////////////////////////////////////////
 // Linux extended file attributes
diff --git a/src/test/java/com/google/devtools/build/lib/vfs/ScopeEscapableFileSystemTest.java b/src/test/java/com/google/devtools/build/lib/vfs/ScopeEscapableFileSystemTest.java
index 719ffde..87c4cce 100644
--- a/src/test/java/com/google/devtools/build/lib/vfs/ScopeEscapableFileSystemTest.java
+++ b/src/test/java/com/google/devtools/build/lib/vfs/ScopeEscapableFileSystemTest.java
@@ -85,6 +85,7 @@
     @Override protected boolean isWritable(Path path) { throw re(); }
     @Override protected boolean isDirectory(Path path, boolean followSymlinks) { throw re(); }
     @Override protected boolean isFile(Path path, boolean followSymlinks) { throw re(); }
+    @Override protected boolean isSpecialFile(Path path, boolean followSymlinks) { throw re(); }
     @Override protected boolean isExecutable(Path path) { throw re(); }
     @Override protected boolean exists(Path path, boolean followSymlinks) {throw re(); }
     @Override protected boolean isSymbolicLink(Path path) { throw re(); }
diff --git a/src/test/java/com/google/devtools/build/lib/vfs/UnixFileSystemTest.java b/src/test/java/com/google/devtools/build/lib/vfs/UnixFileSystemTest.java
index cb5685e..40ace2b 100644
--- a/src/test/java/com/google/devtools/build/lib/vfs/UnixFileSystemTest.java
+++ b/src/test/java/com/google/devtools/build/lib/vfs/UnixFileSystemTest.java
@@ -15,8 +15,10 @@
 
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.fail;
 
+import com.google.devtools.build.lib.unix.FilesystemUtils;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.JUnit4;
@@ -60,4 +62,20 @@
       // Expected.
     }
   }
+
+  @Test
+  public void testIsSpecialFile() throws Exception {
+    Path regular = absolutize("regular");
+    Path fifo = absolutize("fifo");
+    FileSystemUtils.createEmptyFile(regular);
+    FilesystemUtils.mkfifo(fifo.toString(), 0777);
+    assertTrue(regular.isFile());
+    assertFalse(regular.isSpecialFile());
+    assertTrue(regular.stat().isFile());
+    assertFalse(regular.stat().isSpecialFile());
+    assertTrue(fifo.isFile());
+    assertTrue(fifo.isSpecialFile());
+    assertTrue(fifo.stat().isFile());
+    assertTrue(fifo.stat().isSpecialFile());
+  }
 }