stat_windows.odin 9.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353
  1. //+private
  2. package os2
  3. import "base:runtime"
  4. import "core:time"
  5. import "core:strings"
  6. import win32 "core:sys/windows"
  7. _fstat :: proc(f: ^File, allocator: runtime.Allocator) -> (File_Info, Error) {
  8. if f == nil || (^File_Impl)(f.impl).fd == nil {
  9. return {}, nil
  10. }
  11. path, err := _cleanpath_from_handle(f, allocator)
  12. if err != nil {
  13. return {}, err
  14. }
  15. h := _handle(f)
  16. switch win32.GetFileType(h) {
  17. case win32.FILE_TYPE_PIPE, win32.FILE_TYPE_CHAR:
  18. fi := File_Info {
  19. fullpath = path,
  20. name = basename(path),
  21. type = file_type(h),
  22. }
  23. return fi, nil
  24. }
  25. return _file_info_from_get_file_information_by_handle(path, h, allocator)
  26. }
  27. _stat :: proc(name: string, allocator: runtime.Allocator) -> (File_Info, Error) {
  28. return internal_stat(name, win32.FILE_FLAG_BACKUP_SEMANTICS, allocator)
  29. }
  30. _lstat :: proc(name: string, allocator: runtime.Allocator) -> (File_Info, Error) {
  31. return internal_stat(name, win32.FILE_FLAG_BACKUP_SEMANTICS|win32.FILE_FLAG_OPEN_REPARSE_POINT, allocator)
  32. }
  33. _same_file :: proc(fi1, fi2: File_Info) -> bool {
  34. return fi1.fullpath == fi2.fullpath
  35. }
  36. full_path_from_name :: proc(name: string, allocator: runtime.Allocator) -> (path: string, err: Error) {
  37. name := name
  38. if name == "" {
  39. name = "."
  40. }
  41. TEMP_ALLOCATOR_GUARD()
  42. p := win32_utf8_to_utf16(name, temp_allocator()) or_return
  43. n := win32.GetFullPathNameW(raw_data(p), 0, nil, nil)
  44. if n == 0 {
  45. return "", _get_platform_error()
  46. }
  47. buf := make([]u16, n+1, temp_allocator())
  48. n = win32.GetFullPathNameW(raw_data(p), u32(len(buf)), raw_data(buf), nil)
  49. if n == 0 {
  50. return "", _get_platform_error()
  51. }
  52. return win32_utf16_to_utf8(buf[:n], allocator)
  53. }
  54. internal_stat :: proc(name: string, create_file_attributes: u32, allocator: runtime.Allocator) -> (fi: File_Info, e: Error) {
  55. if len(name) == 0 {
  56. return {}, .Not_Exist
  57. }
  58. TEMP_ALLOCATOR_GUARD()
  59. wname := _fix_long_path(name, temp_allocator()) or_return
  60. fa: win32.WIN32_FILE_ATTRIBUTE_DATA
  61. ok := win32.GetFileAttributesExW(wname, win32.GetFileExInfoStandard, &fa)
  62. if ok && fa.dwFileAttributes & win32.FILE_ATTRIBUTE_REPARSE_POINT == 0 {
  63. // Not a symlink
  64. return _file_info_from_win32_file_attribute_data(&fa, name, allocator)
  65. }
  66. err := 0 if ok else win32.GetLastError()
  67. if err == win32.ERROR_SHARING_VIOLATION {
  68. fd: win32.WIN32_FIND_DATAW
  69. sh := win32.FindFirstFileW(wname, &fd)
  70. if sh == win32.INVALID_HANDLE_VALUE {
  71. e = _get_platform_error()
  72. return
  73. }
  74. win32.FindClose(sh)
  75. return _file_info_from_win32_find_data(&fd, name, allocator)
  76. }
  77. h := win32.CreateFileW(wname, 0, 0, nil, win32.OPEN_EXISTING, create_file_attributes, nil)
  78. if h == win32.INVALID_HANDLE_VALUE {
  79. e = _get_platform_error()
  80. return
  81. }
  82. defer win32.CloseHandle(h)
  83. return _file_info_from_get_file_information_by_handle(name, h, allocator)
  84. }
  85. _cleanpath_strip_prefix :: proc(buf: []u16) -> []u16 {
  86. buf := buf
  87. N := 0
  88. for c, i in buf {
  89. if c == 0 { break }
  90. N = i+1
  91. }
  92. buf = buf[:N]
  93. if len(buf) >= 4 {
  94. if buf[0] == '\\' &&
  95. buf[1] == '\\' &&
  96. buf[2] == '?' &&
  97. buf[3] == '\\' {
  98. buf = buf[4:]
  99. }
  100. }
  101. return buf
  102. }
  103. _cleanpath_from_handle :: proc(f: ^File, allocator: runtime.Allocator) -> (string, Error) {
  104. if f == nil {
  105. return "", nil
  106. }
  107. h := _handle(f)
  108. n := win32.GetFinalPathNameByHandleW(h, nil, 0, 0)
  109. if n == 0 {
  110. return "", _get_platform_error()
  111. }
  112. TEMP_ALLOCATOR_GUARD()
  113. buf := make([]u16, max(n, 260)+1, temp_allocator())
  114. n = win32.GetFinalPathNameByHandleW(h, raw_data(buf), u32(len(buf)), 0)
  115. return _cleanpath_from_buf(buf[:n], allocator)
  116. }
  117. _cleanpath_from_handle_u16 :: proc(f: ^File) -> ([]u16, Error) {
  118. if f == nil {
  119. return nil, nil
  120. }
  121. h := _handle(f)
  122. n := win32.GetFinalPathNameByHandleW(h, nil, 0, 0)
  123. if n == 0 {
  124. return nil, _get_platform_error()
  125. }
  126. TEMP_ALLOCATOR_GUARD()
  127. buf := make([]u16, max(n, 260)+1, temp_allocator())
  128. n = win32.GetFinalPathNameByHandleW(h, raw_data(buf), u32(len(buf)), 0)
  129. return _cleanpath_strip_prefix(buf[:n]), nil
  130. }
  131. _cleanpath_from_buf :: proc(buf: []u16, allocator: runtime.Allocator) -> (string, runtime.Allocator_Error) {
  132. buf := buf
  133. buf = _cleanpath_strip_prefix(buf)
  134. return win32_utf16_to_utf8(buf, allocator)
  135. }
  136. basename :: proc(name: string) -> (base: string) {
  137. name := name
  138. if len(name) > 3 && name[:3] == `\\?` {
  139. name = name[3:]
  140. }
  141. if len(name) == 2 && name[1] == ':' {
  142. return "."
  143. } else if len(name) > 2 && name[1] == ':' {
  144. name = name[2:]
  145. }
  146. i := len(name)-1
  147. for ; i > 0 && (name[i] == '/' || name[i] == '\\'); i -= 1 {
  148. name = name[:i]
  149. }
  150. for i -= 1; i >= 0; i -= 1 {
  151. if name[i] == '/' || name[i] == '\\' {
  152. name = name[i+1:]
  153. break
  154. }
  155. }
  156. return name
  157. }
  158. file_type :: proc(h: win32.HANDLE) -> File_Type {
  159. switch win32.GetFileType(h) {
  160. case win32.FILE_TYPE_PIPE: return .Named_Pipe
  161. case win32.FILE_TYPE_CHAR: return .Character_Device
  162. case win32.FILE_TYPE_DISK: return .Regular
  163. }
  164. return .Undetermined
  165. }
  166. _file_type_mode_from_file_attributes :: proc(file_attributes: win32.DWORD, h: win32.HANDLE, ReparseTag: win32.DWORD) -> (type: File_Type, mode: int) {
  167. if file_attributes & win32.FILE_ATTRIBUTE_READONLY != 0 {
  168. mode |= 0o444
  169. } else {
  170. mode |= 0o666
  171. }
  172. is_sym := false
  173. if file_attributes & win32.FILE_ATTRIBUTE_REPARSE_POINT == 0 {
  174. is_sym = false
  175. } else {
  176. is_sym = ReparseTag == win32.IO_REPARSE_TAG_SYMLINK || ReparseTag == win32.IO_REPARSE_TAG_MOUNT_POINT
  177. }
  178. if is_sym {
  179. type = .Symlink
  180. } else {
  181. if file_attributes & win32.FILE_ATTRIBUTE_DIRECTORY != 0 {
  182. type = .Directory
  183. mode |= 0o111
  184. }
  185. if h != nil {
  186. type = file_type(h)
  187. }
  188. }
  189. return
  190. }
  191. _file_info_from_win32_file_attribute_data :: proc(d: ^win32.WIN32_FILE_ATTRIBUTE_DATA, name: string, allocator: runtime.Allocator) -> (fi: File_Info, e: Error) {
  192. fi.size = i64(d.nFileSizeHigh)<<32 + i64(d.nFileSizeLow)
  193. type, mode := _file_type_mode_from_file_attributes(d.dwFileAttributes, nil, 0)
  194. fi.type = type
  195. fi.mode |= mode
  196. fi.creation_time = time.unix(0, win32.FILETIME_as_unix_nanoseconds(d.ftCreationTime))
  197. fi.modification_time = time.unix(0, win32.FILETIME_as_unix_nanoseconds(d.ftLastWriteTime))
  198. fi.access_time = time.unix(0, win32.FILETIME_as_unix_nanoseconds(d.ftLastAccessTime))
  199. fi.fullpath, e = full_path_from_name(name, allocator)
  200. fi.name = basename(fi.fullpath)
  201. return
  202. }
  203. _file_info_from_win32_find_data :: proc(d: ^win32.WIN32_FIND_DATAW, name: string, allocator: runtime.Allocator) -> (fi: File_Info, e: Error) {
  204. fi.size = i64(d.nFileSizeHigh)<<32 + i64(d.nFileSizeLow)
  205. type, mode := _file_type_mode_from_file_attributes(d.dwFileAttributes, nil, 0)
  206. fi.type = type
  207. fi.mode |= mode
  208. fi.creation_time = time.unix(0, win32.FILETIME_as_unix_nanoseconds(d.ftCreationTime))
  209. fi.modification_time = time.unix(0, win32.FILETIME_as_unix_nanoseconds(d.ftLastWriteTime))
  210. fi.access_time = time.unix(0, win32.FILETIME_as_unix_nanoseconds(d.ftLastAccessTime))
  211. fi.fullpath, e = full_path_from_name(name, allocator)
  212. fi.name = basename(fi.fullpath)
  213. return
  214. }
  215. _file_info_from_get_file_information_by_handle :: proc(path: string, h: win32.HANDLE, allocator: runtime.Allocator) -> (File_Info, Error) {
  216. d: win32.BY_HANDLE_FILE_INFORMATION
  217. if !win32.GetFileInformationByHandle(h, &d) {
  218. return {}, _get_platform_error()
  219. }
  220. ti: win32.FILE_ATTRIBUTE_TAG_INFO
  221. if !win32.GetFileInformationByHandleEx(h, .FileAttributeTagInfo, &ti, size_of(ti)) {
  222. err := win32.GetLastError()
  223. if err != win32.ERROR_INVALID_PARAMETER {
  224. return {}, Platform_Error(err)
  225. }
  226. // Indicate this is a symlink on FAT file systems
  227. ti.ReparseTag = 0
  228. }
  229. fi: File_Info
  230. fi.fullpath = path
  231. fi.name = basename(path)
  232. fi.inode = u128(u64(d.nFileIndexHigh)<<32 + u64(d.nFileIndexLow))
  233. fi.size = i64(d.nFileSizeHigh)<<32 + i64(d.nFileSizeLow)
  234. type, mode := _file_type_mode_from_file_attributes(d.dwFileAttributes, nil, 0)
  235. fi.type = type
  236. fi.mode |= mode
  237. fi.creation_time = time.unix(0, win32.FILETIME_as_unix_nanoseconds(d.ftCreationTime))
  238. fi.modification_time = time.unix(0, win32.FILETIME_as_unix_nanoseconds(d.ftLastWriteTime))
  239. fi.access_time = time.unix(0, win32.FILETIME_as_unix_nanoseconds(d.ftLastAccessTime))
  240. return fi, nil
  241. }
  242. reserved_names := [?]string{
  243. "CON", "PRN", "AUX", "NUL",
  244. "COM1", "COM2", "COM3", "COM4", "COM5", "COM6", "COM7", "COM8", "COM9",
  245. "LPT1", "LPT2", "LPT3", "LPT4", "LPT5", "LPT6", "LPT7", "LPT8", "LPT9",
  246. }
  247. _is_reserved_name :: proc(path: string) -> bool {
  248. if len(path) == 0 {
  249. return false
  250. }
  251. for reserved in reserved_names {
  252. if strings.equal_fold(path, reserved) {
  253. return true
  254. }
  255. }
  256. return false
  257. }
  258. _is_UNC :: proc(path: string) -> bool {
  259. return _volume_name_len(path) > 2
  260. }
  261. _volume_name_len :: proc(path: string) -> int {
  262. if ODIN_OS == .Windows {
  263. if len(path) < 2 {
  264. return 0
  265. }
  266. c := path[0]
  267. if path[1] == ':' {
  268. switch c {
  269. case 'a'..='z', 'A'..='Z':
  270. return 2
  271. }
  272. }
  273. // URL: https://msdn.microsoft.com/en-us/library/windows/desktop/aa365247(v=vs.85).aspx
  274. if l := len(path); l >= 5 && _is_path_separator(path[0]) && _is_path_separator(path[1]) &&
  275. !_is_path_separator(path[2]) && path[2] != '.' {
  276. for n := 3; n < l-1; n += 1 {
  277. if _is_path_separator(path[n]) {
  278. n += 1
  279. if !_is_path_separator(path[n]) {
  280. if path[n] == '.' {
  281. break
  282. }
  283. }
  284. for ; n < l; n += 1 {
  285. if _is_path_separator(path[n]) {
  286. break
  287. }
  288. }
  289. return n
  290. }
  291. break
  292. }
  293. }
  294. }
  295. return 0
  296. }
  297. _is_abs :: proc(path: string) -> bool {
  298. if _is_reserved_name(path) {
  299. return true
  300. }
  301. l := _volume_name_len(path)
  302. if l == 0 {
  303. return false
  304. }
  305. path := path
  306. path = path[l:]
  307. if path == "" {
  308. return false
  309. }
  310. return is_path_separator(path[0])
  311. }