file_windows.odin 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614
  1. package os
  2. import win32 "core:sys/windows"
  3. import "core:intrinsics"
  4. import "core: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+i < 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) -> (int, 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. single_read_length: win32.DWORD
  134. total_read: int
  135. length := len(data)
  136. // NOTE(Jeroen): `length` can't be casted to win32.DWORD here because it'll overflow if > 4 GiB and return 0 if exactly that.
  137. to_read := min(i64(length), MAX_RW)
  138. e: win32.BOOL
  139. if is_console {
  140. n, err := read_console(handle, data[total_read:][:to_read])
  141. total_read += n
  142. if err != 0 {
  143. return int(total_read), err
  144. }
  145. } else {
  146. // NOTE(Jeroen): So we cast it here *after* we've ensured that `to_read` is at most MAX_RW (1 GiB)
  147. e = win32.ReadFile(handle, &data[total_read], win32.DWORD(to_read), &single_read_length, nil)
  148. }
  149. if single_read_length <= 0 || !e {
  150. err := Errno(win32.GetLastError())
  151. return int(total_read), err
  152. }
  153. total_read += int(single_read_length)
  154. return int(total_read), ERROR_NONE
  155. }
  156. seek :: proc(fd: Handle, offset: i64, whence: int) -> (i64, Errno) {
  157. w: u32
  158. switch whence {
  159. case 0: w = win32.FILE_BEGIN
  160. case 1: w = win32.FILE_CURRENT
  161. case 2: w = win32.FILE_END
  162. }
  163. hi := i32(offset>>32)
  164. lo := i32(offset)
  165. ft := win32.GetFileType(win32.HANDLE(fd))
  166. if ft == win32.FILE_TYPE_PIPE {
  167. return 0, ERROR_FILE_IS_PIPE
  168. }
  169. dw_ptr := win32.SetFilePointer(win32.HANDLE(fd), lo, &hi, w)
  170. if dw_ptr == win32.INVALID_SET_FILE_POINTER {
  171. err := Errno(win32.GetLastError())
  172. return 0, err
  173. }
  174. return i64(hi)<<32 + i64(dw_ptr), ERROR_NONE
  175. }
  176. file_size :: proc(fd: Handle) -> (i64, Errno) {
  177. length: win32.LARGE_INTEGER
  178. err: Errno
  179. if !win32.GetFileSizeEx(win32.HANDLE(fd), &length) {
  180. err = Errno(win32.GetLastError())
  181. }
  182. return i64(length), err
  183. }
  184. @(private)
  185. MAX_RW :: 1<<30
  186. ERROR_EOF :: 38
  187. @(private)
  188. pread :: proc(fd: Handle, data: []byte, offset: i64) -> (int, Errno) {
  189. buf := data
  190. if len(buf) > MAX_RW {
  191. buf = buf[:MAX_RW]
  192. }
  193. o := win32.OVERLAPPED{
  194. OffsetHigh = u32(offset>>32),
  195. Offset = u32(offset),
  196. }
  197. // TODO(bill): Determine the correct behaviour for consoles
  198. h := win32.HANDLE(fd)
  199. done: win32.DWORD
  200. e: Errno
  201. if !win32.ReadFile(h, raw_data(buf), u32(len(buf)), &done, &o) {
  202. e = Errno(win32.GetLastError())
  203. done = 0
  204. }
  205. return int(done), e
  206. }
  207. @(private)
  208. pwrite :: proc(fd: Handle, data: []byte, offset: i64) -> (int, Errno) {
  209. buf := data
  210. if len(buf) > MAX_RW {
  211. buf = buf[:MAX_RW]
  212. }
  213. o := win32.OVERLAPPED{
  214. OffsetHigh = u32(offset>>32),
  215. Offset = u32(offset),
  216. }
  217. h := win32.HANDLE(fd)
  218. done: win32.DWORD
  219. e: Errno
  220. if !win32.WriteFile(h, raw_data(buf), u32(len(buf)), &done, &o) {
  221. e = Errno(win32.GetLastError())
  222. done = 0
  223. }
  224. return int(done), e
  225. }
  226. /*
  227. read_at returns n: 0, err: 0 on EOF
  228. on Windows, read_at changes the position of the file cursor, on *nix, it does not.
  229. bytes: [8]u8{}
  230. read_at(fd, bytes, 0)
  231. read(fd, bytes)
  232. will read from the location twice on *nix, and from two different locations on Windows
  233. */
  234. read_at :: proc(fd: Handle, data: []byte, offset: i64) -> (n: int, err: Errno) {
  235. if offset < 0 {
  236. return 0, ERROR_NEGATIVE_OFFSET
  237. }
  238. b, offset := data, offset
  239. for len(b) > 0 {
  240. m, e := pread(fd, b, offset)
  241. if e == ERROR_EOF {
  242. err = 0
  243. break
  244. }
  245. if e != 0 {
  246. err = e
  247. break
  248. }
  249. n += m
  250. b = b[m:]
  251. offset += i64(m)
  252. }
  253. return
  254. }
  255. /*
  256. on Windows, write_at changes the position of the file cursor, on *nix, it does not.
  257. bytes: [8]u8{}
  258. write_at(fd, bytes, 0)
  259. write(fd, bytes)
  260. will write to the location twice on *nix, and to two different locations on Windows
  261. */
  262. write_at :: proc(fd: Handle, data: []byte, offset: i64) -> (n: int, err: Errno) {
  263. if offset < 0 {
  264. return 0, ERROR_NEGATIVE_OFFSET
  265. }
  266. b, offset := data, offset
  267. for len(b) > 0 {
  268. m, e := pwrite(fd, b, offset)
  269. if e != 0 {
  270. err = e
  271. break
  272. }
  273. n += m
  274. b = b[m:]
  275. offset += i64(m)
  276. }
  277. return
  278. }
  279. // NOTE(bill): Uses startup to initialize it
  280. stdin := get_std_handle(uint(win32.STD_INPUT_HANDLE))
  281. stdout := get_std_handle(uint(win32.STD_OUTPUT_HANDLE))
  282. stderr := get_std_handle(uint(win32.STD_ERROR_HANDLE))
  283. get_std_handle :: proc "contextless" (h: uint) -> Handle {
  284. fd := win32.GetStdHandle(win32.DWORD(h))
  285. return Handle(fd)
  286. }
  287. exists :: proc(path: string) -> bool {
  288. runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD()
  289. wpath := win32.utf8_to_wstring(path, context.temp_allocator)
  290. attribs := win32.GetFileAttributesW(wpath)
  291. return i32(attribs) != win32.INVALID_FILE_ATTRIBUTES
  292. }
  293. is_file :: proc(path: string) -> bool {
  294. runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD()
  295. wpath := win32.utf8_to_wstring(path, context.temp_allocator)
  296. attribs := win32.GetFileAttributesW(wpath)
  297. if i32(attribs) != win32.INVALID_FILE_ATTRIBUTES {
  298. return attribs & win32.FILE_ATTRIBUTE_DIRECTORY == 0
  299. }
  300. return false
  301. }
  302. is_dir :: proc(path: string) -> bool {
  303. runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD()
  304. wpath := win32.utf8_to_wstring(path, context.temp_allocator)
  305. attribs := win32.GetFileAttributesW(wpath)
  306. if i32(attribs) != win32.INVALID_FILE_ATTRIBUTES {
  307. return attribs & win32.FILE_ATTRIBUTE_DIRECTORY != 0
  308. }
  309. return false
  310. }
  311. // NOTE(tetra): GetCurrentDirectory is not thread safe with SetCurrentDirectory and GetFullPathName
  312. @private cwd_lock := win32.SRWLOCK{} // zero is initialized
  313. get_current_directory :: proc(allocator := context.allocator) -> string {
  314. win32.AcquireSRWLockExclusive(&cwd_lock)
  315. runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD(ignore = context.temp_allocator == allocator)
  316. sz_utf16 := win32.GetCurrentDirectoryW(0, nil)
  317. dir_buf_wstr := make([]u16, sz_utf16, context.temp_allocator) // the first time, it _includes_ the NUL.
  318. sz_utf16 = win32.GetCurrentDirectoryW(win32.DWORD(len(dir_buf_wstr)), raw_data(dir_buf_wstr))
  319. assert(int(sz_utf16)+1 == len(dir_buf_wstr)) // the second time, it _excludes_ the NUL.
  320. win32.ReleaseSRWLockExclusive(&cwd_lock)
  321. return win32.utf16_to_utf8(dir_buf_wstr, allocator) or_else ""
  322. }
  323. set_current_directory :: proc(path: string) -> (err: Errno) {
  324. wstr := win32.utf8_to_wstring(path)
  325. win32.AcquireSRWLockExclusive(&cwd_lock)
  326. if !win32.SetCurrentDirectoryW(wstr) {
  327. err = Errno(win32.GetLastError())
  328. }
  329. win32.ReleaseSRWLockExclusive(&cwd_lock)
  330. return
  331. }
  332. change_directory :: proc(path: string) -> (err: Errno) {
  333. runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD()
  334. wpath := win32.utf8_to_wstring(path, context.temp_allocator)
  335. if !win32.SetCurrentDirectoryW(wpath) {
  336. err = Errno(win32.GetLastError())
  337. }
  338. return
  339. }
  340. make_directory :: proc(path: string, mode: u32 = 0) -> (err: Errno) {
  341. runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD()
  342. // Mode is unused on Windows, but is needed on *nix
  343. wpath := win32.utf8_to_wstring(path, context.temp_allocator)
  344. if !win32.CreateDirectoryW(wpath, nil) {
  345. err = Errno(win32.GetLastError())
  346. }
  347. return
  348. }
  349. remove_directory :: proc(path: string) -> (err: Errno) {
  350. runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD()
  351. wpath := win32.utf8_to_wstring(path, context.temp_allocator)
  352. if !win32.RemoveDirectoryW(wpath) {
  353. err = Errno(win32.GetLastError())
  354. }
  355. return
  356. }
  357. @(private)
  358. is_abs :: proc(path: string) -> bool {
  359. if len(path) > 0 && path[0] == '/' {
  360. return true
  361. }
  362. when ODIN_OS == .Windows {
  363. if len(path) > 2 {
  364. switch path[0] {
  365. case 'A'..='Z', 'a'..='z':
  366. return path[1] == ':' && is_path_separator(path[2])
  367. }
  368. }
  369. }
  370. return false
  371. }
  372. @(private)
  373. fix_long_path :: proc(path: string) -> string {
  374. if len(path) < 248 {
  375. return path
  376. }
  377. if len(path) >= 2 && path[:2] == `\\` {
  378. return path
  379. }
  380. if !is_abs(path) {
  381. return path
  382. }
  383. prefix :: `\\?`
  384. path_buf := make([]byte, len(prefix)+len(path)+len(`\`), context.temp_allocator)
  385. copy(path_buf, prefix)
  386. n := len(path)
  387. r, w := 0, len(prefix)
  388. for r < n {
  389. switch {
  390. case is_path_separator(path[r]):
  391. r += 1
  392. case path[r] == '.' && (r+1 == n || is_path_separator(path[r+1])):
  393. r += 1
  394. case r+1 < n && path[r] == '.' && path[r+1] == '.' && (r+2 == n || is_path_separator(path[r+2])):
  395. return path
  396. case:
  397. path_buf[w] = '\\'
  398. w += 1
  399. for ; r < n && !is_path_separator(path[r]); r += 1 {
  400. path_buf[w] = path[r]
  401. w += 1
  402. }
  403. }
  404. }
  405. if w == len(`\\?\c:`) {
  406. path_buf[w] = '\\'
  407. w += 1
  408. }
  409. return string(path_buf[:w])
  410. }
  411. link :: proc(old_name, new_name: string) -> (err: Errno) {
  412. runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD()
  413. n := win32.utf8_to_wstring(fix_long_path(new_name))
  414. o := win32.utf8_to_wstring(fix_long_path(old_name))
  415. return Errno(win32.CreateHardLinkW(n, o, nil))
  416. }
  417. unlink :: proc(path: string) -> (err: Errno) {
  418. runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD()
  419. wpath := win32.utf8_to_wstring(path, context.temp_allocator)
  420. if !win32.DeleteFileW(wpath) {
  421. err = Errno(win32.GetLastError())
  422. }
  423. return
  424. }
  425. rename :: proc(old_path, new_path: string) -> (err: Errno) {
  426. runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD()
  427. from := win32.utf8_to_wstring(old_path, context.temp_allocator)
  428. to := win32.utf8_to_wstring(new_path, context.temp_allocator)
  429. if !win32.MoveFileExW(from, to, win32.MOVEFILE_REPLACE_EXISTING) {
  430. err = Errno(win32.GetLastError())
  431. }
  432. return
  433. }
  434. ftruncate :: proc(fd: Handle, length: i64) -> (err: Errno) {
  435. curr_off, e := seek(fd, 0, 1)
  436. if e != 0 {
  437. return e
  438. }
  439. defer seek(fd, curr_off, 0)
  440. _, e = seek(fd, length, 0)
  441. if e != 0 {
  442. return e
  443. }
  444. ok := win32.SetEndOfFile(win32.HANDLE(fd))
  445. if !ok {
  446. return Errno(win32.GetLastError())
  447. }
  448. return ERROR_NONE
  449. }
  450. truncate :: proc(path: string, length: i64) -> (err: Errno) {
  451. fd: Handle
  452. fd, err = open(path, O_WRONLY|O_CREATE, 0o666)
  453. if err != 0 {
  454. return
  455. }
  456. defer close(fd)
  457. err = ftruncate(fd, length)
  458. return
  459. }
  460. remove :: proc(name: string) -> Errno {
  461. p := win32.utf8_to_wstring(fix_long_path(name))
  462. err, err1: win32.DWORD
  463. if !win32.DeleteFileW(p) {
  464. err = win32.GetLastError()
  465. }
  466. if err == 0 {
  467. return 0
  468. }
  469. if !win32.RemoveDirectoryW(p) {
  470. err1 = win32.GetLastError()
  471. }
  472. if err1 == 0 {
  473. return 0
  474. }
  475. if err != err1 {
  476. a := win32.GetFileAttributesW(p)
  477. if a == ~u32(0) {
  478. err = win32.GetLastError()
  479. } else {
  480. if a & win32.FILE_ATTRIBUTE_DIRECTORY != 0 {
  481. err = err1
  482. } else if a & win32.FILE_ATTRIBUTE_READONLY != 0 {
  483. if win32.SetFileAttributesW(p, a &~ win32.FILE_ATTRIBUTE_READONLY) {
  484. err = 0
  485. if !win32.DeleteFileW(p) {
  486. err = win32.GetLastError()
  487. }
  488. }
  489. }
  490. }
  491. }
  492. return Errno(err)
  493. }
  494. pipe :: proc() -> (r, w: Handle, err: Errno) {
  495. sa: win32.SECURITY_ATTRIBUTES
  496. sa.nLength = size_of(win32.SECURITY_ATTRIBUTES)
  497. sa.bInheritHandle = true
  498. if !win32.CreatePipe((^win32.HANDLE)(&r), (^win32.HANDLE)(&w), &sa, 0) {
  499. err = Errno(win32.GetLastError())
  500. }
  501. return
  502. }