|
| 1 | +const builtin = @import("builtin"); |
| 2 | +const std = @import("std"); |
| 3 | + |
| 4 | +const root = @import("../root.zig"); |
| 5 | + |
| 6 | +const os_char = root.util.os_char; |
| 7 | + |
| 8 | +pub const FileIdentity = switch (builtin.os.tag) { |
| 9 | + .linux => struct { |
| 10 | + dev_major: u32, |
| 11 | + dev_minor: u32, |
| 12 | + ino: u64, |
| 13 | + }, |
| 14 | + .macos => struct { |
| 15 | + dev: i32, |
| 16 | + ino: u64, |
| 17 | + }, |
| 18 | + .windows => FILE_ID_INFO, |
| 19 | + else => @compileError("Unsupported OS"), |
| 20 | +}; |
| 21 | + |
| 22 | +pub fn are_same(a: FileIdentity, b: FileIdentity) bool { |
| 23 | + return switch (builtin.os.tag) { |
| 24 | + .linux => a.dev_major == b.dev_major and a.dev_minor == b.dev_minor and a.ino == b.ino, |
| 25 | + .macos => a.dev == b.dev and a.ino == b.ino, |
| 26 | + .windows => a.volume_serial_number == b.volume_serial_number and std.mem.eql(u8, &a.file_id.identifier, &b.file_id.identifier), |
| 27 | + else => @compileError("Unsupported OS"), |
| 28 | + }; |
| 29 | +} |
| 30 | + |
| 31 | +const Handle = if (builtin.os.tag == .windows) std.os.windows.HANDLE else i32; |
| 32 | + |
| 33 | +/// If path is empty, `dir` itself will be inspected and can be any kind of file handle. |
| 34 | +pub fn getFileIdentity(dir: ?Handle, path: [:0]const os_char) !FileIdentity { |
| 35 | + switch (builtin.os.tag) { |
| 36 | + .linux => { |
| 37 | + var buf: std.os.linux.Statx = undefined; |
| 38 | + if (std.os.linux.statx( |
| 39 | + dir orelse std.os.linux.AT.FDCWD, |
| 40 | + path, |
| 41 | + if (path.len == 0) std.os.linux.AT.EMPTY_PATH else 0, |
| 42 | + std.os.linux.STATX_INO, |
| 43 | + &buf, |
| 44 | + ) != 0) { |
| 45 | + return switch (std.posix.errno(std.c._errno().*)) { |
| 46 | + .ACCES => error.AccessDenied, |
| 47 | + .BADF => unreachable, |
| 48 | + .FAULT => unreachable, |
| 49 | + .INVAL => unreachable, |
| 50 | + .LOOP => error.SymLinkLoop, |
| 51 | + .NAMETOOLONG => error.NameTooLong, |
| 52 | + .NOENT => error.FileNotFound, |
| 53 | + .NOMEM => error.SystemResources, |
| 54 | + .NOTDIR => error.NotDir, |
| 55 | + else => |err| std.posix.unexpectedErrno(err), |
| 56 | + }; |
| 57 | + } |
| 58 | + return .{ .dev_major = buf.dev_major, .dev_minor = buf.dev_minor, .ino = buf.ino }; |
| 59 | + }, |
| 60 | + .macos => { |
| 61 | + if (dir != null and path.len != 0) { |
| 62 | + return error.Unsupported; |
| 63 | + } |
| 64 | + var buf: std.c.Stat = undefined; |
| 65 | + if ((if (dir) |fd| std.c.fstat(fd, &buf) else std.c.stat(path, &buf)) != 0) { |
| 66 | + return switch (std.posix.errno(std.c._errno().*)) { |
| 67 | + .ACCES => error.AccessDenied, |
| 68 | + .IO => error.FileSystem, |
| 69 | + .BADF => unreachable, |
| 70 | + .FAULT => unreachable, |
| 71 | + .LOOP => error.SymLinkLoop, |
| 72 | + .NAMETOOLONG => error.NameTooLong, |
| 73 | + .NOENT => error.FileNotFound, |
| 74 | + .NOTDIR => error.NotDir, |
| 75 | + .OVERFLOW => error.FileTooBig, |
| 76 | + else => |err| std.posix.unexpectedErrno(err), |
| 77 | + }; |
| 78 | + } |
| 79 | + return .{ .dev = buf.dev, .ino = buf.ino }; |
| 80 | + }, |
| 81 | + .windows => { |
| 82 | + if (dir != null and path.len != 0) { |
| 83 | + return error.Unsupported; |
| 84 | + } |
| 85 | + |
| 86 | + const handle = try std.os.windows.OpenFile(path, .{ |
| 87 | + // Not sure if this is sufficient. Might need GENERIC_READ. |
| 88 | + .access_mask = 0, |
| 89 | + .creation = std.os.windows.OPEN_EXISTING, |
| 90 | + }); |
| 91 | + defer std.os.windows.CloseHandle(handle); |
| 92 | + |
| 93 | + return getFileInfo(handle, .FileIdInfo); |
| 94 | + }, |
| 95 | + else => @compileError("Unsupported OS"), |
| 96 | + } |
| 97 | +} |
| 98 | + |
| 99 | +extern "kernel32" fn GetFileInformationByHandleEx( |
| 100 | + in_hFile: std.os.windows.HANDLE, |
| 101 | + in_FileInformationClass: std.os.windows.FILE_INFO_BY_HANDLE_CLASS, |
| 102 | + out_lpFileInformation: *anyopaque, |
| 103 | + in_dwBufferSize: std.os.windows.DWORD, |
| 104 | +) callconv(.winapi) std.os.windows.BOOL; |
| 105 | + |
| 106 | +const FILE_ID_INFO = extern struct { |
| 107 | + volume_serial_number: u64, |
| 108 | + file_id: extern struct { |
| 109 | + identifier: [16]u8, |
| 110 | + }, |
| 111 | +}; |
| 112 | + |
| 113 | +fn GetFileInfo(comptime class: std.os.windows.FILE_INFO_BY_HANDLE_CLASS) type { |
| 114 | + return switch (class) { |
| 115 | + .FileBasicInfo => std.os.windows.FILE_BASIC_INFORMATION, |
| 116 | + .FileStandardInfo => std.os.windows.FILE_STANDARD_INFORMATION, |
| 117 | + .FileNameInfo => std.os.windows.FILE_NAME_INFO, |
| 118 | + .FileRenameInfo => std.os.windows.FILE_RENAME_INFORMATION_EX, |
| 119 | + .FileDispositionInfo => std.os.windows.FILE_DISPOSITION_INFORMATION_EX, |
| 120 | + .FileEndOfFileInfo => std.os.windows.FILE_END_OF_FILE_INFORMATION, |
| 121 | + .FileAttributeTagInfo => std.os.windows.FILE_ATTRIBUTE_TAG_INFO, |
| 122 | + .FileAlignmentInfo => std.os.windows.FILE_ALIGNMENT_INFORMATION, |
| 123 | + .FileIdInfo => FILE_ID_INFO, |
| 124 | + else => @compileError("Type of class " ++ @tagName(class) ++ " is unknown"), |
| 125 | + }; |
| 126 | +} |
| 127 | + |
| 128 | +fn getFileInfo(file: std.os.windows.HANDLE, comptime class: std.os.windows.FILE_INFO_BY_HANDLE_CLASS) error{Unexpected}!GetFileInfo(class) { |
| 129 | + return switch (class) { |
| 130 | + inline else => |c| { |
| 131 | + var buf: GetFileInfo(c) = undefined; |
| 132 | + if (GetFileInformationByHandleEx(file, class, &buf, @sizeOf(@TypeOf(buf))) == 0) { |
| 133 | + return std.os.windows.unexpectedError(std.os.windows.GetLastError()); |
| 134 | + } |
| 135 | + return buf; |
| 136 | + }, |
| 137 | + }; |
| 138 | +} |
0 commit comments