簡體   English   中英

在 Linux Swift 包中使用 sysctlbyname 函數

[英]Use the sysctlbyname function within a Linux Swift Package

我正在嘗試將 linux 支持添加到我的 swift 包庫中以獲取系統信息,但我不知道如何在 Swift 包中訪問 linux 上的sysctlbyname函數。

對於所有檢測,該庫依賴於sysctlbyname函數,該函數可以通過在 Apple 平台上導入Dariwn.sys.sysctl輕松訪問,但是我找不到任何 Swift 方法在 linux 上訪問該函數,盡管您可以訪問通過在基本上任何 unix 平台上導入 sys/sysctl.h 在 C 中實現它。

所以我想知道如何在 linux 上的 Swift 庫中訪問該函數,以及是否可以在不使用 C 或其他非 Swift 東西的情況下訪問該函數,也是因為我想讓我的代碼與 Swift 游樂場兼容適用於蘋果系統的應用程序,它不支持具有 C 導入功能的 SPM 庫。

作為參考,我在這里留下了我的項目中負責與sysctlbyname的代碼部分:


import Foundation

#if os(Linux)
import Glibc //? not sure about where i can find `sysctlbyname` in linux without using C headers
#else
import Darwin.sys.sysctl
#endif

///Generic protocol to allow easy fetching of values out of `sysctlbyname`
public protocol SysctlFetch{
    static var namePrefix: String {get}
}

public extension SysctlFetch{
    
    ///Gets a `String` from the `sysctlbyname` function
    static func getString(_ valueName: String) -> String?{
        
        var size: size_t = 0
        
        let name = namePrefix + valueName
        
        var res = sysctlbyname(name, nil, &size, nil, 0)
        
        if res != 0 {
            return nil
        }
        
        var ret = [CChar].init(repeating: 0, count: size + 1)
        
        res = sysctlbyname(name, &ret, &size, nil, 0)
        
        return res == 0 ? String(cString: ret) : nil
    }
    
    ///Gets an Integer value from the `sysctlbyname` function
    static func getInteger<T: FixedWidthInteger>(_ valueName: String) -> T?{
        var ret = T()
        
        var size = MemoryLayout.size(ofValue: ret)
        
        let res = sysctlbyname(namePrefix + valueName, &ret, &size, nil, 0)
        
        return res == 0 ? ret : nil
    }
    
    ///Gets a `Bool` value from the `sysctlbyname` function
    static func getBool(_ valueName: String) -> Bool?{
        guard let res: Int32 = getInteger(valueName) else{
            return nil
        }
        
        return res == 1
    }
    
}

還有一個如何在代碼中使用它的例子(詛咒它被用來檢索更多的東西):

    ///Kernel info
    final class KernelInfo: SysctlFetch{
        
        static var namePrefix: String{
            #if os(Linux)
                return "kernel."
            #else
                return "kern."
            #endif
        }
        
        ///The os kernel name
        static var ostype: String?{
            return Self.getString("ostype")
        }

        /* Other static vars here */

    }


因此,可以將 C、C++ 或 Objective-C 目標添加到 Swift 包中,這樣就可以導入所需的系統頭文件,然后創建一些包裝函數,使 Swift 可以訪問所需的內容,但這會破壞 Swift Playgrounds 應用程序開發兼容性,因為它只支持 Swift 目標(一種可能的解決方法是將 C/C++ 目標放在單獨的 swift 包中,以便有條件地將其用作 linux 的依賴項,有關更多詳細信息,請參閱相關的 swift 包文檔)。

所以添加一個 C/C++ 目標可能已經解決了這個問題,但問題是在 Linux 內核版本 5.5 及更高版本中, sysctl函數已被棄用,即使在較舊的內核上,它們也不適用於 Linux 支持的所有 cpu 架構,等等運行最近的內核或某些特定的非 x86 cpu 架構的計算機,這樣的 Swift 包將不會成功構建。

當前訪問sysctl函數提供的信息的方法是直接從/proc/sys目錄中的文件系統讀取它,它適用於所有支持的 cpu 架構,它是sysctl命令行實用程序那個數據。

因此,只有在 linux 上,代碼必須像這樣修改,才能在所有平台上成功收集該數據:


import Foundation

#if os(Linux)
import Glibc //? not sure about where i can find `sysctlbyname` in linux without using C headers
#else
import Darwin.sys.sysctl
#endif

///Generic protocol to allow easy fetching of values out of `sysctlbyname`
public protocol SysctlFetch{
    static var namePrefix: String {get}
}

public extension SysctlFetch{
    
#if !os(Linux)
    ///Gets a `String` from the `sysctlbyname` function
    static func getString(_ valueName: String) -> String?{
        
        var size: size_t = 0
        
        let name = namePrefix + valueName
        
        var res = sysctlbyname(name, nil, &size, nil, 0)
        
        if res != 0 {
            return nil
        }
        
        var ret = [CChar].init(repeating: 0, count: size + 1)
        
        res = sysctlbyname(name, &ret, &size, nil, 0)
        
        return res == 0 ? String(cString: ret) : nil
    }
    
    ///Gets an Integer value from the `sysctlbyname` function
    static func getInteger<T: FixedWidthInteger>(_ valueName: String) -> T?{
        var ret = T()
        
        var size = MemoryLayout.size(ofValue: ret)
        
        let res = sysctlbyname(namePrefix + valueName, &ret, &size, nil, 0)
        
        return res == 0 ? ret : nil
    }
#else
    ///Gets a `String` from `/proc/sys`
    static func getString(_ valueName: String) -> String?{
        
        let path = "/proc/sys/" + (namePrefix + valueName).replacingOccurrences(of: ".", with: "/")

        var contents = ""
        
        do{
            contents = try String(contentsOfFile: path)
        }catch let err{
            return nil
        }
        
        if contents.last == "\n"{
            contents.removeLast()
        }
        
        return contents
    }
    
    ///Gets an Integer value from `/proc/sys`
    static func getInteger<T: FixedWidthInteger>(_ valueName: String) -> T?{
        guard let str = getString(valueName) else { return nil }
        return T(str)
    }
#endif
    
    ///Gets a `Bool` value from the `sysctlbyname` function
    static func getBool(_ valueName: String) -> Bool?{
        guard let res: Int32 = getInteger(valueName) else{
            return nil
        }
        
        return res == 1
    }
    
}

所以最后我自己想通了,我希望這對任何必須做同樣事情的人有用。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM