使用可变参数模板转发 Linux 系统调用的代理

Proxy to forward Linux sytem calls using variadic templates

我有一个 class 可以在调用某个系统调用之前进行一些功能处理。这是通过可变参数模板函数完成的:

class PrivilegesLinux
{
private:
    //! Prevent type deduction on template parameter.
    template<typename T> struct NoDeduce { typedef T type; };

public:

template<typename... PAR>
static int syscallProxy(cap_value_t capability,
    int(*syscall)(PAR...), typename NoDeduce<PAR>::type... params)
    {
        int rc = 0;
        // ... acquire capabilities

        // Forward to system call
        rc = (*syscall)(params...);

        // ... drop capabilities

        return rc;
    }
//...
};

这在“普通”系统调用上效果很好。系统调用的签名是从模板参数中推导出来的。 typename NoDeduce<PAR>::type... params 为实际参数强制使用非推导上下文。一旦我有一个支持 C++20 的编译器,这可能会被 std::type_identity 取代。下面是使用此函数获取 kill 系统调用权限的示例:

PrivilegesLinux::syscallProxy(CAP_KILL, kill, 1, SIGTERM);

但是,一旦我尝试使用签名中包含 ... 的系统调用(例如 ioctl),我就会收到编译器错误(此处我尝试更改 MTU尺码):

PrivilegesLinux::syscallProxy(CAP_NET_ADMIN, ioctl, nSocketID, SIOCSIFMTU, (char *)&ifr);
/*
error: no matching function for call to 'PrivilegesLinux::syscallProxy(int, int (&)(int, long unsigned int, ...) noexcept, const INT32&, int, char*)'
note: candidate: 'static int PrivilegesLinux::syscallProxy(cap_value_t, int (*)(PAR ...), typename PrivilegesLinux::NoDeduce<PAR>::type ...) [with PAR = {int, long unsigned int}; cap_value_t = int]'
note:   candidate expects 4 arguments, 5 provided
*/

显然,作为参数包一部分的省略号导致了这里的问题。我想我可以通过使用 std::forward:

来解决这个问题
template<typename... PAR>
static int syscallProxy(cap_value_t capability,
    int(*syscall)(PAR...), typename NoDeduce<PAR>::type&&... params)
{
    //...
    rc = (*syscall)(std::forward<PAR>(params)...);
    //...
}

然而,这失败了:

error: cannot bind rvalue reference of type 'PrivilegesLinux::NoDeduce<long unsigned int>::type&&' {aka 'long unsigned int&&'} to lvalue of type 'pthread_t' {aka 'long unsigned int'}

有没有人建议使用单个模板函数来完成这项工作? 我目前的解决方法是定义另一个模板:

template<typename... SYSCALL_PAR, typename... PAR>
static int syscallProxy(cap_value_t capability,
    int(*syscall)(SYSCALL_PAR..., ...), PAR... params)
{
    // copy of the code above
}

如何反转推导:函数的通用类型(避免推导参数的类型)和推导参数(可能添加转发)?

我的意思是……

template <typename Func, typename... PARS>
static int syscallProxy (cap_value_t capability,
                         Func syscall, PARS && ... params)
 {
   int rc = 0;
   // ... acquire capabilities

   // Forward to system call
   rc = syscall(std::forward<PARS>(params)...);

   // ... drop capabilities

   return rc;
 }

或者,您可以通过添加额外的重载来显式处理 C-ellipsis:

template<typename... PAR, typename ... Ts>
static int syscallProxy(cap_value_t capability,
                        int(*syscall)(PAR..., ...),
                        typename NoDeduce<PAR>::type... params,
                        Ts... args) // ellipsis arguments
{
    // Possibly check Ts... are "valid" type for ellipsis
    int rc = 0;
    // ... acquire capabilities

    // Forward to system call
    rc = (*syscall)(params..., args...);

    // ... drop capabilities

    return rc;
}

注:

  • int(*syscall)(PAR..., ...)也可以写成int(*syscall)(PAR......)
  • Ts 是按值传递的,因为 ellipsis arguments types 无论如何都是有限的。