path_linux.odin 5.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204
  1. //+private
  2. package os2
  3. import "core:strconv"
  4. import "base:runtime"
  5. import "core:sys/linux"
  6. _Path_Separator :: '/'
  7. _Path_Separator_String :: "/"
  8. _Path_List_Separator :: ':'
  9. _OPENDIR_FLAGS : linux.Open_Flags : {.NONBLOCK, .DIRECTORY, .LARGEFILE, .CLOEXEC}
  10. _is_path_separator :: proc(c: byte) -> bool {
  11. return c == '/'
  12. }
  13. _mkdir :: proc(path: string, perm: int) -> Error {
  14. TEMP_ALLOCATOR_GUARD()
  15. path_cstr := temp_cstring(path) or_return
  16. return _get_platform_error(linux.mkdir(path_cstr, transmute(linux.Mode)u32(perm)))
  17. }
  18. _mkdir_all :: proc(path: string, perm: int) -> Error {
  19. mkdirat :: proc(dfd: linux.Fd, path: []u8, perm: int, has_created: ^bool) -> Error {
  20. i: int
  21. for ; i < len(path) - 1 && path[i] != '/'; i += 1 {}
  22. if i == 0 {
  23. return _get_platform_error(linux.close(dfd))
  24. }
  25. path[i] = 0
  26. new_dfd, errno := linux.openat(dfd, cstring(&path[0]), _OPENDIR_FLAGS)
  27. #partial switch errno {
  28. case .ENOENT:
  29. if errno = linux.mkdirat(dfd, cstring(&path[0]), transmute(linux.Mode)u32(perm)); errno != .NONE {
  30. return _get_platform_error(errno)
  31. }
  32. has_created^ = true
  33. if new_dfd, errno = linux.openat(dfd, cstring(&path[0]), _OPENDIR_FLAGS); errno != .NONE {
  34. return _get_platform_error(errno)
  35. }
  36. fallthrough
  37. case .NONE:
  38. if errno = linux.close(dfd); errno != .NONE {
  39. return _get_platform_error(errno)
  40. }
  41. // skip consecutive '/'
  42. for i += 1; i < len(path) && path[i] == '/'; i += 1 {}
  43. return mkdirat(new_dfd, path[i:], perm, has_created)
  44. }
  45. return _get_platform_error(errno)
  46. }
  47. TEMP_ALLOCATOR_GUARD()
  48. // need something we can edit, and use to generate cstrings
  49. path_bytes := make([]u8, len(path) + 1, temp_allocator())
  50. // zero terminate the byte slice to make it a valid cstring
  51. copy(path_bytes, path)
  52. path_bytes[len(path)] = 0
  53. dfd: linux.Fd
  54. errno: linux.Errno
  55. if path_bytes[0] == '/' {
  56. dfd, errno = linux.open("/", _OPENDIR_FLAGS)
  57. path_bytes = path_bytes[1:]
  58. } else {
  59. dfd, errno = linux.open(".", _OPENDIR_FLAGS)
  60. }
  61. if errno != .NONE {
  62. return _get_platform_error(errno)
  63. }
  64. has_created: bool
  65. mkdirat(dfd, path_bytes, perm, &has_created) or_return
  66. return nil if has_created else .Exist
  67. }
  68. dirent64 :: struct {
  69. d_ino: u64,
  70. d_off: u64,
  71. d_reclen: u16,
  72. d_type: u8,
  73. d_name: [1]u8,
  74. }
  75. _remove_all :: proc(path: string) -> Error {
  76. DT_DIR :: 4
  77. remove_all_dir :: proc(dfd: linux.Fd) -> Error {
  78. n := 64
  79. buf := make([]u8, n)
  80. defer delete(buf)
  81. loop: for {
  82. buflen, errno := linux.getdents(dfd, buf[:])
  83. #partial switch errno {
  84. case .EINVAL:
  85. delete(buf)
  86. n *= 2
  87. buf = make([]u8, n)
  88. continue loop
  89. case .NONE:
  90. if buflen == 0 { break loop }
  91. case:
  92. return _get_platform_error(errno)
  93. }
  94. d: ^dirent64
  95. for i := 0; i < buflen; i += int(d.d_reclen) {
  96. d = (^dirent64)(rawptr(&buf[i]))
  97. d_name_cstr := cstring(&d.d_name[0])
  98. buf_len := uintptr(d.d_reclen) - offset_of(d.d_name)
  99. /* check for current directory (.) */
  100. #no_bounds_check if buf_len > 1 && d.d_name[0] == '.' && d.d_name[1] == 0 {
  101. continue
  102. }
  103. /* check for parent directory (..) */
  104. #no_bounds_check if buf_len > 2 && d.d_name[0] == '.' && d.d_name[1] == '.' && d.d_name[2] == 0 {
  105. continue
  106. }
  107. switch d.d_type {
  108. case DT_DIR:
  109. new_dfd: linux.Fd
  110. new_dfd, errno = linux.openat(dfd, d_name_cstr, _OPENDIR_FLAGS)
  111. if errno != .NONE {
  112. return _get_platform_error(errno)
  113. }
  114. defer linux.close(new_dfd)
  115. remove_all_dir(new_dfd) or_return
  116. errno = linux.unlinkat(dfd, d_name_cstr, {.REMOVEDIR})
  117. case:
  118. errno = linux.unlinkat(dfd, d_name_cstr, nil)
  119. }
  120. if errno != .NONE {
  121. return _get_platform_error(errno)
  122. }
  123. }
  124. }
  125. return nil
  126. }
  127. TEMP_ALLOCATOR_GUARD()
  128. path_cstr := temp_cstring(path) or_return
  129. fd, errno := linux.open(path_cstr, _OPENDIR_FLAGS)
  130. #partial switch errno {
  131. case .NONE:
  132. break
  133. case .ENOTDIR:
  134. return _get_platform_error(linux.unlink(path_cstr))
  135. case:
  136. return _get_platform_error(errno)
  137. }
  138. defer linux.close(fd)
  139. remove_all_dir(fd) or_return
  140. return _get_platform_error(linux.rmdir(path_cstr))
  141. }
  142. _get_working_directory :: proc(allocator: runtime.Allocator) -> (string, Error) {
  143. // NOTE(tetra): I would use PATH_MAX here, but I was not able to find
  144. // an authoritative value for it across all systems.
  145. // The largest value I could find was 4096, so might as well use the page size.
  146. // NOTE(jason): Avoiding libc, so just use 4096 directly
  147. PATH_MAX :: 4096
  148. buf := make([dynamic]u8, PATH_MAX, allocator)
  149. for {
  150. #no_bounds_check n, errno := linux.getcwd(buf[:])
  151. if errno == .NONE {
  152. return string(buf[:n-1]), nil
  153. }
  154. if errno != .ERANGE {
  155. return "", _get_platform_error(errno)
  156. }
  157. resize(&buf, len(buf)+PATH_MAX)
  158. }
  159. unreachable()
  160. }
  161. _set_working_directory :: proc(dir: string) -> Error {
  162. dir_cstr := temp_cstring(dir) or_return
  163. return _get_platform_error(linux.chdir(dir_cstr))
  164. }
  165. _get_full_path :: proc(fd: linux.Fd, allocator: runtime.Allocator) -> (fullpath: string, err: Error) {
  166. PROC_FD_PATH :: "/proc/self/fd/"
  167. buf: [32]u8
  168. copy(buf[:], PROC_FD_PATH)
  169. strconv.itoa(buf[len(PROC_FD_PATH):], int(fd))
  170. if fullpath, err = _read_link_cstr(cstring(&buf[0]), allocator); err != nil || fullpath[0] != '/' {
  171. delete(fullpath, allocator)
  172. fullpath = ""
  173. }
  174. return
  175. }