2
0

file_windows.odin 13 KB

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