safeResolvePath function
Safely resolves a file path, handling symlinks and errors gracefully.
Error handling strategy:
- If the file doesn't exist, returns the original path (allows for file creation)
- If symlink resolution fails (broken symlink, permission denied, circular links), returns the original path and marks it as not a symlink
- This ensures operations can continue with the original path rather than failing
Implementation
SafeResolvePathResult safeResolvePath(FsOperations fs, String filePath) {
// Block UNC paths before any filesystem access to prevent network
// requests (DNS/SMB) during validation on Windows
if (filePath.startsWith('//') || filePath.startsWith('\\\\')) {
return SafeResolvePathResult(
resolvedPath: filePath,
isSymlink: false,
isCanonical: false,
);
}
try {
// Check for special file types (FIFOs, sockets, devices) before calling realpathSync.
// realpathSync can block on FIFOs waiting for a writer, causing hangs.
// If the file doesn't exist, lstatSync throws which the catch
// below handles by returning the original path (allows file creation).
final stats = fs.lstatSync(filePath);
if (stats.isFIFO() ||
stats.isSocket() ||
stats.isCharacterDevice() ||
stats.isBlockDevice()) {
return SafeResolvePathResult(
resolvedPath: filePath,
isSymlink: false,
isCanonical: false,
);
}
final resolvedPath = fs.realpathSync(filePath);
return SafeResolvePathResult(
resolvedPath: resolvedPath,
isSymlink: resolvedPath != filePath,
// realpathSync returned: resolvedPath is canonical (all symlinks in
// all path components resolved). Callers can skip further symlink
// resolution on this path.
isCanonical: true,
);
} catch (_) {
// If lstat/realpath fails for any reason (ENOENT, broken symlink,
// EACCES, ELOOP, etc.), return the original path to allow operations
// to proceed
return SafeResolvePathResult(
resolvedPath: filePath,
isSymlink: false,
isCanonical: false,
);
}
}