Search code examples
c++variadic-templatessystem-callstemplate-argument-deduction

Proxy to forward Linux sytem calls using variadic templates


I have a class that does some capability handling before calling a certain system call. This is done with a variadic template function:

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;
    }
//...
};

This works nice on "normal" system calls. The system call's signature is deduced from the template arguments. The typename NoDeduce<PAR>::type... params forces a non-deduced context for the actual parameters. This might be replaced by std::type_identity once I have a compiler supporting C++20. Here is an example that uses this function to acquire the privileges for a kill system call:

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

However, as soon as I try to use a system call that has an ... in its signature (e.g. ioctl), I get a compiler error (here I try to change the MTU size):

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

Obviously, the ellipsis as part of the parameter pack is causing this problem here. I thought I could solve this by using 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)...);
    //...
}

However, this fails with:

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'}

Does anyone have a suggestion to make this work with a single template function? My current workaround is to define another template:

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

Solution

  • What about reversing the deduction: a generic type for the function (avoiding deducing the types of the arguments) and deducing the arguments (maybe adding forwarding)?

    I mean... something as

    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;
     }