glean-core/ios/Glean/Utils/Sysctl.swift (99 lines of code) (raw):

// swiftlint:disable line_length // REASON: URLs and doc strings // Copyright © 2017 Matt Gallagher ( http://cocoawithlove.com ). All rights reserved. // // Original: https://github.com/mattgallagher/CwlUtils/blob/0e08b0194bf95861e5aac27e8857a972983315d7/Sources/CwlUtils/CwlSysctl.swift // Modified: // * iOS only // * removed unused functions // * reformatted // // ISC License // // Permission to use, copy, modify, and/or distribute this software for any // purpose with or without fee is hereby granted, provided that the above // copyright notice and this permission notice appear in all copies. // // THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES // WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF // MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY // SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES // WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN // ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR // IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. import Foundation /// A "static"-only namespace around a series of functions that operate on buffers returned from the `Darwin.sysctl` function struct Sysctl { /// Possible errors. enum Error: Swift.Error { case unknown case malformedUTF8 case invalidSize case posixError(POSIXErrorCode) } /// Access the raw data for an array of sysctl identifiers. public static func data(for keys: [Int32]) throws -> [Int8] { return try keys.withUnsafeBufferPointer { keysPointer throws -> [Int8] in // Preflight the request to get the required data size var requiredSize = 0 let preFlightResult = Darwin.sysctl( UnsafeMutablePointer<Int32>(mutating: keysPointer.baseAddress), UInt32(keys.count), nil, &requiredSize, nil, 0 ) if preFlightResult != 0 { throw POSIXErrorCode(rawValue: errno).map { print($0.rawValue) return Error.posixError($0) } ?? Error.unknown } // Run the actual request with an appropriately sized array buffer let data = [Int8](repeating: 0, count: requiredSize) let result = data.withUnsafeBufferPointer { dataBuffer -> Int32 in Darwin.sysctl( UnsafeMutablePointer<Int32>(mutating: keysPointer.baseAddress), UInt32(keys.count), UnsafeMutableRawPointer(mutating: dataBuffer.baseAddress), &requiredSize, nil, 0 ) } if result != 0 { throw POSIXErrorCode(rawValue: errno).map { Error.posixError($0) } ?? Error.unknown } return data } } /// Convert a sysctl name string like "hw.memsize" to the array of `sysctl` identifiers (e.g. [CTL_HW, HW_MEMSIZE]) public static func keys(for name: String) throws -> [Int32] { var keysBufferSize = Int(CTL_MAXNAME) var keysBuffer = [Int32](repeating: 0, count: keysBufferSize) try keysBuffer.withUnsafeMutableBufferPointer { (lbp: inout UnsafeMutableBufferPointer<Int32>) throws in try name.withCString { (nbp: UnsafePointer<Int8>) throws in guard sysctlnametomib(nbp, lbp.baseAddress, &keysBufferSize) == 0 else { throw POSIXErrorCode(rawValue: errno).map { Error.posixError($0) } ?? Error.unknown } } } if keysBuffer.count > keysBufferSize { keysBuffer.removeSubrange(keysBufferSize ..< keysBuffer.count) } return keysBuffer } /// Invoke `sysctl` with an array of identifers, interpreting the returned buffer as the specified type. /// This function will throw `Error.invalidSize` if the size of buffer returned from `sysctl` fails to match the size of `T`. public static func value<T>(ofType _: T.Type, forKeys keys: [Int32]) throws -> T { let buffer = try data(for: keys) if buffer.count != MemoryLayout<T>.size { throw Error.invalidSize } return try buffer.withUnsafeBufferPointer { bufferPtr throws -> T in guard let baseAddress = bufferPtr.baseAddress else { throw Error.unknown } return baseAddress.withMemoryRebound(to: T.self, capacity: 1) { $0.pointee } } } /// Invoke `sysctl` with an array of identifers, interpreting the returned buffer as the specified type. /// This function will throw `Error.invalidSize` if the size of buffer returned from `sysctl` fails to match the size of `T`. public static func value<T>(ofType type: T.Type, forKeys keys: Int32...) throws -> T { return try value(ofType: type, forKeys: keys) } /// Invoke `sysctl` with the specified name, interpreting the returned buffer as the specified type. /// This function will throw `Error.invalidSize` if the size of buffer returned from `sysctl` fails to match the size of `T`. public static func value<T>(ofType type: T.Type, forName name: String) throws -> T { return try value(ofType: type, forKeys: keys(for: name)) } /// Invoke `sysctl` with an array of identifers, interpreting the returned buffer as a `String`. /// This function will throw `Error.malformedUTF8` if the buffer returned from `sysctl` cannot be interpreted as a UTF8 buffer. public static func string(for keys: [Int32]) throws -> String { let optionalString = try data(for: keys).withUnsafeBufferPointer { dataPointer -> String? in dataPointer.baseAddress.flatMap { String(validatingUTF8: $0) } } guard let s = optionalString else { throw Error.malformedUTF8 } return s } /// Invoke `sysctl` with an array of identifers, interpreting the returned buffer as a `String`. /// This function will throw `Error.malformedUTF8` if the buffer returned from `sysctl` cannot be interpreted as a UTF8 buffer. public static func string(for keys: Int32...) throws -> String { return try string(for: keys) } /// Invoke `sysctl` with the specified name, interpreting the returned buffer as a `String`. /// This function will throw `Error.malformedUTF8` if the buffer returned from `sysctl` cannot be interpreted as a UTF8 buffer. public static func string(for name: String) throws -> String { return try string(for: keys(for: name)) } /// Always the same on Apple hardware public static var manufacturer: String = "Apple" /// e.g. "N71mAP" public static var machine: String { return (try? Sysctl.string(for: [CTL_HW, HW_MODEL])) ?? "Unknown" } /// e.g. "iPhone8,1" public static var model: String { return (try? Sysctl.string(for: [CTL_HW, HW_MACHINE])) ?? "Unknown" } /// e.g. "15D21" or "13D20" public static var osVersion: String { return (try? Sysctl.string(for: [CTL_KERN, KERN_OSVERSION])) ?? "Unknown" } } // swiftlint:enable line_length