Skip to content

Commit f0a4f59

Browse files
committed
Use stat Instead of lstat
Using `lstat` here will do the wrong thing when the input path is a symlink since it's going to give us mod time info on the symlink instead of the file it references. Since mod time updates are intentionally not propagated across links, this has the potential to cause us to miscompile if a file's contents change on disk but the symlink passed as input hasn't actually changed. It also has the potential to defeat the incremental build when build systems that generate symlinks on every build interact with the driver. On Darwin (and, technically, other UNIX-likes) the fix is simple: use stat instead of lstat which will resolve symlinks for us. On Windows the answer is... complicated. Instead, on the fallback path, resolve symlinks before retrieving the modification time of the file. rdar://78828871
1 parent 14fedb3 commit f0a4f59

File tree

1 file changed

+17
-2
lines changed

1 file changed

+17
-2
lines changed

Sources/SwiftDriver/Utilities/VirtualPath.swift

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -715,18 +715,33 @@ extension TSCBasic.FileSystem {
715715
try resolvingVirtualPath(path, apply: exists)
716716
}
717717

718+
/// Retrieves the last modification time of the file referenced at the given path.
719+
///
720+
/// If the given file path references a symbolic link, the modification time for the *linked file*
721+
/// - not the symlink itself - is returned.
722+
///
723+
/// - Parameter file: The path to a file.
724+
/// - Throws: `SystemError` if the underlying `stat` operation fails.
725+
/// - Returns: A `Date` value containing the last modification time.
718726
public func lastModificationTime(for file: VirtualPath) throws -> Date {
719727
try resolvingVirtualPath(file) { path in
720728
#if os(macOS)
721729
var s = Darwin.stat()
722-
let err = lstat(path.pathString, &s)
730+
let err = stat(path.pathString, &s)
723731
guard err == 0 else {
724732
throw SystemError.stat(errno, path.pathString)
725733
}
726734
let ti = (TimeInterval(s.st_mtimespec.tv_sec) - kCFAbsoluteTimeIntervalSince1970) + (1.0e-9 * TimeInterval(s.st_mtimespec.tv_nsec))
727735
return Date(timeIntervalSinceReferenceDate: ti)
728736
#else
729-
return try localFileSystem.getFileInfo(file).modTime
737+
// `getFileInfo` is going to ask Foundation to stat this path, and
738+
// Foundation is always going to use `lstat` to do so. This is going to
739+
// do the wrong thing for symbolic links, for which we always want to
740+
// retrieve the mod time of the underlying file. This makes build systems
741+
// that regenerate lots of symlinks but do not otherwise alter the
742+
// contents of files - like Bazel - quite happy.
743+
let path = resolveSymlinks(path)
744+
return try localFileSystem.getFileInfo(path).modTime
730745
#endif
731746
}
732747
}

0 commit comments

Comments
 (0)