stat_windows.odin 7.8 KB

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