file_windows.odin 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604
  1. package os
  2. import win32 "core:sys/windows"
  3. import "base:intrinsics"
  4. import "base:runtime"
  5. import "core:unicode/utf16"
  6. is_path_separator :: proc(c: byte) -> bool {
  7. return c == '/' || c == '\\'
  8. }
  9. open :: proc(path: string, mode: int = O_RDONLY, perm: int = 0) -> (Handle, Errno) {
  10. if len(path) == 0 {
  11. return INVALID_HANDLE, ERROR_FILE_NOT_FOUND
  12. }
  13. access: u32
  14. switch mode & (O_RDONLY|O_WRONLY|O_RDWR) {
  15. case O_RDONLY: access = win32.FILE_GENERIC_READ
  16. case O_WRONLY: access = win32.FILE_GENERIC_WRITE
  17. case O_RDWR: access = win32.FILE_GENERIC_READ | win32.FILE_GENERIC_WRITE
  18. }
  19. if mode&O_CREATE != 0 {
  20. access |= win32.FILE_GENERIC_WRITE
  21. }
  22. if mode&O_APPEND != 0 {
  23. access &~= win32.FILE_GENERIC_WRITE
  24. access |= win32.FILE_APPEND_DATA
  25. }
  26. share_mode := win32.FILE_SHARE_READ|win32.FILE_SHARE_WRITE
  27. sa: ^win32.SECURITY_ATTRIBUTES = nil
  28. sa_inherit := win32.SECURITY_ATTRIBUTES{nLength = size_of(win32.SECURITY_ATTRIBUTES), bInheritHandle = true}
  29. if mode&O_CLOEXEC == 0 {
  30. sa = &sa_inherit
  31. }
  32. create_mode: u32
  33. switch {
  34. case mode&(O_CREATE|O_EXCL) == (O_CREATE | O_EXCL):
  35. create_mode = win32.CREATE_NEW
  36. case mode&(O_CREATE|O_TRUNC) == (O_CREATE | O_TRUNC):
  37. create_mode = win32.CREATE_ALWAYS
  38. case mode&O_CREATE == O_CREATE:
  39. create_mode = win32.OPEN_ALWAYS
  40. case mode&O_TRUNC == O_TRUNC:
  41. create_mode = win32.TRUNCATE_EXISTING
  42. case:
  43. create_mode = win32.OPEN_EXISTING
  44. }
  45. wide_path := win32.utf8_to_wstring(path)
  46. handle := Handle(win32.CreateFileW(wide_path, access, share_mode, sa, create_mode, win32.FILE_ATTRIBUTE_NORMAL|win32.FILE_FLAG_BACKUP_SEMANTICS, nil))
  47. if handle != INVALID_HANDLE {
  48. return handle, ERROR_NONE
  49. }
  50. err := Errno(win32.GetLastError())
  51. return INVALID_HANDLE, err
  52. }
  53. close :: proc(fd: Handle) -> Errno {
  54. if !win32.CloseHandle(win32.HANDLE(fd)) {
  55. return Errno(win32.GetLastError())
  56. }
  57. return ERROR_NONE
  58. }
  59. flush :: proc(fd: Handle) -> (err: Errno) {
  60. if !win32.FlushFileBuffers(win32.HANDLE(fd)) {
  61. err = Errno(win32.GetLastError())
  62. }
  63. return
  64. }
  65. write :: proc(fd: Handle, data: []byte) -> (int, Errno) {
  66. if len(data) == 0 {
  67. return 0, ERROR_NONE
  68. }
  69. single_write_length: win32.DWORD
  70. total_write: i64
  71. length := i64(len(data))
  72. for total_write < length {
  73. remaining := length - total_write
  74. to_write := win32.DWORD(min(i32(remaining), MAX_RW))
  75. e := win32.WriteFile(win32.HANDLE(fd), &data[total_write], to_write, &single_write_length, nil)
  76. if single_write_length <= 0 || !e {
  77. err := Errno(win32.GetLastError())
  78. return int(total_write), err
  79. }
  80. total_write += i64(single_write_length)
  81. }
  82. return int(total_write), ERROR_NONE
  83. }
  84. @(private="file")
  85. read_console :: proc(handle: win32.HANDLE, b: []byte) -> (n: int, err: Errno) {
  86. if len(b) == 0 {
  87. return 0, 0
  88. }
  89. BUF_SIZE :: 386
  90. buf16: [BUF_SIZE]u16
  91. buf8: [4*BUF_SIZE]u8
  92. for n < len(b) && err == 0 {
  93. min_read := max(len(b)/4, 1 if len(b) > 0 else 0)
  94. max_read := u32(min(BUF_SIZE, min_read))
  95. if max_read == 0 {
  96. break
  97. }
  98. single_read_length: u32
  99. ok := win32.ReadConsoleW(handle, &buf16[0], max_read, &single_read_length, nil)
  100. if !ok {
  101. err = Errno(win32.GetLastError())
  102. }
  103. buf8_len := utf16.decode_to_utf8(buf8[:], buf16[:single_read_length])
  104. src := buf8[:buf8_len]
  105. ctrl_z := false
  106. for i := 0; i < len(src) && n < len(b); i += 1 {
  107. x := src[i]
  108. if x == 0x1a { // ctrl-z
  109. ctrl_z = true
  110. break
  111. }
  112. b[n] = x
  113. n += 1
  114. }
  115. if ctrl_z || single_read_length < max_read {
  116. break
  117. }
  118. // NOTE(bill): if the last two values were a newline, then it is expected that
  119. // this is the end of the input
  120. if n >= 2 && single_read_length == max_read && string(b[n-2:n]) == "\r\n" {
  121. break
  122. }
  123. }
  124. return
  125. }
  126. read :: proc(fd: Handle, data: []byte) -> (total_read: int, err: Errno) {
  127. if len(data) == 0 {
  128. return 0, ERROR_NONE
  129. }
  130. handle := win32.HANDLE(fd)
  131. m: u32
  132. is_console := win32.GetConsoleMode(handle, &m)
  133. length := len(data)
  134. // NOTE(Jeroen): `length` can't be casted to win32.DWORD here because it'll overflow if > 4 GiB and return 0 if exactly that.
  135. to_read := min(i64(length), MAX_RW)
  136. if is_console {
  137. total_read, err = read_console(handle, data[total_read:][:to_read])
  138. if err != 0 {
  139. return total_read, err
  140. }
  141. } else {
  142. // NOTE(Jeroen): So we cast it here *after* we've ensured that `to_read` is at most MAX_RW (1 GiB)
  143. bytes_read: win32.DWORD
  144. if e := win32.ReadFile(handle, &data[total_read], win32.DWORD(to_read), &bytes_read, nil); e {
  145. // Successful read can mean two things, including EOF, see:
  146. // https://learn.microsoft.com/en-us/windows/win32/fileio/testing-for-the-end-of-a-file
  147. if bytes_read == 0 {
  148. return 0, ERROR_HANDLE_EOF
  149. } else {
  150. return int(bytes_read), ERROR_NONE
  151. }
  152. } else {
  153. return 0, Errno(win32.GetLastError())
  154. }
  155. }
  156. return total_read, ERROR_NONE
  157. }
  158. seek :: proc(fd: Handle, offset: i64, whence: int) -> (i64, Errno) {
  159. w: u32
  160. switch whence {
  161. case 0: w = win32.FILE_BEGIN
  162. case 1: w = win32.FILE_CURRENT
  163. case 2: w = win32.FILE_END
  164. }
  165. hi := i32(offset>>32)
  166. lo := i32(offset)
  167. ft := win32.GetFileType(win32.HANDLE(fd))
  168. if ft == win32.FILE_TYPE_PIPE {
  169. return 0, ERROR_FILE_IS_PIPE
  170. }
  171. dw_ptr := win32.SetFilePointer(win32.HANDLE(fd), lo, &hi, w)
  172. if dw_ptr == win32.INVALID_SET_FILE_POINTER {
  173. err := Errno(win32.GetLastError())
  174. return 0, err
  175. }
  176. return i64(hi)<<32 + i64(dw_ptr), ERROR_NONE
  177. }
  178. file_size :: proc(fd: Handle) -> (i64, Errno) {
  179. length: win32.LARGE_INTEGER
  180. err: Errno
  181. if !win32.GetFileSizeEx(win32.HANDLE(fd), &length) {
  182. err = Errno(win32.GetLastError())
  183. }
  184. return i64(length), err
  185. }
  186. @(private)
  187. MAX_RW :: 1<<30
  188. ERROR_EOF :: 38
  189. @(private)
  190. pread :: proc(fd: Handle, data: []byte, offset: i64) -> (int, Errno) {
  191. buf := data
  192. if len(buf) > MAX_RW {
  193. buf = buf[:MAX_RW]
  194. }
  195. o := win32.OVERLAPPED{
  196. OffsetHigh = u32(offset>>32),
  197. Offset = u32(offset),
  198. }
  199. // TODO(bill): Determine the correct behaviour for consoles
  200. h := win32.HANDLE(fd)
  201. done: win32.DWORD
  202. e: Errno
  203. if !win32.ReadFile(h, raw_data(buf), u32(len(buf)), &done, &o) {
  204. e = Errno(win32.GetLastError())
  205. done = 0
  206. }
  207. return int(done), e
  208. }
  209. @(private)
  210. pwrite :: proc(fd: Handle, data: []byte, offset: i64) -> (int, Errno) {
  211. buf := data
  212. if len(buf) > MAX_RW {
  213. buf = buf[:MAX_RW]
  214. }
  215. o := win32.OVERLAPPED{
  216. OffsetHigh = u32(offset>>32),
  217. Offset = u32(offset),
  218. }
  219. h := win32.HANDLE(fd)
  220. done: win32.DWORD
  221. e: Errno
  222. if !win32.WriteFile(h, raw_data(buf), u32(len(buf)), &done, &o) {
  223. e = Errno(win32.GetLastError())
  224. done = 0
  225. }
  226. return int(done), e
  227. }
  228. /*
  229. read_at returns n: 0, err: 0 on EOF
  230. on Windows, read_at changes the position of the file cursor, on *nix, it does not.
  231. bytes: [8]u8{}
  232. read_at(fd, bytes, 0)
  233. read(fd, bytes)
  234. will read from the location twice on *nix, and from two different locations on Windows
  235. */
  236. read_at :: proc(fd: Handle, data: []byte, offset: i64) -> (n: int, err: Errno) {
  237. if offset < 0 {
  238. return 0, ERROR_NEGATIVE_OFFSET
  239. }
  240. b, offset := data, offset
  241. for len(b) > 0 {
  242. m, e := pread(fd, b, offset)
  243. if e == ERROR_EOF {
  244. err = 0
  245. break
  246. }
  247. if e != 0 {
  248. err = e
  249. break
  250. }
  251. n += m
  252. b = b[m:]
  253. offset += i64(m)
  254. }
  255. return
  256. }
  257. /*
  258. on Windows, write_at changes the position of the file cursor, on *nix, it does not.
  259. bytes: [8]u8{}
  260. write_at(fd, bytes, 0)
  261. write(fd, bytes)
  262. will write to the location twice on *nix, and to two different locations on Windows
  263. */
  264. write_at :: proc(fd: Handle, data: []byte, offset: i64) -> (n: int, err: Errno) {
  265. if offset < 0 {
  266. return 0, ERROR_NEGATIVE_OFFSET
  267. }
  268. b, offset := data, offset
  269. for len(b) > 0 {
  270. m, e := pwrite(fd, b, offset)
  271. if e != 0 {
  272. err = e
  273. break
  274. }
  275. n += m
  276. b = b[m:]
  277. offset += i64(m)
  278. }
  279. return
  280. }
  281. // NOTE(bill): Uses startup to initialize it
  282. stdin := get_std_handle(uint(win32.STD_INPUT_HANDLE))
  283. stdout := get_std_handle(uint(win32.STD_OUTPUT_HANDLE))
  284. stderr := get_std_handle(uint(win32.STD_ERROR_HANDLE))
  285. get_std_handle :: proc "contextless" (h: uint) -> Handle {
  286. fd := win32.GetStdHandle(win32.DWORD(h))
  287. return Handle(fd)
  288. }
  289. exists :: proc(path: string) -> bool {
  290. runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD()
  291. wpath := win32.utf8_to_wstring(path, context.temp_allocator)
  292. attribs := win32.GetFileAttributesW(wpath)
  293. return attribs != win32.INVALID_FILE_ATTRIBUTES
  294. }
  295. is_file :: proc(path: string) -> bool {
  296. runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD()
  297. wpath := win32.utf8_to_wstring(path, context.temp_allocator)
  298. attribs := win32.GetFileAttributesW(wpath)
  299. if attribs != win32.INVALID_FILE_ATTRIBUTES {
  300. return attribs & win32.FILE_ATTRIBUTE_DIRECTORY == 0
  301. }
  302. return false
  303. }
  304. is_dir :: proc(path: string) -> bool {
  305. runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD()
  306. wpath := win32.utf8_to_wstring(path, context.temp_allocator)
  307. attribs := win32.GetFileAttributesW(wpath)
  308. if attribs != win32.INVALID_FILE_ATTRIBUTES {
  309. return attribs & win32.FILE_ATTRIBUTE_DIRECTORY != 0
  310. }
  311. return false
  312. }
  313. // NOTE(tetra): GetCurrentDirectory is not thread safe with SetCurrentDirectory and GetFullPathName
  314. @private cwd_lock := win32.SRWLOCK{} // zero is initialized
  315. get_current_directory :: proc(allocator := context.allocator) -> string {
  316. win32.AcquireSRWLockExclusive(&cwd_lock)
  317. runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD(ignore = context.temp_allocator == allocator)
  318. sz_utf16 := win32.GetCurrentDirectoryW(0, nil)
  319. dir_buf_wstr := make([]u16, sz_utf16, context.temp_allocator) // the first time, it _includes_ the NUL.
  320. sz_utf16 = win32.GetCurrentDirectoryW(win32.DWORD(len(dir_buf_wstr)), raw_data(dir_buf_wstr))
  321. assert(int(sz_utf16)+1 == len(dir_buf_wstr)) // the second time, it _excludes_ the NUL.
  322. win32.ReleaseSRWLockExclusive(&cwd_lock)
  323. return win32.utf16_to_utf8(dir_buf_wstr, allocator) or_else ""
  324. }
  325. set_current_directory :: proc(path: string) -> (err: Errno) {
  326. runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD()
  327. wstr := win32.utf8_to_wstring(path, context.temp_allocator)
  328. win32.AcquireSRWLockExclusive(&cwd_lock)
  329. if !win32.SetCurrentDirectoryW(wstr) {
  330. err = Errno(win32.GetLastError())
  331. }
  332. win32.ReleaseSRWLockExclusive(&cwd_lock)
  333. return
  334. }
  335. change_directory :: set_current_directory
  336. make_directory :: proc(path: string, mode: u32 = 0) -> (err: Errno) {
  337. runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD()
  338. // Mode is unused on Windows, but is needed on *nix
  339. wpath := win32.utf8_to_wstring(path, context.temp_allocator)
  340. if !win32.CreateDirectoryW(wpath, nil) {
  341. err = Errno(win32.GetLastError())
  342. }
  343. return
  344. }
  345. remove_directory :: proc(path: string) -> (err: Errno) {
  346. runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD()
  347. wpath := win32.utf8_to_wstring(path, context.temp_allocator)
  348. if !win32.RemoveDirectoryW(wpath) {
  349. err = Errno(win32.GetLastError())
  350. }
  351. return
  352. }
  353. @(private)
  354. is_abs :: proc(path: string) -> bool {
  355. if len(path) > 0 && path[0] == '/' {
  356. return true
  357. }
  358. when ODIN_OS == .Windows {
  359. if len(path) > 2 {
  360. switch path[0] {
  361. case 'A'..='Z', 'a'..='z':
  362. return path[1] == ':' && is_path_separator(path[2])
  363. }
  364. }
  365. }
  366. return false
  367. }
  368. @(private)
  369. fix_long_path :: proc(path: string) -> string {
  370. if len(path) < 248 {
  371. return path
  372. }
  373. if len(path) >= 2 && path[:2] == `\\` {
  374. return path
  375. }
  376. if !is_abs(path) {
  377. return path
  378. }
  379. prefix :: `\\?`
  380. path_buf := make([]byte, len(prefix)+len(path)+len(`\`), context.temp_allocator)
  381. copy(path_buf, prefix)
  382. n := len(path)
  383. r, w := 0, len(prefix)
  384. for r < n {
  385. switch {
  386. case is_path_separator(path[r]):
  387. r += 1
  388. case path[r] == '.' && (r+1 == n || is_path_separator(path[r+1])):
  389. r += 1
  390. case r+1 < n && path[r] == '.' && path[r+1] == '.' && (r+2 == n || is_path_separator(path[r+2])):
  391. return path
  392. case:
  393. path_buf[w] = '\\'
  394. w += 1
  395. for ; r < n && !is_path_separator(path[r]); r += 1 {
  396. path_buf[w] = path[r]
  397. w += 1
  398. }
  399. }
  400. }
  401. if w == len(`\\?\c:`) {
  402. path_buf[w] = '\\'
  403. w += 1
  404. }
  405. return string(path_buf[:w])
  406. }
  407. link :: proc(old_name, new_name: string) -> (err: Errno) {
  408. runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD()
  409. n := win32.utf8_to_wstring(fix_long_path(new_name))
  410. o := win32.utf8_to_wstring(fix_long_path(old_name))
  411. return Errno(win32.CreateHardLinkW(n, o, nil))
  412. }
  413. unlink :: proc(path: string) -> (err: Errno) {
  414. runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD()
  415. wpath := win32.utf8_to_wstring(path, context.temp_allocator)
  416. if !win32.DeleteFileW(wpath) {
  417. err = Errno(win32.GetLastError())
  418. }
  419. return
  420. }
  421. rename :: proc(old_path, new_path: string) -> (err: Errno) {
  422. runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD()
  423. from := win32.utf8_to_wstring(old_path, context.temp_allocator)
  424. to := win32.utf8_to_wstring(new_path, context.temp_allocator)
  425. if !win32.MoveFileExW(from, to, win32.MOVEFILE_REPLACE_EXISTING) {
  426. err = Errno(win32.GetLastError())
  427. }
  428. return
  429. }
  430. ftruncate :: proc(fd: Handle, length: i64) -> (err: Errno) {
  431. curr_off, e := seek(fd, 0, 1)
  432. if e != 0 {
  433. return e
  434. }
  435. defer seek(fd, curr_off, 0)
  436. _, e = seek(fd, length, 0)
  437. if e != 0 {
  438. return e
  439. }
  440. ok := win32.SetEndOfFile(win32.HANDLE(fd))
  441. if !ok {
  442. return Errno(win32.GetLastError())
  443. }
  444. return ERROR_NONE
  445. }
  446. truncate :: proc(path: string, length: i64) -> (err: Errno) {
  447. fd: Handle
  448. fd, err = open(path, O_WRONLY|O_CREATE, 0o666)
  449. if err != 0 {
  450. return
  451. }
  452. defer close(fd)
  453. err = ftruncate(fd, length)
  454. return
  455. }
  456. remove :: proc(name: string) -> Errno {
  457. p := win32.utf8_to_wstring(fix_long_path(name))
  458. err, err1: win32.DWORD
  459. if !win32.DeleteFileW(p) {
  460. err = win32.GetLastError()
  461. }
  462. if err == 0 {
  463. return 0
  464. }
  465. if !win32.RemoveDirectoryW(p) {
  466. err1 = win32.GetLastError()
  467. }
  468. if err1 == 0 {
  469. return 0
  470. }
  471. if err != err1 {
  472. a := win32.GetFileAttributesW(p)
  473. if a == ~u32(0) {
  474. err = win32.GetLastError()
  475. } else {
  476. if a & win32.FILE_ATTRIBUTE_DIRECTORY != 0 {
  477. err = err1
  478. } else if a & win32.FILE_ATTRIBUTE_READONLY != 0 {
  479. if win32.SetFileAttributesW(p, a &~ win32.FILE_ATTRIBUTE_READONLY) {
  480. err = 0
  481. if !win32.DeleteFileW(p) {
  482. err = win32.GetLastError()
  483. }
  484. }
  485. }
  486. }
  487. }
  488. return Errno(err)
  489. }
  490. pipe :: proc() -> (r, w: Handle, err: Errno) {
  491. sa: win32.SECURITY_ATTRIBUTES
  492. sa.nLength = size_of(win32.SECURITY_ATTRIBUTES)
  493. sa.bInheritHandle = true
  494. if !win32.CreatePipe((^win32.HANDLE)(&r), (^win32.HANDLE)(&w), &sa, 0) {
  495. err = Errno(win32.GetLastError())
  496. }
  497. return
  498. }