diff --git a/scripts/test_dependency_invalidation.sh b/scripts/test_dependency_invalidation.sh index 81961b6..47f5ae5 100755 --- a/scripts/test_dependency_invalidation.sh +++ b/scripts/test_dependency_invalidation.sh @@ -73,6 +73,16 @@ if [[ "$(http_marker)" != *"dependency-marker-a"* ]]; then exit 1 fi +# Compiler source canonicalization must retain symlink-target identity while +# avoiding realpath's repeated per-segment probes on ordinary source graphs. +printf '%s\n' 'String symlink_dependency_marker() { return("symlink-marker-a"); }' >"$source_dir/symlink-target-a.uce" +printf '%s\n' 'String symlink_dependency_marker() { return("symlink-marker-b"); }' >"$source_dir/symlink-target-b.uce" +ln -s "symlink-target-a.uce" "$source_dir/symlink-child.uce" +printf '%s\n' '#load "symlink-child.uce"' 'CLI(Request& context) { print(symlink_dependency_marker()); }' >"$source_dir/symlink-parent.uce" +assert_marker symlink-parent symlink-marker-a +ln -sfn "symlink-target-b.uce" "$source_dir/symlink-child.uce" +assert_marker symlink-parent symlink-marker-b + # HTTP entry units can resolve route/components dynamically. A changed dynamic # component must enter the demand-priority queue just like a changed entry unit; # otherwise a common dependency rebuild can leave the requested page stale for diff --git a/src/lib/compiler.cpp b/src/lib/compiler.cpp index 2b99d10..f5e2e38 100644 --- a/src/lib/compiler.cpp +++ b/src/lib/compiler.cpp @@ -158,6 +158,23 @@ bool compiler_source_readable(String file_name, const struct stat& info, int* re return(error == 0); } +String compiler_source_path_real(String file_name) +{ + int fd = open(file_name.c_str(), O_PATH | O_CLOEXEC); + if(fd < 0) + return(path_real(file_name)); + String proc_path = "/proc/self/fd/" + std::to_string(fd); + char resolved[PATH_MAX]; + ssize_t length = readlink(proc_path.c_str(), resolved, sizeof(resolved)); + close(fd); + if(length <= 0 || length >= (ssize_t)sizeof(resolved)) + return(path_real(file_name)); + String result(resolved, (size_t)length); + if(str_ends_with(result, " (deleted)")) + return(path_real(file_name)); + return(result); +} + UnitSourceSignatureEntry compiler_unit_source_entry(String file_name, bool allow_recent_stat) { auto checked_at = std::chrono::steady_clock::now(); @@ -204,7 +221,7 @@ void compiler_append_unit_source_signature(String file_name, std::set& v bool recent = allow_recent_stat && compiler_recent_unit_source_entry(normalized, entry); if(!recent) { - normalized = path_real(file_name); + normalized = compiler_source_path_real(file_name); if(normalized == "") normalized = file_name; }