file_windows.odin 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597
  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. @(private)
  187. pread :: proc(fd: Handle, data: []byte, offset: i64) -> (int, Errno) {
  188. buf := data
  189. if len(buf) > MAX_RW {
  190. buf = buf[:MAX_RW]
  191. }
  192. curr_offset, e := seek(fd, offset, 1)
  193. if e != 0 {
  194. return 0, e
  195. }
  196. defer seek(fd, curr_offset, 0)
  197. o := win32.OVERLAPPED{
  198. OffsetHigh = u32(offset>>32),
  199. Offset = u32(offset),
  200. }
  201. // TODO(bill): Determine the correct behaviour for consoles
  202. h := win32.HANDLE(fd)
  203. done: win32.DWORD
  204. if !win32.ReadFile(h, raw_data(buf), u32(len(buf)), &done, &o) {
  205. e = Errno(win32.GetLastError())
  206. done = 0
  207. }
  208. return int(done), e
  209. }
  210. @(private)
  211. pwrite :: proc(fd: Handle, data: []byte, offset: i64) -> (int, Errno) {
  212. buf := data
  213. if len(buf) > MAX_RW {
  214. buf = buf[:MAX_RW]
  215. }
  216. curr_offset, e := seek(fd, offset, 1)
  217. if e != 0 {
  218. return 0, e
  219. }
  220. defer seek(fd, curr_offset, 0)
  221. o := win32.OVERLAPPED{
  222. OffsetHigh = u32(offset>>32),
  223. Offset = u32(offset),
  224. }
  225. h := win32.HANDLE(fd)
  226. done: win32.DWORD
  227. if !win32.WriteFile(h, raw_data(buf), u32(len(buf)), &done, &o) {
  228. e = Errno(win32.GetLastError())
  229. done = 0
  230. }
  231. return int(done), e
  232. }
  233. read_at :: proc(fd: Handle, data: []byte, offset: i64) -> (n: int, err: Errno) {
  234. if offset < 0 {
  235. return 0, ERROR_NEGATIVE_OFFSET
  236. }
  237. b, offset := data, offset
  238. for len(b) > 0 {
  239. m, e := pread(fd, b, offset)
  240. if e != 0 {
  241. err = e
  242. break
  243. }
  244. n += m
  245. b = b[m:]
  246. offset += i64(m)
  247. }
  248. return
  249. }
  250. write_at :: proc(fd: Handle, data: []byte, offset: i64) -> (n: int, err: Errno) {
  251. if offset < 0 {
  252. return 0, ERROR_NEGATIVE_OFFSET
  253. }
  254. b, offset := data, offset
  255. for len(b) > 0 {
  256. m, e := pwrite(fd, b, offset)
  257. if e != 0 {
  258. err = e
  259. break
  260. }
  261. n += m
  262. b = b[m:]
  263. offset += i64(m)
  264. }
  265. return
  266. }
  267. // NOTE(bill): Uses startup to initialize it
  268. stdin := get_std_handle(uint(win32.STD_INPUT_HANDLE))
  269. stdout := get_std_handle(uint(win32.STD_OUTPUT_HANDLE))
  270. stderr := get_std_handle(uint(win32.STD_ERROR_HANDLE))
  271. get_std_handle :: proc "contextless" (h: uint) -> Handle {
  272. fd := win32.GetStdHandle(win32.DWORD(h))
  273. return Handle(fd)
  274. }
  275. exists :: proc(path: string) -> bool {
  276. runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD()
  277. wpath := win32.utf8_to_wstring(path, context.temp_allocator)
  278. attribs := win32.GetFileAttributesW(wpath)
  279. return i32(attribs) != win32.INVALID_FILE_ATTRIBUTES
  280. }
  281. is_file :: proc(path: string) -> bool {
  282. runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD()
  283. wpath := win32.utf8_to_wstring(path, context.temp_allocator)
  284. attribs := win32.GetFileAttributesW(wpath)
  285. if i32(attribs) != win32.INVALID_FILE_ATTRIBUTES {
  286. return attribs & win32.FILE_ATTRIBUTE_DIRECTORY == 0
  287. }
  288. return false
  289. }
  290. is_dir :: proc(path: string) -> bool {
  291. runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD()
  292. wpath := win32.utf8_to_wstring(path, context.temp_allocator)
  293. attribs := win32.GetFileAttributesW(wpath)
  294. if i32(attribs) != win32.INVALID_FILE_ATTRIBUTES {
  295. return attribs & win32.FILE_ATTRIBUTE_DIRECTORY != 0
  296. }
  297. return false
  298. }
  299. // NOTE(tetra): GetCurrentDirectory is not thread safe with SetCurrentDirectory and GetFullPathName
  300. @private cwd_lock := win32.SRWLOCK{} // zero is initialized
  301. get_current_directory :: proc(allocator := context.allocator) -> string {
  302. win32.AcquireSRWLockExclusive(&cwd_lock)
  303. runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD(ignore = context.temp_allocator == allocator)
  304. sz_utf16 := win32.GetCurrentDirectoryW(0, nil)
  305. dir_buf_wstr := make([]u16, sz_utf16, context.temp_allocator) // the first time, it _includes_ the NUL.
  306. sz_utf16 = win32.GetCurrentDirectoryW(win32.DWORD(len(dir_buf_wstr)), raw_data(dir_buf_wstr))
  307. assert(int(sz_utf16)+1 == len(dir_buf_wstr)) // the second time, it _excludes_ the NUL.
  308. win32.ReleaseSRWLockExclusive(&cwd_lock)
  309. return win32.utf16_to_utf8(dir_buf_wstr, allocator) or_else ""
  310. }
  311. set_current_directory :: proc(path: string) -> (err: Errno) {
  312. wstr := win32.utf8_to_wstring(path)
  313. win32.AcquireSRWLockExclusive(&cwd_lock)
  314. if !win32.SetCurrentDirectoryW(wstr) {
  315. err = Errno(win32.GetLastError())
  316. }
  317. win32.ReleaseSRWLockExclusive(&cwd_lock)
  318. return
  319. }
  320. change_directory :: proc(path: string) -> (err: Errno) {
  321. runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD()
  322. wpath := win32.utf8_to_wstring(path, context.temp_allocator)
  323. if !win32.SetCurrentDirectoryW(wpath) {
  324. err = Errno(win32.GetLastError())
  325. }
  326. return
  327. }
  328. make_directory :: proc(path: string, mode: u32 = 0) -> (err: Errno) {
  329. runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD()
  330. // Mode is unused on Windows, but is needed on *nix
  331. wpath := win32.utf8_to_wstring(path, context.temp_allocator)
  332. if !win32.CreateDirectoryW(wpath, nil) {
  333. err = Errno(win32.GetLastError())
  334. }
  335. return
  336. }
  337. remove_directory :: proc(path: string) -> (err: Errno) {
  338. runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD()
  339. wpath := win32.utf8_to_wstring(path, context.temp_allocator)
  340. if !win32.RemoveDirectoryW(wpath) {
  341. err = Errno(win32.GetLastError())
  342. }
  343. return
  344. }
  345. @(private)
  346. is_abs :: proc(path: string) -> bool {
  347. if len(path) > 0 && path[0] == '/' {
  348. return true
  349. }
  350. when ODIN_OS == .Windows {
  351. if len(path) > 2 {
  352. switch path[0] {
  353. case 'A'..='Z', 'a'..='z':
  354. return path[1] == ':' && is_path_separator(path[2])
  355. }
  356. }
  357. }
  358. return false
  359. }
  360. @(private)
  361. fix_long_path :: proc(path: string) -> string {
  362. if len(path) < 248 {
  363. return path
  364. }
  365. if len(path) >= 2 && path[:2] == `\\` {
  366. return path
  367. }
  368. if !is_abs(path) {
  369. return path
  370. }
  371. prefix :: `\\?`
  372. path_buf := make([]byte, len(prefix)+len(path)+len(`\`), context.temp_allocator)
  373. copy(path_buf, prefix)
  374. n := len(path)
  375. r, w := 0, len(prefix)
  376. for r < n {
  377. switch {
  378. case is_path_separator(path[r]):
  379. r += 1
  380. case path[r] == '.' && (r+1 == n || is_path_separator(path[r+1])):
  381. r += 1
  382. case r+1 < n && path[r] == '.' && path[r+1] == '.' && (r+2 == n || is_path_separator(path[r+2])):
  383. return path
  384. case:
  385. path_buf[w] = '\\'
  386. w += 1
  387. for ; r < n && !is_path_separator(path[r]); r += 1 {
  388. path_buf[w] = path[r]
  389. w += 1
  390. }
  391. }
  392. }
  393. if w == len(`\\?\c:`) {
  394. path_buf[w] = '\\'
  395. w += 1
  396. }
  397. return string(path_buf[:w])
  398. }
  399. link :: proc(old_name, new_name: string) -> (err: Errno) {
  400. runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD()
  401. n := win32.utf8_to_wstring(fix_long_path(new_name))
  402. o := win32.utf8_to_wstring(fix_long_path(old_name))
  403. return Errno(win32.CreateHardLinkW(n, o, nil))
  404. }
  405. unlink :: proc(path: string) -> (err: Errno) {
  406. runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD()
  407. wpath := win32.utf8_to_wstring(path, context.temp_allocator)
  408. if !win32.DeleteFileW(wpath) {
  409. err = Errno(win32.GetLastError())
  410. }
  411. return
  412. }
  413. rename :: proc(old_path, new_path: string) -> (err: Errno) {
  414. runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD()
  415. from := win32.utf8_to_wstring(old_path, context.temp_allocator)
  416. to := win32.utf8_to_wstring(new_path, context.temp_allocator)
  417. if !win32.MoveFileExW(from, to, win32.MOVEFILE_REPLACE_EXISTING) {
  418. err = Errno(win32.GetLastError())
  419. }
  420. return
  421. }
  422. ftruncate :: proc(fd: Handle, length: i64) -> (err: Errno) {
  423. curr_off, e := seek(fd, 0, 1)
  424. if e != 0 {
  425. return e
  426. }
  427. defer seek(fd, curr_off, 0)
  428. _, e = seek(fd, length, 0)
  429. if e != 0 {
  430. return e
  431. }
  432. ok := win32.SetEndOfFile(win32.HANDLE(fd))
  433. if !ok {
  434. return Errno(win32.GetLastError())
  435. }
  436. return ERROR_NONE
  437. }
  438. truncate :: proc(path: string, length: i64) -> (err: Errno) {
  439. fd: Handle
  440. fd, err = open(path, O_WRONLY|O_CREATE, 0o666)
  441. if err != 0 {
  442. return
  443. }
  444. defer close(fd)
  445. err = ftruncate(fd, length)
  446. return
  447. }
  448. remove :: proc(name: string) -> Errno {
  449. p := win32.utf8_to_wstring(fix_long_path(name))
  450. err, err1: win32.DWORD
  451. if !win32.DeleteFileW(p) {
  452. err = win32.GetLastError()
  453. }
  454. if err == 0 {
  455. return 0
  456. }
  457. if !win32.RemoveDirectoryW(p) {
  458. err1 = win32.GetLastError()
  459. }
  460. if err1 == 0 {
  461. return 0
  462. }
  463. if err != err1 {
  464. a := win32.GetFileAttributesW(p)
  465. if a == ~u32(0) {
  466. err = win32.GetLastError()
  467. } else {
  468. if a & win32.FILE_ATTRIBUTE_DIRECTORY != 0 {
  469. err = err1
  470. } else if a & win32.FILE_ATTRIBUTE_READONLY != 0 {
  471. if win32.SetFileAttributesW(p, a &~ win32.FILE_ATTRIBUTE_READONLY) {
  472. err = 0
  473. if !win32.DeleteFileW(p) {
  474. err = win32.GetLastError()
  475. }
  476. }
  477. }
  478. }
  479. }
  480. return Errno(err)
  481. }
  482. pipe :: proc() -> (r, w: Handle, err: Errno) {
  483. sa: win32.SECURITY_ATTRIBUTES
  484. sa.nLength = size_of(win32.SECURITY_ATTRIBUTES)
  485. sa.bInheritHandle = true
  486. if !win32.CreatePipe((^win32.HANDLE)(&r), (^win32.HANDLE)(&w), &sa, 0) {
  487. err = Errno(win32.GetLastError())
  488. }
  489. return
  490. }