/*      $NetBSD: uipc_syscalls_40.c,v 1.24 2022/07/07 18:17:33 riastradh Exp $  */

/* written by Pavel Cahyna, 2006. Public domain. */

#include <sys/cdefs.h>
__KERNEL_RCSID(0, "$NetBSD: uipc_syscalls_40.c,v 1.24 2022/07/07 18:17:33 riastradh Exp $");

#if defined(_KERNEL_OPT)
#include "opt_compat_netbsd.h"
#endif

/*
* System call interface to the socket abstraction.
*/

#include <sys/param.h>
#include <sys/kernel.h>
#include <sys/msg.h>
#include <sys/sysctl.h>
#include <sys/syscallargs.h>
#include <sys/errno.h>
#include <sys/compat_stub.h>

#include <net/if.h>

#include <compat/sys/socket.h>
#include <compat/sys/sockio.h>

#include <compat/common/compat_mod.h>

/*
* Return interface configuration of system.  List may be used in later
* ioctl's (above) to get other information.
*/
/*ARGSUSED*/
static int
compat_ifconf(u_long cmd, void *data)
{
       struct oifconf *ifc = data;
       struct ifnet *ifp;
       struct oifreq ifr, *ifrp = NULL;
       int space = 0, error = 0;
       const int sz = (int)sizeof(ifr);
       int s;
       int bound;
       struct psref psref;

       switch (cmd) {
       case OSIOCGIFCONF:
       case OOSIOCGIFCONF:
               break;
       default:
               return ENOSYS;
       }

       const bool docopy = ifc->ifc_req != NULL;
       if (docopy) {
               if (ifc->ifc_len < 0)
                       return EINVAL;

               space = ifc->ifc_len;
               ifrp = ifc->ifc_req;
       }
       memset(&ifr, 0, sizeof(ifr));

       bound = curlwp_bind();
       s = pserialize_read_enter();
       IFNET_READER_FOREACH(ifp) {
               struct ifaddr *ifa;

               if_acquire(ifp, &psref);
               pserialize_read_exit(s);

               (void)strncpy(ifr.ifr_name, ifp->if_xname,
                   sizeof(ifr.ifr_name));
               if (ifr.ifr_name[sizeof(ifr.ifr_name) - 1] != '\0') {
                       error = ENAMETOOLONG;
                       goto release_exit;
               }
               if (IFADDR_READER_EMPTY(ifp)) {
                       memset(&ifr.ifr_addr, 0, sizeof(ifr.ifr_addr));
                       if (space >= sz) {
                               error = copyout(&ifr, ifrp, sz);
                               if (error != 0)
                                       goto release_exit;
                               ifrp++;
                       }
                       space -= sizeof(ifr);
                       goto next;
               }

               s = pserialize_read_enter();
               IFADDR_READER_FOREACH(ifa, ifp) {
                       struct sockaddr *sa = ifa->ifa_addr;
                       struct psref psref_ifa;

                       ifa_acquire(ifa, &psref_ifa);
                       pserialize_read_exit(s);
#ifdef COMPAT_OSOCK
                       if (cmd == OOSIOCGIFCONF) {
                               struct osockaddr *osa =
                                   (struct osockaddr *)&ifr.ifr_addr;
                               /*
                                * If it does not fit, we don't bother with it
                                */
                               if (sa->sa_len > sizeof(*osa))
                                       goto next_ifa;
                               memcpy(&ifr.ifr_addr, sa, sa->sa_len);
                               osa->sa_family = sa->sa_family;
                               if (space >= sz) {
                                       error = copyout(&ifr, ifrp, sz);
                                       ifrp++;
                               }
                       } else
#endif
                       if (sa->sa_len <= sizeof(*sa)) {
                               memcpy(&ifr.ifr_addr, sa, sa->sa_len);
                               if (space >= sz) {
                                       error = copyout(&ifr, ifrp, sz);
                                       ifrp++;
                               }
                       } else {
                               space -= sa->sa_len - sizeof(*sa);
                               if (space >= sz) {
                                       error = copyout(&ifr.ifr_name, ifrp,
                                           sizeof(ifr.ifr_name));
                                       if (error == 0) {
                                               error = copyout(sa,
                                                   &ifrp->ifr_addr,
                                                   sa->sa_len);
                                       }
                                       ifrp = (struct oifreq *)
                                               (sa->sa_len +
                                                (char *)&ifrp->ifr_addr);
                               }
                       }
                       if (error != 0) {
                               ifa_release(ifa, &psref_ifa);
                               goto release_exit;
                       }
                       space -= sz;

#ifdef COMPAT_OSOCK
               next_ifa:
#endif
                       s = pserialize_read_enter();
                       ifa_release(ifa, &psref_ifa);
               }
               pserialize_read_exit(s);

       next:
               s = pserialize_read_enter();
               if_release(ifp, &psref);
       }
       pserialize_read_exit(s);
       curlwp_bindx(bound);

       if (docopy)
               ifc->ifc_len -= space;
       else
               ifc->ifc_len = -space;
       return 0;

release_exit:
       if_release(ifp, &psref);
       curlwp_bindx(bound);
       return error;
}

void
uipc_syscalls_40_init(void)
{

       MODULE_HOOK_SET(uipc_syscalls_40_hook, compat_ifconf);
}

void
uipc_syscalls_40_fini(void)
{

       MODULE_HOOK_UNSET(uipc_syscalls_40_hook);
}