convert ioctl time64 fallbacks to table-driven framework
authorRich Felker <dalias@aerifal.cx>
Thu, 19 Dec 2019 01:48:58 +0000 (20:48 -0500)
committerRich Felker <dalias@aerifal.cx>
Thu, 19 Dec 2019 04:17:28 +0000 (23:17 -0500)
with the current set of supported ioctls, this conversion is hardly an
improvement, but it sets the stage for being able to do alsa, v4l2,
ppp, and other ioctls with timespec/timeval-derived types. without
this capability, a lot of functionality users depend on would stop
working with the time64 switchover.

src/misc/ioctl.c

index 6f31d4b..48613b3 100644 (file)
@@ -3,8 +3,63 @@
 #include <errno.h>
 #include <time.h>
 #include <sys/time.h>
+#include <stddef.h>
+#include <string.h>
 #include "syscall.h"
 
+#define alignof(t) offsetof(struct { char c; t x; }, x)
+
+#define W 1
+#define R 2
+#define WR 3
+
+struct ioctl_compat_map {
+       int new_req, old_req, old_size;
+       char dir, force_align;
+       int offsets[4];
+};
+
+static const struct ioctl_compat_map compat_map[] = {
+       { SIOCGSTAMP, SIOCGSTAMP_OLD, 8, R, 0, { 0, -1, -1, -1 } },
+       { SIOCGSTAMPNS, SIOCGSTAMPNS_OLD, 8, R, 0, { 0, -1, -1, -1 } },
+};
+
+static void convert_ioctl_struct(const struct ioctl_compat_map *map, char *old, char *new, int dir)
+{
+       int new_offset = 0;
+       int old_offset = 0;
+       int old_size = map->old_size;
+       if (!(dir & map->dir)) return;
+       for (int i=0; i < sizeof map->offsets / sizeof *map->offsets; i++) {
+               int ts_offset = map->offsets[i];
+               if (ts_offset < 0) break;
+               int len = ts_offset-old_offset;
+               if (dir==W) memcpy(old+old_offset, new+new_offset, len);
+               else memcpy(new+new_offset, old+old_offset, len);
+               new_offset += len;
+               old_offset += len;
+               long long new_ts[2];
+               long old_ts[2];
+               int align = map->force_align ? sizeof(time_t) : alignof(time_t);
+               new_offset += (align-1) & -new_offset;
+               if (dir==W) {
+                       memcpy(new_ts, new+new_offset, sizeof new_ts);
+                       old_ts[0] = new_ts[0];
+                       old_ts[1] = new_ts[1];
+                       memcpy(old+old_offset, old_ts, sizeof old_ts);
+               } else {
+                       memcpy(old_ts, old+old_offset, sizeof old_ts);
+                       new_ts[0] = old_ts[0];
+                       new_ts[1] = old_ts[1];
+                       memcpy(new+new_offset, new_ts, sizeof new_ts);
+               }
+               new_offset += sizeof new_ts;
+               old_offset += sizeof old_ts;
+       }
+       if (dir==W) memcpy(old+old_offset, new+new_offset, old_size-old_offset);
+       else memcpy(new+new_offset, old+old_offset, old_size-old_offset);
+}
+
 int ioctl(int fd, int req, ...)
 {
        void *arg;
@@ -13,23 +68,17 @@ int ioctl(int fd, int req, ...)
        arg = va_arg(ap, void *);
        va_end(ap);
        int r = __syscall(SYS_ioctl, fd, req, arg);
-       if (r==-ENOTTY) switch (req) {
-       case SIOCGSTAMP:
-       case SIOCGSTAMPNS:
-               if (SIOCGSTAMP==SIOCGSTAMP_OLD) break;
-               if (req==SIOCGSTAMP) req=SIOCGSTAMP_OLD;
-               if (req==SIOCGSTAMPNS) req=SIOCGSTAMPNS_OLD;
-               long t32[2];
-               r = __syscall(SYS_ioctl, fd, req, t32);
-               if (r<0) break;
-               if (req==SIOCGSTAMP_OLD) {
-                       struct timeval *tv = arg;
-                       tv->tv_sec = t32[0];
-                       tv->tv_usec = t32[1];
-               } else {
-                       struct timespec *ts = arg;
-                       ts->tv_sec = t32[0];
-                       ts->tv_nsec = t32[1];
+       if (r==-ENOTTY) {
+               for (int i=0; i<sizeof compat_map/sizeof *compat_map; i++) {
+                       if (compat_map[i].new_req != req) continue;
+                       union {
+                               long long align;
+                               char buf[256];
+                       } u;
+                       convert_ioctl_struct(&compat_map[i], u.buf, arg, W);
+                       r = __syscall(SYS_ioctl, fd, compat_map[i].old_req, u.buf);
+                       if (r<0) break;
+                       convert_ioctl_struct(&compat_map[i], u.buf, arg, R);
                }
        }
        return __syscall_ret(r);