简体   繁体   English

在 Linux Swift 包中使用 sysctlbyname 函数

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

i am trying to add linux support to my swift package library for system info, but i don't know how i can access the sysctlbyname function on linux within a Swift package.我正在尝试将 linux 支持添加到我的 swift 包库中以获取系统信息,但我不知道如何在 Swift 包中访问 linux 上的sysctlbyname函数。

For all of it's detections the library relies on the sysctlbyname function which is easily accessible by importing Dariwn.sys.sysctl on Apple platforms, however i can't find any Swift ways to access that function on linux, despite the fact that you can access it in C by importing sys/sysctl.h on basically any unix platform.对于所有检测,该库依赖于sysctlbyname函数,该函数可以通过在 Apple 平台上导入Dariwn.sys.sysctl轻松访问,但是我找不到任何 Swift 方法在 linux 上访问该函数,尽管您可以访问通过在基本上任何 unix 平台上导入 sys/sysctl.h 在 C 中实现它。

So i was wondering how can access that function in my Swift library on linux and if it's possible to do it without having to use C or some other non-Swift stuff, also because i'd like to keep my code compatible with the Swift playgrounds app for apple systems, which doesn't support SPM libraries featuring C imports.所以我想知道如何在 linux 上的 Swift 库中访问该函数,以及是否可以在不使用 C 或其他非 Swift 东西的情况下访问该函数,也是因为我想让我的代码与 Swift 游乐场兼容适用于苹果系统的应用程序,它不支持具有 C 导入功能的 SPM 库。

Just as a reference i leave here the part of the code responsible for interfacing with sysctlbyname in my project:作为参考,我在这里留下了我的项目中负责与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
    }
    
}

And an example of how it's used in the code (of curse it's used to retrive much more stuff):还有一个如何在代码中使用它的例子(诅咒它被用来检索更多的东西):

    ///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 */

    }


So one can add C, C++ or Objective-C targets to a Swift package so it's possible to import the needed system headers, and then create some wrapper functions, that makes what is needed, accessible to Swift, but this breaks Swift playgrounds app development compatibility, since that support Swift-only targets (a possible workaround is to put the C/C++ target in a separate swift package to use it as a dependecy conditionally just for linux, for more details see the relative swift package documentation).因此,可以将 C、C++ 或 Objective-C 目标添加到 Swift 包中,这样就可以导入所需的系统头文件,然后创建一些包装函数,使 Swift 可以访问所需的内容,但这会破坏 Swift Playgrounds 应用程序开发兼容性,因为它只支持 Swift 目标(一种可能的解决方法是将 C/C++ 目标放在单独的 swift 包中,以便有条件地将其用作 linux 的依赖项,有关更多详细信息,请参阅相关的 swift 包文档)。

So adding a C/C++ target could have solved the problem, BUT the issue is that in the Linux kernel version 5.5 and onwards the sysctl functions have been deprecated and even on the older kernels they weren't available on all the cpu architectures Linux supports, and so on a computer running a recent kernel or some particular non-x86 cpu architecture, such Swift package would not have been built successfully.所以添加一个 C/C++ 目标可能已经解决了这个问题,但问题是在 Linux 内核版本 5.5 及更高版本中, sysctl函数已被弃用,即使在较旧的内核上,它们也不适用于 Linux 支持的所有 cpu 架构,等等运行最近的内核或某些特定的非 x86 cpu 架构的计算机,这样的 Swift 包将不会成功构建。

The current way to access the information that used to be provided by the sysctl functions is to read it directly from the file system inside the /proc/sys directory and it works on all supported cpu architectures, and it's were the sysctl command line utility gets that data.当前访问sysctl函数提供的信息的方法是直接从/proc/sys目录中的文件系统读取它,它适用于所有支持的 cpu 架构,它是sysctl命令行实用程序那个数据。

So only on linux the code have to modified like this, to successfully gather that data on all platforms:因此,只有在 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
    }
    
}

So at the end i figured it out on my own, i hope this can be useful to anyone having to do the same thing.所以最后我自己想通了,我希望这对任何必须做同样事情的人有用。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM