path_windows.odin 8.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354
  1. #+private
  2. package os2
  3. import "base:runtime"
  4. import "core:strings"
  5. import win32 "core:sys/windows"
  6. _Path_Separator :: '\\'
  7. _Path_Separator_String :: "\\"
  8. _Path_List_Separator :: ';'
  9. _is_path_separator :: proc(c: byte) -> bool {
  10. return c == '\\' || c == '/'
  11. }
  12. _mkdir :: proc(name: string, perm: int) -> Error {
  13. temp_allocator := TEMP_ALLOCATOR_GUARD({})
  14. if !win32.CreateDirectoryW(_fix_long_path(name, temp_allocator) or_return, nil) {
  15. return _get_platform_error()
  16. }
  17. return nil
  18. }
  19. _mkdir_all :: proc(path: string, perm: int) -> Error {
  20. fix_root_directory :: proc(p: string) -> (s: string, allocated: bool, err: runtime.Allocator_Error) {
  21. if len(p) == len(`\\?\c:`) {
  22. if is_path_separator(p[0]) && is_path_separator(p[1]) && p[2] == '?' && is_path_separator(p[3]) && p[5] == ':' {
  23. s = concatenate({p, `\`}, file_allocator()) or_return
  24. allocated = true
  25. return
  26. }
  27. }
  28. return p, false, nil
  29. }
  30. temp_allocator := TEMP_ALLOCATOR_GUARD({})
  31. dir_stat, err := stat(path, temp_allocator)
  32. if err == nil {
  33. if dir_stat.type == .Directory {
  34. return nil
  35. }
  36. return .Exist
  37. }
  38. i := len(path)
  39. for i > 0 && is_path_separator(path[i-1]) {
  40. i -= 1
  41. }
  42. j := i
  43. for j > 0 && !is_path_separator(path[j-1]) {
  44. j -= 1
  45. }
  46. if j > 1 {
  47. new_path, allocated := fix_root_directory(path[:j-1]) or_return
  48. defer if allocated {
  49. delete(new_path, file_allocator())
  50. }
  51. mkdir_all(new_path, perm) or_return
  52. }
  53. err = mkdir(path, perm)
  54. if err != nil {
  55. new_dir_stat, err1 := lstat(path, temp_allocator)
  56. if err1 == nil && new_dir_stat.type == .Directory {
  57. return nil
  58. }
  59. return err
  60. }
  61. return nil
  62. }
  63. _remove_all :: proc(path: string) -> Error {
  64. if path == "" {
  65. return nil
  66. }
  67. err := remove(path)
  68. if err == nil || err == .Not_Exist {
  69. return nil
  70. }
  71. temp_allocator := TEMP_ALLOCATOR_GUARD({})
  72. dir := win32_utf8_to_wstring(path, temp_allocator) or_return
  73. empty: [1]u16
  74. file_op := win32.SHFILEOPSTRUCTW {
  75. nil,
  76. win32.FO_DELETE,
  77. dir,
  78. cstring16(&empty[0]),
  79. win32.FOF_NOCONFIRMATION | win32.FOF_NOERRORUI | win32.FOF_SILENT,
  80. false,
  81. nil,
  82. cstring16(&empty[0]),
  83. }
  84. res := win32.SHFileOperationW(&file_op)
  85. if res != 0 {
  86. return _get_platform_error()
  87. }
  88. return nil
  89. }
  90. @private cwd_lock: win32.SRWLOCK // zero is initialized
  91. _get_working_directory :: proc(allocator: runtime.Allocator) -> (dir: string, err: Error) {
  92. win32.AcquireSRWLockExclusive(&cwd_lock)
  93. temp_allocator := TEMP_ALLOCATOR_GUARD({ allocator })
  94. sz_utf16 := win32.GetCurrentDirectoryW(0, nil)
  95. dir_buf_wstr := make([]u16, sz_utf16, temp_allocator) or_return
  96. sz_utf16 = win32.GetCurrentDirectoryW(win32.DWORD(len(dir_buf_wstr)), raw_data(dir_buf_wstr))
  97. assert(int(sz_utf16)+1 == len(dir_buf_wstr)) // the second time, it _excludes_ the NUL.
  98. win32.ReleaseSRWLockExclusive(&cwd_lock)
  99. return win32_utf16_to_utf8(dir_buf_wstr, allocator)
  100. }
  101. _set_working_directory :: proc(dir: string) -> (err: Error) {
  102. temp_allocator := TEMP_ALLOCATOR_GUARD({})
  103. wstr := win32_utf8_to_wstring(dir, temp_allocator) or_return
  104. win32.AcquireSRWLockExclusive(&cwd_lock)
  105. if !win32.SetCurrentDirectoryW(wstr) {
  106. err = _get_platform_error()
  107. }
  108. win32.ReleaseSRWLockExclusive(&cwd_lock)
  109. return
  110. }
  111. _get_executable_path :: proc(allocator: runtime.Allocator) -> (path: string, err: Error) {
  112. temp_allocator := TEMP_ALLOCATOR_GUARD({ allocator })
  113. buf := make([dynamic]u16, 512, temp_allocator) or_return
  114. for {
  115. ret := win32.GetModuleFileNameW(nil, raw_data(buf), win32.DWORD(len(buf)))
  116. if ret == 0 {
  117. err = _get_platform_error()
  118. return
  119. }
  120. if ret == win32.DWORD(len(buf)) && win32.GetLastError() == win32.ERROR_INSUFFICIENT_BUFFER {
  121. resize(&buf, len(buf)*2) or_return
  122. continue
  123. }
  124. return win32_utf16_to_utf8(buf[:ret], allocator)
  125. }
  126. }
  127. can_use_long_paths: bool
  128. @(init)
  129. init_long_path_support :: proc "contextless" () {
  130. can_use_long_paths = false
  131. key: win32.HKEY
  132. res := win32.RegOpenKeyExW(win32.HKEY_LOCAL_MACHINE, win32.L(`SYSTEM\CurrentControlSet\Control\FileSystem`), 0, win32.KEY_READ, &key)
  133. defer win32.RegCloseKey(key)
  134. if res != 0 {
  135. return
  136. }
  137. value: u32
  138. size := u32(size_of(value))
  139. res = win32.RegGetValueW(
  140. key,
  141. nil,
  142. win32.L("LongPathsEnabled"),
  143. win32.RRF_RT_ANY,
  144. nil,
  145. &value,
  146. &size,
  147. )
  148. if res != 0 {
  149. return
  150. }
  151. if value == 1 {
  152. can_use_long_paths = true
  153. }
  154. }
  155. @(require_results)
  156. _fix_long_path_slice :: proc(path: string, allocator: runtime.Allocator) -> ([]u16, runtime.Allocator_Error) {
  157. return win32_utf8_to_utf16(_fix_long_path_internal(path), allocator)
  158. }
  159. @(require_results)
  160. _fix_long_path :: proc(path: string, allocator: runtime.Allocator) -> (win32.wstring, runtime.Allocator_Error) {
  161. return win32_utf8_to_wstring(_fix_long_path_internal(path), allocator)
  162. }
  163. @(require_results)
  164. _fix_long_path_internal :: proc(path: string) -> string {
  165. if can_use_long_paths {
  166. return path
  167. }
  168. // When using win32 to create a directory, the path
  169. // cannot be too long that you cannot append an 8.3
  170. // file name, because MAX_PATH is 260, 260-12 = 248
  171. if len(path) < 248 {
  172. return path
  173. }
  174. // UNC paths do not need to be modified
  175. if len(path) >= 2 && path[:2] == `\\` {
  176. return path
  177. }
  178. if !_is_absolute_path(path) { // relative path
  179. return path
  180. }
  181. temp_allocator := TEMP_ALLOCATOR_GUARD({})
  182. PREFIX :: `\\?`
  183. path_buf := make([]byte, len(PREFIX)+len(path)+1, temp_allocator)
  184. copy(path_buf, PREFIX)
  185. n := len(path)
  186. r, w := 0, len(PREFIX)
  187. for r < n {
  188. switch {
  189. case is_path_separator(path[r]):
  190. r += 1
  191. case path[r] == '.' && (r+1 == n || is_path_separator(path[r+1])):
  192. // \.\
  193. r += 1
  194. case r+1 < n && path[r] == '.' && path[r+1] == '.' && (r+2 == n || is_path_separator(path[r+2])):
  195. // Skip \..\ paths
  196. return path
  197. case:
  198. path_buf[w] = '\\'
  199. w += 1
  200. for r < n && !is_path_separator(path[r]) {
  201. path_buf[w] = path[r]
  202. r += 1
  203. w += 1
  204. }
  205. }
  206. }
  207. // Root directories require a trailing \
  208. if w == len(`\\?\c:`) {
  209. path_buf[w] = '\\'
  210. w += 1
  211. }
  212. return string(path_buf[:w])
  213. }
  214. _are_paths_identical :: strings.equal_fold
  215. _clean_path_handle_start :: proc(path: string, buffer: []u8) -> (rooted: bool, start: int) {
  216. // Preserve rooted paths.
  217. start = _volume_name_len(path)
  218. if start > 0 {
  219. rooted = true
  220. if len(path) > start && _is_path_separator(path[start]) {
  221. // Take `C:` to `C:\`.
  222. start += 1
  223. }
  224. copy(buffer, path[:start])
  225. for n in 0..<start {
  226. if _is_path_separator(buffer[n]) {
  227. buffer[n] = _Path_Separator
  228. }
  229. }
  230. }
  231. return
  232. }
  233. _is_absolute_path :: proc(path: string) -> bool {
  234. if _is_reserved_name(path) {
  235. return true
  236. }
  237. l := _volume_name_len(path)
  238. if l == 0 {
  239. return false
  240. }
  241. path := path
  242. path = path[l:]
  243. if path == "" {
  244. return false
  245. }
  246. return _is_path_separator(path[0])
  247. }
  248. _get_absolute_path :: proc(path: string, allocator: runtime.Allocator) -> (absolute_path: string, err: Error) {
  249. rel := path
  250. if rel == "" {
  251. rel = "."
  252. }
  253. temp_allocator := TEMP_ALLOCATOR_GUARD({ allocator })
  254. rel_utf16 := win32.utf8_to_utf16(rel, temp_allocator)
  255. n := win32.GetFullPathNameW(cstring16(raw_data(rel_utf16)), 0, nil, nil)
  256. if n == 0 {
  257. return "", Platform_Error(win32.GetLastError())
  258. }
  259. buf := make([]u16, n, temp_allocator) or_return
  260. n = win32.GetFullPathNameW(cstring16(raw_data(rel_utf16)), u32(n), cstring16(raw_data(buf)), nil)
  261. if n == 0 {
  262. return "", Platform_Error(win32.GetLastError())
  263. }
  264. return win32.utf16_to_utf8(buf, allocator)
  265. }
  266. _get_relative_path_handle_start :: proc(base, target: string) -> bool {
  267. base_root := base[:_volume_name_len(base)]
  268. target_root := target[:_volume_name_len(target)]
  269. return strings.equal_fold(base_root, target_root)
  270. }
  271. _get_common_path_len :: proc(base, target: string) -> int {
  272. i := 0
  273. end := min(len(base), len(target))
  274. for j in 0..=end {
  275. if j == end || _is_path_separator(base[j]) {
  276. if strings.equal_fold(base[i:j], target[i:j]) {
  277. i = j
  278. } else {
  279. break
  280. }
  281. }
  282. }
  283. return i
  284. }
  285. _split_path :: proc(path: string) -> (dir, file: string) {
  286. vol_len := _volume_name_len(path)
  287. i := len(path) - 1
  288. for i >= vol_len && !_is_path_separator(path[i]) {
  289. i -= 1
  290. }
  291. if i == vol_len {
  292. return path[:i+1], path[i+1:]
  293. } else if i > vol_len {
  294. return path[:i], path[i+1:]
  295. }
  296. return "", path
  297. }