stat_windows.odin 7.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299
  1. package os
  2. import "core:time"
  3. import win32 "core:sys/windows"
  4. @(private)
  5. full_path_from_name :: proc(name: string, allocator := context.allocator) -> (path: string, err: Errno) {
  6. context.allocator = allocator
  7. name := name
  8. if name == "" {
  9. name = "."
  10. }
  11. p := win32.utf8_to_utf16(name, context.temp_allocator)
  12. buf := make([dynamic]u16, 100)
  13. defer delete(buf)
  14. for {
  15. n := win32.GetFullPathNameW(raw_data(p), u32(len(buf)), raw_data(buf), nil)
  16. if n == 0 {
  17. return "", Errno(win32.GetLastError())
  18. }
  19. if n <= u32(len(buf)) {
  20. return win32.utf16_to_utf8(buf[:n], allocator) or_else "", ERROR_NONE
  21. }
  22. resize(&buf, len(buf)*2)
  23. }
  24. return
  25. }
  26. @(private)
  27. _stat :: proc(name: string, create_file_attributes: u32, allocator := context.allocator) -> (fi: File_Info, e: Errno) {
  28. if len(name) == 0 {
  29. return {}, ERROR_PATH_NOT_FOUND
  30. }
  31. context.allocator = allocator
  32. wname := win32.utf8_to_wstring(fix_long_path(name), context.temp_allocator)
  33. fa: win32.WIN32_FILE_ATTRIBUTE_DATA
  34. ok := win32.GetFileAttributesExW(wname, win32.GetFileExInfoStandard, &fa)
  35. if ok && fa.dwFileAttributes & win32.FILE_ATTRIBUTE_REPARSE_POINT == 0 {
  36. // Not a symlink
  37. return file_info_from_win32_file_attribute_data(&fa, name)
  38. }
  39. err := 0 if ok else win32.GetLastError()
  40. if err == win32.ERROR_SHARING_VIOLATION {
  41. fd: win32.WIN32_FIND_DATAW
  42. sh := win32.FindFirstFileW(wname, &fd)
  43. if sh == win32.INVALID_HANDLE_VALUE {
  44. e = Errno(win32.GetLastError())
  45. return
  46. }
  47. win32.FindClose(sh)
  48. return file_info_from_win32_find_data(&fd, name)
  49. }
  50. h := win32.CreateFileW(wname, 0, 0, nil, win32.OPEN_EXISTING, create_file_attributes, nil)
  51. if h == win32.INVALID_HANDLE_VALUE {
  52. e = Errno(win32.GetLastError())
  53. return
  54. }
  55. defer win32.CloseHandle(h)
  56. return file_info_from_get_file_information_by_handle(name, h)
  57. }
  58. lstat :: proc(name: string, allocator := context.allocator) -> (File_Info, Errno) {
  59. attrs := win32.FILE_FLAG_BACKUP_SEMANTICS
  60. attrs |= win32.FILE_FLAG_OPEN_REPARSE_POINT
  61. return _stat(name, attrs, allocator)
  62. }
  63. stat :: proc(name: string, allocator := context.allocator) -> (File_Info, Errno) {
  64. attrs := win32.FILE_FLAG_BACKUP_SEMANTICS
  65. return _stat(name, attrs, allocator)
  66. }
  67. fstat :: proc(fd: Handle, allocator := context.allocator) -> (fi: File_Info, errno: Errno) {
  68. if fd == 0 {
  69. return {}, ERROR_INVALID_HANDLE
  70. }
  71. context.allocator = allocator
  72. path, err := cleanpath_from_handle(fd)
  73. if err != ERROR_NONE {
  74. return {}, err
  75. }
  76. h := win32.HANDLE(fd)
  77. switch win32.GetFileType(h) {
  78. case win32.FILE_TYPE_PIPE, win32.FILE_TYPE_CHAR:
  79. fi.name = basename(path)
  80. fi.mode |= file_type_mode(h)
  81. errno = ERROR_NONE
  82. case:
  83. fi, errno = file_info_from_get_file_information_by_handle(path, h)
  84. }
  85. fi.fullpath = path
  86. return
  87. }
  88. @(private)
  89. cleanpath_strip_prefix :: proc(buf: []u16) -> []u16 {
  90. buf := buf
  91. N := 0
  92. for c, i in buf {
  93. if c == 0 { break }
  94. N = i+1
  95. }
  96. buf = buf[:N]
  97. if len(buf) >= 4 && buf[0] == '\\' && buf[1] == '\\' && buf[2] == '?' && buf[3] == '\\' {
  98. buf = buf[4:]
  99. /*
  100. NOTE(Jeroen): Properly handle UNC paths.
  101. We need to turn `\\?\UNC\synology.local` into `\\synology.local`.
  102. */
  103. if len(buf) >= 3 && buf[0] == 'U' && buf[1] == 'N' && buf[2] == 'C' {
  104. buf = buf[2:]
  105. buf[0] = '\\'
  106. }
  107. }
  108. return buf
  109. }
  110. @(private)
  111. cleanpath_from_handle :: proc(fd: Handle) -> (string, Errno) {
  112. buf, err := cleanpath_from_handle_u16(fd)
  113. if err != 0 {
  114. return "", err
  115. }
  116. return win32.utf16_to_utf8(buf, context.allocator) or_else "", err
  117. }
  118. @(private)
  119. cleanpath_from_handle_u16 :: proc(fd: Handle) -> ([]u16, Errno) {
  120. if fd == 0 {
  121. return nil, ERROR_INVALID_HANDLE
  122. }
  123. h := win32.HANDLE(fd)
  124. n := win32.GetFinalPathNameByHandleW(h, nil, 0, 0)
  125. if n == 0 {
  126. return nil, Errno(win32.GetLastError())
  127. }
  128. buf := make([]u16, max(n, win32.DWORD(260))+1, context.temp_allocator)
  129. buf_len := win32.GetFinalPathNameByHandleW(h, raw_data(buf), n, 0)
  130. return buf[:buf_len], ERROR_NONE
  131. }
  132. @(private)
  133. cleanpath_from_buf :: proc(buf: []u16) -> string {
  134. buf := buf
  135. buf = cleanpath_strip_prefix(buf)
  136. return win32.utf16_to_utf8(buf, context.allocator) or_else ""
  137. }
  138. @(private)
  139. basename :: proc(name: string) -> (base: string) {
  140. name := name
  141. if len(name) > 3 && name[:3] == `\\?` {
  142. name = name[3:]
  143. }
  144. if len(name) == 2 && name[1] == ':' {
  145. return "."
  146. } else if len(name) > 2 && name[1] == ':' {
  147. name = name[2:]
  148. }
  149. i := len(name)-1
  150. for ; i > 0 && (name[i] == '/' || name[i] == '\\'); i -= 1 {
  151. name = name[:i]
  152. }
  153. for i -= 1; i >= 0; i -= 1 {
  154. if name[i] == '/' || name[i] == '\\' {
  155. name = name[i+1:]
  156. break
  157. }
  158. }
  159. return name
  160. }
  161. @(private)
  162. file_type_mode :: proc(h: win32.HANDLE) -> File_Mode {
  163. switch win32.GetFileType(h) {
  164. case win32.FILE_TYPE_PIPE:
  165. return File_Mode_Named_Pipe
  166. case win32.FILE_TYPE_CHAR:
  167. return File_Mode_Device | File_Mode_Char_Device
  168. }
  169. return 0
  170. }
  171. @(private)
  172. file_mode_from_file_attributes :: proc(FileAttributes: win32.DWORD, h: win32.HANDLE, ReparseTag: win32.DWORD) -> (mode: File_Mode) {
  173. if FileAttributes & win32.FILE_ATTRIBUTE_READONLY != 0 {
  174. mode |= 0o444
  175. } else {
  176. mode |= 0o666
  177. }
  178. is_sym := false
  179. if FileAttributes & win32.FILE_ATTRIBUTE_REPARSE_POINT == 0 {
  180. is_sym = false
  181. } else {
  182. is_sym = ReparseTag == win32.IO_REPARSE_TAG_SYMLINK || ReparseTag == win32.IO_REPARSE_TAG_MOUNT_POINT
  183. }
  184. if is_sym {
  185. mode |= File_Mode_Sym_Link
  186. } else {
  187. if FileAttributes & win32.FILE_ATTRIBUTE_DIRECTORY != 0 {
  188. mode |= 0o111 | File_Mode_Dir
  189. }
  190. if h != nil {
  191. mode |= file_type_mode(h)
  192. }
  193. }
  194. return
  195. }
  196. @(private)
  197. windows_set_file_info_times :: proc(fi: ^File_Info, d: ^$T) {
  198. fi.creation_time = time.unix(0, win32.FILETIME_as_unix_nanoseconds(d.ftCreationTime))
  199. fi.modification_time = time.unix(0, win32.FILETIME_as_unix_nanoseconds(d.ftLastWriteTime))
  200. fi.access_time = time.unix(0, win32.FILETIME_as_unix_nanoseconds(d.ftLastAccessTime))
  201. }
  202. @(private)
  203. file_info_from_win32_file_attribute_data :: proc(d: ^win32.WIN32_FILE_ATTRIBUTE_DATA, name: string) -> (fi: File_Info, e: Errno) {
  204. fi.size = i64(d.nFileSizeHigh)<<32 + i64(d.nFileSizeLow)
  205. fi.mode |= file_mode_from_file_attributes(d.dwFileAttributes, nil, 0)
  206. fi.is_dir = fi.mode & File_Mode_Dir != 0
  207. windows_set_file_info_times(&fi, d)
  208. fi.fullpath, e = full_path_from_name(name)
  209. fi.name = basename(fi.fullpath)
  210. return
  211. }
  212. @(private)
  213. file_info_from_win32_find_data :: proc(d: ^win32.WIN32_FIND_DATAW, name: string) -> (fi: File_Info, e: Errno) {
  214. fi.size = i64(d.nFileSizeHigh)<<32 + i64(d.nFileSizeLow)
  215. fi.mode |= file_mode_from_file_attributes(d.dwFileAttributes, nil, 0)
  216. fi.is_dir = fi.mode & File_Mode_Dir != 0
  217. windows_set_file_info_times(&fi, d)
  218. fi.fullpath, e = full_path_from_name(name)
  219. fi.name = basename(fi.fullpath)
  220. return
  221. }
  222. @(private)
  223. file_info_from_get_file_information_by_handle :: proc(path: string, h: win32.HANDLE) -> (File_Info, Errno) {
  224. d: win32.BY_HANDLE_FILE_INFORMATION
  225. if !win32.GetFileInformationByHandle(h, &d) {
  226. err := Errno(win32.GetLastError())
  227. return {}, err
  228. }
  229. ti: win32.FILE_ATTRIBUTE_TAG_INFO
  230. if !win32.GetFileInformationByHandleEx(h, .FileAttributeTagInfo, &ti, size_of(ti)) {
  231. err := win32.GetLastError()
  232. if err != u32(ERROR_INVALID_PARAMETER) {
  233. return {}, Errno(err)
  234. }
  235. // Indicate this is a symlink on FAT file systems
  236. ti.ReparseTag = 0
  237. }
  238. fi: File_Info
  239. fi.fullpath = path
  240. fi.name = basename(path)
  241. fi.size = i64(d.nFileSizeHigh)<<32 + i64(d.nFileSizeLow)
  242. fi.mode |= file_mode_from_file_attributes(ti.FileAttributes, h, ti.ReparseTag)
  243. fi.is_dir = fi.mode & File_Mode_Dir != 0
  244. windows_set_file_info_times(&fi, &d)
  245. return fi, ERROR_NONE
  246. }