在Linux Swift软件包中使用sysctlbyname函数

发布于 2025-01-30 20:28:38 字数 2535 浏览 3 评论 0原文

我正在尝试将Linux支持添加到我的Swift软件包库中以获取系统信息,但是我不知道如何在Swift软件包中的Linux上访问sysctlbyname函数。

对于所有检测,库依赖于sysctlbyname函数,该函数可以通过在Apple Platform上导入dariwn.sysctl可以轻松访问,但是我找不到任何swift方法要在Linux上访问该功能,尽管您可以通过在任何UNIX平台上导入SYS/sysctl.h在C中访问它。

因此,我想知道如何在Linux上的Swift库中访问该功能,并且是否可以使用C或其他一些非旋转的东西,这也是因为我想让我的代码与Swift Playgrounds兼容Apple Systems的应用程序,该应用不支持CMINTICS的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 */

    }


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.

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.

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.

Just as a reference i leave here the part of the code responsible for interfacing with sysctlbyname in my project:


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

    }


如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(1

手心的温暖 2025-02-06 20:28:38

因此,可以将C,C ++或Objective-C目标添加到Swift软件包,以便导入所需的系统标头,然后创建一些包装器功能,使Swift可以访问所需的内容,但这打破了Swift Playgrounds应用程序开发兼容性,因为该支持仅迅速目标(可能的解决方法是将C/C ++目标放在单独的Swift软件包中,以将其用作有条件地用于Linux的依赖,请参见相对的Swift软件包文档)。

因此,添加C/C ++目标可能已经解决了问题,但是问题是在Linux内核版本5.5及以后sysctl功能已弃用,即使在较旧的内核上,它们也不可用在所有CPU架构Linux均支持的所有CPU架构上,依此类推,在运行最近的内核或某些特定非X86 CPU架构的计算机上,这种Swift软件包将不会成功地构建。

访问sysctl函数提供的信息的当前方法是直接从/proc/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
    }
    
}

因此,最后我自己弄清楚了,我希望这对任何必须做同样的事情都会有用。

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).

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.

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.

So only on linux the code have to modified like this, to successfully gather that data on all platforms:


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.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文