apple_kit

Apple-native services for Radroots iOS and macOS apps
git clone https://radroots.dev/git/apple_kit.git
Log | Files | Refs | README | LICENSE

RadrootsAdmissionFileScan.swift (3984B)


      1 import Darwin
      2 import Foundation
      3 
      4 /// One bounded pass over native reservation metadata, not a statement about
      5 /// transfer completion or global absence. New entries may require another pass.
      6 public struct RadrootsAdmissionCleanupResult: Sendable, Equatable {
      7     public let scannedEntries: Int
      8     public let removedFiles: Int
      9     public let reachedEnd: Bool
     10 }
     11 
     12 /// Owns two descriptors. The mutex serializes stream position and validation;
     13 /// deinitialization runs only after all method borrows finish. No callbacks or
     14 /// asynchronous work execute under this lock.
     15 final class RadrootsAdmissionFileScan: @unchecked Sendable {
     16     private let lock = NSLock()
     17     private let directory: RadrootsAtomicFile.Directory
     18     private let stream: UnsafeMutablePointer<DIR>
     19 
     20     init(url: URL) throws {
     21         guard url.isFileURL, !url.path.utf8.contains(0) else { throw RadrootsAppleFileError.invalidRequest }
     22         let parts = url.path.split(separator: "/").map(String.init)
     23         guard parts.allSatisfy({ $0 != "." && $0 != ".." }) else { throw RadrootsAppleFileError.invalidRequest }
     24         let directory = try RadrootsAtomicFile.Directory.open(parts, create: false)
     25         let copy = Darwin.fcntl(directory.descriptor, F_DUPFD_CLOEXEC, 0)
     26         guard copy >= 0 else {
     27             Darwin.close(directory.descriptor)
     28             throw RadrootsAppleFileError.permanentFailure
     29         }
     30         guard let stream = Darwin.fdopendir(copy) else {
     31             Darwin.close(copy)
     32             Darwin.close(directory.descriptor)
     33             throw RadrootsAppleFileError.permanentFailure
     34         }
     35         self.directory = directory
     36         self.stream = stream
     37     }
     38 
     39     deinit {
     40         Darwin.closedir(stream)
     41         Darwin.close(directory.descriptor)
     42     }
     43 
     44     func next(limit: Int) throws -> (names: [String], scanned: Int, reachedEnd: Bool) {
     45         try lock.withLock {
     46             try directory.validate()
     47             var names: [String] = []
     48             var scanned = 0
     49             var reachedEnd = false
     50             for _ in 0 ..< limit {
     51                 errno = 0
     52                 guard let entry = Darwin.readdir(stream) else {
     53                     guard errno == 0 else { throw RadrootsAppleFileError.permanentFailure }
     54                     reachedEnd = true
     55                     break
     56                 }
     57                 scanned += 1
     58                 let count = Int(entry.pointee.d_namlen)
     59                 let name = withUnsafeBytes(of: entry.pointee.d_name) { bytes -> String? in
     60                     guard count <= bytes.count else { return nil }
     61                     return String(bytes: bytes.prefix(count), encoding: .utf8)
     62                 }
     63                 if let name {
     64                     names.append(name)
     65                 }
     66             }
     67             try directory.validate()
     68             return (names, scanned, reachedEnd)
     69         }
     70     }
     71 
     72     func removeInactive(name: String, coordination: Int32) throws -> Bool {
     73         try lock.withLock {
     74             try directory.validate()
     75             var held = stat()
     76             var gate = stat()
     77             guard Darwin.fstat(coordination, &held) == 0,
     78                   Darwin.fstatat(directory.descriptor, ".coordination.lock", &gate, AT_SYMLINK_NOFOLLOW) == 0,
     79                   held.st_dev == gate.st_dev, held.st_ino == gate.st_ino
     80             else {
     81                 throw RadrootsAppleFileError.permanentFailure
     82             }
     83             var value = stat()
     84             guard name.withCString({ Darwin.fstatat(directory.descriptor, $0, &value, AT_SYMLINK_NOFOLLOW) }) == 0,
     85                   value.st_mode & S_IFMT == S_IFREG, value.st_size == 0 else { return false }
     86             guard let descriptor = try RadrootsAtomicFile.acquireExclusiveLock(in: directory, name: name, create: false) else {
     87                 return false
     88             }
     89             defer { Darwin.close(descriptor) }
     90             return try RadrootsAtomicFile.removeEmptyLockedFile(in: directory, name: name, descriptor: descriptor)
     91         }
     92     }
     93 }