FilePathWindows.swift 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538
  1. /*
  2. This source file is part of the Swift System open source project
  3. Copyright (c) 2020 Apple Inc. and the Swift System project authors
  4. Licensed under Apache License v2.0 with Runtime Library Exception
  5. See https://swift.org/LICENSE.txt for license information
  6. */
  7. internal struct _ParsedWindowsRoot {
  8. var rootEnd: SystemString.Index
  9. // TODO: Remove when I normalize to always (except `C:`)
  10. // have trailing separator
  11. var relativeBegin: SystemString.Index
  12. var drive: SystemChar?
  13. var fullyQualified: Bool
  14. var deviceSigil: SystemChar?
  15. var host: Range<SystemString.Index>?
  16. var volume: Range<SystemString.Index>?
  17. }
  18. extension _ParsedWindowsRoot {
  19. static func traditional(
  20. drive: SystemChar?, fullQualified: Bool, endingAt idx: SystemString.Index
  21. ) -> _ParsedWindowsRoot {
  22. _ParsedWindowsRoot(
  23. rootEnd: idx,
  24. relativeBegin: idx,
  25. drive: drive,
  26. fullyQualified: fullQualified,
  27. deviceSigil: nil,
  28. host: nil,
  29. volume: nil)
  30. }
  31. static func unc(
  32. deviceSigil: SystemChar?,
  33. server: Range<SystemString.Index>,
  34. share: Range<SystemString.Index>,
  35. endingAt end: SystemString.Index,
  36. relativeBegin relBegin: SystemString.Index
  37. ) -> _ParsedWindowsRoot {
  38. _ParsedWindowsRoot(
  39. rootEnd: end,
  40. relativeBegin: relBegin,
  41. drive: nil,
  42. fullyQualified: true,
  43. deviceSigil: deviceSigil,
  44. host: server,
  45. volume: share)
  46. }
  47. static func device(
  48. deviceSigil: SystemChar,
  49. volume: Range<SystemString.Index>,
  50. endingAt end: SystemString.Index,
  51. relativeBegin relBegin: SystemString.Index
  52. ) -> _ParsedWindowsRoot {
  53. _ParsedWindowsRoot(
  54. rootEnd: end,
  55. relativeBegin: relBegin,
  56. drive: nil,
  57. fullyQualified: true,
  58. deviceSigil: deviceSigil,
  59. host: nil,
  60. volume: volume)
  61. }
  62. }
  63. struct _Lexer {
  64. var slice: Slice<SystemString>
  65. init(_ str: SystemString) {
  66. self.slice = str[...]
  67. }
  68. var backslash: SystemChar { .backslash }
  69. // Try to eat a backslash, returns false if nothing happened
  70. mutating func eatBackslash() -> Bool {
  71. slice._eat(.backslash) != nil
  72. }
  73. // Try to consume a drive letter and subsequent `:`.
  74. mutating func eatDrive() -> SystemChar? {
  75. let copy = slice
  76. if let d = slice._eat(if: { $0.isLetter }), slice._eat(.colon) != nil {
  77. return d
  78. }
  79. // Restore slice
  80. slice = copy
  81. return nil
  82. }
  83. // Try to consume a device sigil (stand-alone . or ?)
  84. mutating func eatSigil() -> SystemChar? {
  85. let copy = slice
  86. guard let sigil = slice._eat(.question) ?? slice._eat(.dot) else {
  87. return nil
  88. }
  89. // Check for something like .hidden or ?question
  90. guard isEmpty || slice.first == backslash else {
  91. slice = copy
  92. return nil
  93. }
  94. return sigil
  95. }
  96. // Try to consume an explicit "UNC" directory
  97. mutating func eatUNC() -> Bool {
  98. slice._eatSequence("UNC".unicodeScalars.lazy.map { SystemChar(ascii: $0) }) != nil
  99. }
  100. // Eat everything up to but not including a backslash or null
  101. mutating func eatComponent() -> Range<SystemString.Index> {
  102. let backslash = self.backslash
  103. let component = slice._eatWhile({ $0 != backslash })
  104. ?? slice[slice.startIndex ..< slice.startIndex]
  105. return component.indices
  106. }
  107. var isEmpty: Bool {
  108. return slice.isEmpty
  109. }
  110. var current: SystemString.Index { slice.startIndex }
  111. mutating func clear() {
  112. // TODO: Intern empty system string
  113. self = _Lexer(SystemString())
  114. }
  115. mutating func reset(to: SystemString, at: SystemString.Index) {
  116. self.slice = to[at...]
  117. }
  118. }
  119. internal struct WindowsRootInfo {
  120. // The "volume" of a root. For UNC paths, this is also known as the "share".
  121. internal enum Volume: Equatable {
  122. /// No volume specified
  123. ///
  124. /// * Traditional root relative to the current drive: `\`,
  125. /// * Omitted volume from other forms: `\\.\`, `\\.\UNC\server\\`, `\\server\\`
  126. case empty
  127. // TODO: NT paths? Admin paths using `$`?
  128. /// A specified drive.
  129. ///
  130. /// * Traditional disk: `C:\`, `C:`
  131. /// * Device disk: `\\.\C:\`, `\\?\C:\`
  132. /// * UNC: `\\server\e:\`, `\\?\UNC\server\e:\`
  133. case drive(Character)
  134. // TODO: GUID type?
  135. /// A volume with a GUID in a non-traditional path
  136. ///
  137. /// * UNC: `\\host\Volume{0000-...}\`, `\\.\UNC\host\Volume{0000-...}\`
  138. /// * Device roots: `\\.\Volume{0000-...}\`, `\\?\Volume{000-...}\`
  139. case guid(String)
  140. // TODO: Legacy DOS devices, such as COM1?
  141. /// Device object or share name
  142. ///
  143. /// * Device roots: `\\.\BootPartition\`
  144. /// * UNC: `\\host\volume\`
  145. case volume(String)
  146. // TODO: Should legacy DOS devices be detected and/or converted at construction time?
  147. // TODO: What about NT paths: `\??\`
  148. }
  149. /// Represents the syntactic form of the path
  150. internal enum Form: Equatable {
  151. /// Traditional DOS roots: `C:\`, `C:`, and `\`
  152. case traditional(fullyQualified: Bool) // `C:\`, `C:`, `\`
  153. /// UNC syntactic form: `\\server\share\`
  154. case unc
  155. /// DOS device syntactic form: `\\?\BootPartition`, `\\.\C:\`, `\\?\UNC\server\share`
  156. case device(sigil: Character)
  157. // TODO: NT?
  158. }
  159. /// The host for UNC paths, else `nil`.
  160. internal var host: String?
  161. /// The specified volume (or UNC share) for the root
  162. internal var volume: Volume
  163. /// The syntactic form the root is in
  164. internal var form: Form
  165. init(host: String?, volume: Volume, form: Form) {
  166. self.host = host
  167. self.volume = volume
  168. self.form = form
  169. checkInvariants()
  170. }
  171. }
  172. extension _ParsedWindowsRoot {
  173. fileprivate func volumeInfo(_ root: SystemString) -> WindowsRootInfo.Volume {
  174. if let d = self.drive {
  175. return .drive(Character(d.asciiScalar!))
  176. }
  177. guard let vol = self.volume, !vol.isEmpty else { return .empty }
  178. // TODO: check for GUID
  179. // TODO: check for drive
  180. return .volume(root[vol].string)
  181. }
  182. }
  183. extension WindowsRootInfo {
  184. internal init(_ root: SystemString, _ parsed: _ParsedWindowsRoot) {
  185. self.volume = parsed.volumeInfo(root)
  186. if let host = parsed.host {
  187. self.host = root[host].string
  188. } else {
  189. self.host = nil
  190. }
  191. if let sig = parsed.deviceSigil {
  192. self.form = .device(sigil: Character(sig.asciiScalar!))
  193. } else if parsed.host != nil {
  194. assert(parsed.volume != nil)
  195. self.form = .unc
  196. } else {
  197. self.form = .traditional(fullyQualified: parsed.fullyQualified)
  198. }
  199. }
  200. }
  201. extension WindowsRootInfo {
  202. /// NOT `\foo\bar` nor `C:foo\bar`
  203. internal var isFullyQualified: Bool {
  204. return form != .traditional(fullyQualified: false)
  205. }
  206. ///
  207. /// `\\server\share\foo\bar.exe`, `\\.\UNC\server\share\foo\bar.exe`
  208. internal var isUNC: Bool {
  209. host != nil
  210. }
  211. ///
  212. /// `\foo\bar.exe`
  213. internal var isTraditionalRooted: Bool {
  214. form == .traditional(fullyQualified: false) && volume == .empty
  215. }
  216. ///
  217. /// `C:foo\bar.exe`
  218. internal var isTraditionalDriveRelative: Bool {
  219. switch (form, volume) {
  220. case (.traditional(fullyQualified: false), .drive(_)): return true
  221. default: return false
  222. }
  223. }
  224. // TODO: Should this be component?
  225. func formPath() -> FilePath {
  226. fatalError("Unimplemented")
  227. }
  228. // static func traditional(
  229. // drive: Character?, fullyQualified: Bool
  230. // ) -> WindowsRootInfo {
  231. // let vol: Volume
  232. // if let d = Character {
  233. // vol = .drive(d)
  234. // } else {
  235. // vol = .relative
  236. // }
  237. //
  238. // return WindowsRootInfo(
  239. // volume: .relative, form: .traditional(fullyQualified: false))
  240. // }
  241. internal func checkInvariants() {
  242. switch form {
  243. case .traditional(let qual):
  244. precondition(host == nil)
  245. switch volume {
  246. case .empty:
  247. precondition(!qual)
  248. break
  249. case .drive(_): break
  250. default: preconditionFailure()
  251. }
  252. case .unc:
  253. precondition(host != nil)
  254. case .device(_): break
  255. }
  256. }
  257. }
  258. extension SystemString {
  259. // TODO: Or, should I always inline this to remove some of the bookeeping?
  260. private func _parseWindowsRootInternal() -> _ParsedWindowsRoot? {
  261. assert(_windowsPaths)
  262. /*
  263. Windows root: device or UNC or DOS
  264. device: (`\\.` or `\\?`) `\` (drive or guid or UNC-link)
  265. drive: letter `:`
  266. guid: `Volume{` (hex-digit or `-`)* `}`
  267. UNC-link: `UNC\` UNC-volume
  268. UNC: `\\` UNC-volume
  269. UNC-volume: server `\` share
  270. DOS: fully-qualified or legacy-device or drive or `\`
  271. full-qualified: drive `\`
  272. TODO: What is \\?\server1\e:\utilities\\filecomparer\ from the docs?
  273. TODO: What about admin use of `$` instead of `:`? E.g. \\system07\C$\
  274. NOTE: Legacy devices are not handled by System at a library level, but
  275. are deferred to the relevant syscalls.
  276. */
  277. var lexer = _Lexer(self)
  278. // Helper to parse a UNC root
  279. func parseUNC(deviceSigil: SystemChar?) -> _ParsedWindowsRoot {
  280. let serverRange = lexer.eatComponent()
  281. guard lexer.eatBackslash() else {
  282. fatalError("expected normalized root to contain backslash")
  283. }
  284. let shareRange = lexer.eatComponent()
  285. let rootEnd = lexer.current
  286. _ = lexer.eatBackslash()
  287. return .unc(
  288. deviceSigil: deviceSigil,
  289. server: serverRange, share: shareRange,
  290. endingAt: rootEnd, relativeBegin: lexer.current)
  291. }
  292. // `C:` or `C:\`
  293. if let d = lexer.eatDrive() {
  294. // `C:\` - fully qualified
  295. let fullyQualified = lexer.eatBackslash()
  296. return .traditional(
  297. drive: d, fullQualified: fullyQualified, endingAt: lexer.current)
  298. }
  299. // `\` or else it's just a rootless relative path
  300. guard lexer.eatBackslash() else { return nil }
  301. // `\\` or else it's just a current-drive rooted traditional path
  302. guard lexer.eatBackslash() else {
  303. return .traditional(
  304. drive: nil, fullQualified: false, endingAt: lexer.current)
  305. }
  306. // `\\.` or `\\?` (device paths) or else it's just UNC
  307. guard let sigil = lexer.eatSigil() else {
  308. return parseUNC(deviceSigil: nil)
  309. }
  310. _ = sigil // suppress warnings
  311. guard lexer.eatBackslash() else {
  312. fatalError("expected normalized root to contain backslash")
  313. }
  314. if lexer.eatUNC() {
  315. guard lexer.eatBackslash() else {
  316. fatalError("expected normalized root to contain backslash")
  317. }
  318. return parseUNC(deviceSigil: sigil)
  319. }
  320. let device = lexer.eatComponent()
  321. let rootEnd = lexer.current
  322. _ = lexer.eatBackslash()
  323. return .device(
  324. deviceSigil: sigil, volume: device,
  325. endingAt: rootEnd, relativeBegin: lexer.current)
  326. }
  327. @inline(never)
  328. internal func _parseWindowsRoot() -> (
  329. rootEnd: SystemString.Index, relativeBegin: SystemString.Index
  330. ) {
  331. guard let parsed = _parseWindowsRootInternal() else {
  332. return (startIndex, startIndex)
  333. }
  334. return (parsed.rootEnd, parsed.relativeBegin)
  335. }
  336. }
  337. extension SystemString {
  338. // UNC and device roots can have multiple repeated roots that are meaningful,
  339. // and extra backslashes may need to be inserted for partial roots (e.g. empty
  340. // volume).
  341. //
  342. // Returns the point where `_normalizeSeparators` should resume.
  343. internal mutating func _prenormalizeWindowsRoots() -> Index {
  344. assert(_windowsPaths)
  345. assert(!self.contains(.slash), "only valid after separator conversion")
  346. var lexer = _Lexer(self)
  347. // Only relevant for UNC or device paths
  348. guard lexer.eatBackslash(), lexer.eatBackslash() else {
  349. return lexer.current
  350. }
  351. // Parse a backslash, inserting one if needed
  352. func expectBackslash() {
  353. if lexer.eatBackslash() { return }
  354. // A little gross, but we reset the lexer because the lexer
  355. // holds a strong reference to `self`.
  356. //
  357. // TODO: Intern the empty SystemString. Right now, this is
  358. // along an uncommon/pathological case, but we want to in
  359. // general make empty strings without allocation
  360. let idx = lexer.current
  361. lexer.clear()
  362. self.insert(.backslash, at: idx)
  363. lexer.reset(to: self, at: idx)
  364. let p = lexer.eatBackslash()
  365. assert(p)
  366. }
  367. // Parse a component and subsequent backslash, insering one if needed
  368. func expectComponent() {
  369. _ = lexer.eatComponent()
  370. expectBackslash()
  371. }
  372. // Check for `\\.` style paths
  373. if lexer.eatSigil() != nil {
  374. expectBackslash()
  375. if lexer.eatUNC() {
  376. expectBackslash()
  377. expectComponent()
  378. expectComponent()
  379. return lexer.current
  380. }
  381. expectComponent()
  382. return lexer.current
  383. }
  384. expectComponent()
  385. expectComponent()
  386. return lexer.current
  387. }
  388. }
  389. #if os(Windows)
  390. import WinSDK
  391. // FIXME: Rather than canonicalizing the path at every call site to a Win32 API,
  392. // we should consider always storing absolute paths with the \\?\ prefix applied,
  393. // for better performance.
  394. extension UnsafePointer where Pointee == CInterop.PlatformChar {
  395. /// Invokes `body` with a resolved and potentially `\\?\`-prefixed version of the pointee,
  396. /// to ensure long paths greater than MAX_PATH (260) characters are handled correctly.
  397. ///
  398. /// - seealso: https://learn.microsoft.com/en-us/windows/win32/fileio/maximum-file-path-limitation
  399. internal func withCanonicalPathRepresentation<Result>(_ body: (Self) throws -> Result) throws -> Result {
  400. // 1. Normalize the path first.
  401. // Contrary to the documentation, this works on long paths independently
  402. // of the registry or process setting to enable long paths (but it will also
  403. // not add the \\?\ prefix required by other functions under these conditions).
  404. let dwLength: DWORD = GetFullPathNameW(self, 0, nil, nil)
  405. return try withUnsafeTemporaryAllocation(of: WCHAR.self, capacity: Int(dwLength)) { pwszFullPath in
  406. guard (1..<dwLength).contains(GetFullPathNameW(self, DWORD(pwszFullPath.count), pwszFullPath.baseAddress, nil)) else {
  407. throw Errno(rawValue: _mapWindowsErrorToErrno(GetLastError()))
  408. }
  409. // 1.5 Leave \\.\ prefixed paths alone since device paths are already an exact representation and PathCchCanonicalizeEx will mangle these.
  410. if let base = pwszFullPath.baseAddress,
  411. base[0] == UInt8(ascii: "\\"),
  412. base[1] == UInt8(ascii: "\\"),
  413. base[2] == UInt8(ascii: "."),
  414. base[3] == UInt8(ascii: "\\") {
  415. return try body(base)
  416. }
  417. // 2. Canonicalize the path.
  418. // This will add the \\?\ prefix if needed based on the path's length.
  419. var pwszCanonicalPath: LPWSTR?
  420. let flags: ULONG = numericCast(PATHCCH_ALLOW_LONG_PATHS.rawValue)
  421. let result = PathAllocCanonicalize(pwszFullPath.baseAddress, flags, &pwszCanonicalPath)
  422. if let pwszCanonicalPath {
  423. defer { LocalFree(pwszCanonicalPath) }
  424. if result == S_OK {
  425. // 3. Perform the operation on the normalized path.
  426. return try body(pwszCanonicalPath)
  427. }
  428. }
  429. throw Errno(rawValue: _mapWindowsErrorToErrno(WIN32_FROM_HRESULT(result)))
  430. }
  431. }
  432. }
  433. @inline(__always)
  434. fileprivate func HRESULT_CODE(_ hr: HRESULT) -> DWORD {
  435. DWORD(hr) & 0xffff
  436. }
  437. @inline(__always)
  438. fileprivate func HRESULT_FACILITY(_ hr: HRESULT) -> DWORD {
  439. DWORD(hr >> 16) & 0x1fff
  440. }
  441. @inline(__always)
  442. fileprivate func SUCCEEDED(_ hr: HRESULT) -> Bool {
  443. hr >= 0
  444. }
  445. // This is a non-standard extension to the Windows SDK that allows us to convert
  446. // an HRESULT to a Win32 error code.
  447. @inline(__always)
  448. fileprivate func WIN32_FROM_HRESULT(_ hr: HRESULT) -> DWORD {
  449. if SUCCEEDED(hr) { return ERROR_SUCCESS }
  450. if HRESULT_FACILITY(hr) == FACILITY_WIN32 {
  451. return HRESULT_CODE(hr)
  452. }
  453. return DWORD(hr)
  454. }
  455. #endif