path_linux.odin 5.6 KB

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