diff --git a/distrib/sets/lists/minix-base/mi b/distrib/sets/lists/minix-base/mi index 6869d3762..c010fbb60 100644 --- a/distrib/sets/lists/minix-base/mi +++ b/distrib/sets/lists/minix-base/mi @@ -206,6 +206,7 @@ ./service/lwip minix-base ./service/memory minix-base ./service/mfs minix-base +./service/mib minix-base ./service/pfs minix-base ./service/pm minix-base ./service/procfs minix-base diff --git a/distrib/sets/lists/minix-comp/mi b/distrib/sets/lists/minix-comp/mi index 13e2b06ff..9ba9837a1 100644 --- a/distrib/sets/lists/minix-comp/mi +++ b/distrib/sets/lists/minix-comp/mi @@ -1237,6 +1237,7 @@ ./usr/include/minix/sound.h minix-comp ./usr/include/minix/spin.h minix-comp ./usr/include/minix/sys_config.h minix-comp +./usr/include/minix/sysctl.h minix-comp ./usr/include/minix/sysinfo.h minix-comp ./usr/include/minix/syslib.h minix-comp ./usr/include/minix/sysutil.h minix-comp diff --git a/distrib/sets/lists/minix-kernel/mi b/distrib/sets/lists/minix-kernel/mi index eb45deb93..94140ed9d 100644 --- a/distrib/sets/lists/minix-kernel/mi +++ b/distrib/sets/lists/minix-kernel/mi @@ -17,10 +17,11 @@ ./boot/minix/.temp/mod05_vfs minix-kernel ./boot/minix/.temp/mod06_memory minix-kernel ./boot/minix/.temp/mod07_tty minix-kernel -./boot/minix/.temp/mod08_mfs minix-kernel +./boot/minix/.temp/mod08_mib minix-kernel ./boot/minix/.temp/mod09_vm minix-kernel ./boot/minix/.temp/mod10_pfs minix-kernel -./boot/minix/.temp/mod11_init minix-kernel +./boot/minix/.temp/mod11_mfs minix-kernel +./boot/minix/.temp/mod12_init minix-kernel ./etc minix-kernel ./etc/mtree minix-kernel ./etc/mtree/set.minix-kernel minix-kernel diff --git a/distrib/sets/lists/minix-tests/mi b/distrib/sets/lists/minix-tests/mi index f3d894979..a41fcd7e5 100644 --- a/distrib/sets/lists/minix-tests/mi +++ b/distrib/sets/lists/minix-tests/mi @@ -186,6 +186,7 @@ ./usr/tests/minix-posix/test84 minix-tests ./usr/tests/minix-posix/test85 minix-tests ./usr/tests/minix-posix/test86 minix-tests +./usr/tests/minix-posix/test87 minix-tests ./usr/tests/minix-posix/test9 minix-tests ./usr/tests/minix-posix/testinterp minix-tests ./usr/tests/minix-posix/testisofs minix-tests diff --git a/etc/rc b/etc/rc index 2e044d101..d38ff0bf5 100755 --- a/etc/rc +++ b/etc/rc @@ -157,6 +157,7 @@ autoboot|start) edit ds edit tty edit memory + edit mib edit pfs edit init # diff --git a/etc/system.conf b/etc/system.conf index 25b5426ac..1edd43d0d 100644 --- a/etc/system.conf +++ b/etc/system.conf @@ -216,6 +216,15 @@ service log priority 2; }; +service mib +{ + system + VIRCOPY # 15 + ; + ipc ALL; + uid 0; +}; + service init { uid 0; diff --git a/lib/libc/gen/Makefile.inc b/lib/libc/gen/Makefile.inc index ae2006695..695347aff 100644 --- a/lib/libc/gen/Makefile.inc +++ b/lib/libc/gen/Makefile.inc @@ -21,8 +21,7 @@ SRCS+= _errno.c .if defined(__MINIX) # Unsupported by Minix. # closefrom.c confstr.c extattr.c getdevmajor.c \ -# pthread_atfork.c \ -# sysctlbyname.c sysctlgetmibinfo.c sysctlnametomib.c +# pthread_atfork.c # # To be ported # nlist.c nlist_aout.c nlist_coff.c nlist_ecoff.c nlist_elf32.c nlist_elf64.c @@ -30,7 +29,7 @@ SRCS+= _errno.c # Not useful but portable # disklabel.c -SRCS+= alarm.c alphasort.c arc4random.c assert.c \ +SRCS+= alarm.c alphasort.c arc4random.c assert.c asysctl.c \ basename.c clock.c closedir.c \ ctermid.c ctype_.c daemon.c \ dehumanize_number.c devname.c dirname.c err.c errx.c \ @@ -53,8 +52,8 @@ SRCS+= alarm.c alphasort.c arc4random.c assert.c \ shquote.c shquotev.c sighold.c sigignore.c siginterrupt.c \ siglist.c signal.c signame.c sigrelse.c \ sigset.c sigsetops.c sleep.c \ - stringlist.c sysconf.c sysctl.c \ - syslog.c telldir.c time.c \ + stringlist.c sysconf.c sysctl.c sysctlbyname.c sysctlgetmibinfo.c \ + sysctlnametomib.c syslog.c telldir.c time.c \ times.c toascii.c tolower_.c ttyname.c ttyslot.c toupper_.c ualarm.c \ ulimit.c uname.c unvis.c usleep.c utime.c utimens.c utmp.c \ utmpx.c valloc.c vis.c wait.c wait3.c waitpid.c warn.c warnx.c \ diff --git a/lib/libc/gen/sysctl.c b/lib/libc/gen/sysctl.c index e3fae8c71..11e5d102c 100644 --- a/lib/libc/gen/sysctl.c +++ b/lib/libc/gen/sysctl.c @@ -69,16 +69,6 @@ static size_t __cvt_node_out(uint, const struct sysctlnode *, void **, #include -#if defined(__minix) -int __sysctl(const int *name, unsigned int namelen, - void *oldp, size_t *oldlenp, - const void *newp, size_t newlen) -{ - errno = ENOENT; - return -1; -} -#endif /* defined(__minix) */ - int sysctl(const int *name, unsigned int namelen, void *oldp, size_t *oldlenp, diff --git a/minix/fs/procfs/service.c b/minix/fs/procfs/service.c index 73cddc9f4..bb2606e65 100644 --- a/minix/fs/procfs/service.c +++ b/minix/fs/procfs/service.c @@ -127,6 +127,7 @@ service_get_policies(struct policies * pol, index_t slot) { .label = "input", .policy_str = "reset" }, { .label = "ipc", .policy_str = "restart" }, { .label = "is", .policy_str = "restart" }, + { .label = "mib", .policy_str = "restart" }, { .label = "pm", .policy_str = "restart" }, { .label = "rs", .policy_str = "restart" }, { .label = "sched", .policy_str = "restart" }, diff --git a/minix/include/minix/Makefile b/minix/include/minix/Makefile index 65406db33..77627ba9f 100644 --- a/minix/include/minix/Makefile +++ b/minix/include/minix/Makefile @@ -17,7 +17,7 @@ INCS+= acpi.h audio_fw.h bitmap.h \ netdriver.h optset.h padconf.h partition.h portio.h \ priv.h procfs.h profile.h queryparam.h \ rs.h safecopies.h sched.h sef.h sffs.h \ - sound.h spin.h sys_config.h sysinfo.h \ + sound.h spin.h sys_config.h sysctl.h sysinfo.h \ syslib.h sysutil.h timers.h type.h \ u64.h usb.h usb_ch9.h vbox.h \ vboxfs.h vboxif.h vboxtype.h vm.h \ diff --git a/minix/include/minix/com.h b/minix/include/minix/com.h index ffd909946..76621a517 100644 --- a/minix/include/minix/com.h +++ b/minix/include/minix/com.h @@ -29,6 +29,7 @@ * 0x1500 - 0x15FF Input server messages * 0x1600 - 0x16FF VirtualBox (VBOX) requests (see vboxif.h) * 0x1700 - 0x17FF PTYFS requests + * 0x1800 - 0x18FF Management Information Base (MIB) requests * * Zero and negative values are widely used for OK and error responses. */ @@ -60,10 +61,11 @@ #define SCHED_PROC_NR ((endpoint_t) 4) /* scheduler */ #define TTY_PROC_NR ((endpoint_t) 5) /* terminal (TTY) driver */ #define DS_PROC_NR ((endpoint_t) 6) /* data store server */ -#define MFS_PROC_NR ((endpoint_t) 7) /* minix root filesystem */ +#define MIB_PROC_NR ((endpoint_t) 7) /* management info base service */ #define VM_PROC_NR ((endpoint_t) 8) /* memory server */ #define PFS_PROC_NR ((endpoint_t) 9) /* pipe filesystem */ -#define LAST_SPECIAL_PROC_NR 10 /* An untyped version for +#define MFS_PROC_NR ((endpoint_t) 10) /* minix root filesystem */ +#define LAST_SPECIAL_PROC_NR 11 /* An untyped version for computation in macros.*/ #define INIT_PROC_NR ((endpoint_t) LAST_SPECIAL_PROC_NR) /* init -- goes multiuser */ @@ -1002,6 +1004,18 @@ #define RTCDEV_Y2KBUG 0x01 /* Interpret 1980 as 2000 for RTC w/Y2K bug */ #define RTCDEV_CMOSREG 0x02 /* Also set the CMOS clock register bits. */ +/*===========================================================================* + * Calls to MIB * + *===========================================================================*/ + +#define MIB_BASE 0x1800 + +#define IS_MIB_CALL(type) (((type) & ~0xff) == MIB_BASE) + +#define MIB_SYSCTL (MIB_BASE + 0) /* sysctl(2) */ + +#define NR_MIB_CALLS 1 /* highest number from base plus one */ + /*===========================================================================* * Internal codes used by several services * *===========================================================================*/ diff --git a/minix/include/minix/ipc.h b/minix/include/minix/ipc.h index 26c249180..f88ab4665 100644 --- a/minix/include/minix/ipc.h +++ b/minix/include/minix/ipc.h @@ -12,6 +12,7 @@ *==========================================================================*/ #define M_PATH_STRING_MAX 40 +#define CTL_SHORTNAME 8 /* max sysctl(2) name length that fits in message */ typedef struct { uint8_t data[56]; @@ -420,6 +421,17 @@ typedef struct { } mess_lc_ipc_shmget; _ASSERT_MSG_SIZE(mess_lc_ipc_shmget); +typedef struct { + vir_bytes oldp; + size_t oldlen; + vir_bytes newp; + size_t newlen; + unsigned int namelen; + vir_bytes namep; + int name[CTL_SHORTNAME]; +} mess_lc_mib_sysctl; +_ASSERT_MSG_SIZE(mess_lc_mib_sysctl); + typedef struct { vir_bytes name; size_t namelen; @@ -1396,6 +1408,12 @@ typedef struct { } mess_lsys_vm_watch_exit; _ASSERT_MSG_SIZE(mess_lsys_vm_watch_exit); +typedef struct { + size_t oldlen; + uint8_t padding[52]; +} mess_mib_lc_sysctl; +_ASSERT_MSG_SIZE(mess_mib_lc_sysctl); + typedef struct { off_t offset; void *addr; @@ -2074,6 +2092,7 @@ typedef struct noxfer_message { mess_lc_ipc_shmctl m_lc_ipc_shmctl; mess_lc_ipc_shmdt m_lc_ipc_shmdt; mess_lc_ipc_shmget m_lc_ipc_shmget; + mess_lc_mib_sysctl m_lc_mib_sysctl; mess_lc_pm_exec m_lc_pm_exec; mess_lc_pm_exit m_lc_pm_exit; mess_lc_pm_getsid m_lc_pm_getsid; @@ -2182,6 +2201,7 @@ typedef struct noxfer_message { mess_lsys_vm_update m_lsys_vm_update; mess_lsys_vm_vmremap m_lsys_vm_vmremap; mess_lsys_vm_watch_exit m_lsys_vm_watch_exit; + mess_mib_lc_sysctl m_mib_lc_sysctl; mess_mmap m_mmap; mess_net_netdrv_dl_conf m_net_netdrv_dl_conf; mess_net_netdrv_dl_getstat_s m_net_netdrv_dl_getstat_s; diff --git a/minix/include/minix/sysctl.h b/minix/include/minix/sysctl.h new file mode 100644 index 000000000..839392ea0 --- /dev/null +++ b/minix/include/minix/sysctl.h @@ -0,0 +1,53 @@ +#ifndef _MINIX_SYSCTL_H +#define _MINIX_SYSCTL_H + +/* MINIX3-specific sysctl(2) extensions. */ + +#include + +/* Special values. */ +#define SYSCTL_NODE_FN ((sysctlfn)0x1) /* node is function-driven */ + +/* + * The top-level MINIX3 identifier is quite a bit beyond the last top-level + * identifier in use by NetBSD, because NetBSD may add more later, and we do + * not want conflicts: this definition is part of the MINIX3 ABI. + */ +#define CTL_MINIX 32 + +#if CTL_MAXID > CTL_MINIX +#error "CTL_MAXID has grown too large!" +#endif + +/* + * The identifiers below follow the standard sysctl naming scheme, which means + * care should be taken not to introduce clashes with other definitions + * elsewhere. On the upside, not many places need to include this header file. + */ +#define MINIX_TEST 0 +#define MINIX_MIB 1 + +/* + * These identifiers, under MINIX_TEST, are used by test87 to test the MIB + * service. + */ +#define TEST_INT 0 +#define TEST_BOOL 1 +#define TEST_QUAD 2 +#define TEST_STRING 3 +#define TEST_STRUCT 4 +#define TEST_PRIVATE 5 +#define TEST_ANYWRITE 6 +#define TEST_DYNAMIC 7 +#define TEST_SECRET 8 +#define TEST_PERM 9 +#define TEST_DESTROY1 10 +#define TEST_DESTROY2 11 + +#define SECRET_VALUE 0 + +/* Identifiers for subnodes of MINIX_MIB. */ +#define MIB_NODES 1 +#define MIB_OBJECTS 2 + +#endif /* !_MINIX_SYSCTL_H */ diff --git a/minix/kernel/table.c b/minix/kernel/table.c index 425e23cdf..d52d2fa03 100644 --- a/minix/kernel/table.c +++ b/minix/kernel/table.c @@ -43,7 +43,7 @@ */ struct boot_image image[NR_BOOT_PROCS] = { -/* process nr, flags, stack size, name */ +/* process nr, name */ {ASYNCM, "asyncm"}, {IDLE, "idle" }, {CLOCK, "clock" }, @@ -58,9 +58,10 @@ struct boot_image image[NR_BOOT_PROCS] = { {VFS_PROC_NR, "vfs" }, {MEM_PROC_NR, "memory"}, {TTY_PROC_NR, "tty" }, -{MFS_PROC_NR, "mfs" }, +{MIB_PROC_NR, "mib" }, {VM_PROC_NR, "vm" }, {PFS_PROC_NR, "pfs" }, +{MFS_PROC_NR, "mfs" }, {INIT_PROC_NR, "init" }, }; diff --git a/minix/lib/libc/sys/Makefile.inc b/minix/lib/libc/sys/Makefile.inc index 93b902ee1..a443dfc16 100644 --- a/minix/lib/libc/sys/Makefile.inc +++ b/minix/lib/libc/sys/Makefile.inc @@ -23,7 +23,7 @@ SRCS+= accept.c access.c adjtime.c bind.c brk.c sbrk.c m_closefrom.c getsid.c \ wait4.c write.c \ utimensat.c utimes.c futimes.c lutimes.c futimens.c \ _exit.c _ucontext.c environ.c __getcwd.c vfork.c sizeup.c init.c \ - getrusage.c setrlimit.c setpgid.c + getrusage.c setrlimit.c setpgid.c __sysctl.c # Minix specific syscalls / utils. SRCS+= kernel_utils.c sprofile.c stack_utils.c _mcontext.c diff --git a/minix/lib/libc/sys/__sysctl.c b/minix/lib/libc/sys/__sysctl.c new file mode 100644 index 000000000..fd02bfa66 --- /dev/null +++ b/minix/lib/libc/sys/__sysctl.c @@ -0,0 +1,40 @@ +#include +#include +#include "namespace.h" +#include "extern.h" +#include + +/* + * The sysctl(2) system call, handled by the MIB service. + */ +int +__sysctl(const int * name, unsigned int namelen, void * oldp, size_t * oldlenp, + const void * newp, size_t newlen) +{ + message m; + int r; + + memset(&m, 0, sizeof(m)); + m.m_lc_mib_sysctl.oldp = (vir_bytes)oldp; + m.m_lc_mib_sysctl.oldlen = (oldlenp != NULL) ? *oldlenp : 0; + m.m_lc_mib_sysctl.newp = (vir_bytes)newp; + m.m_lc_mib_sysctl.newlen = newlen; + m.m_lc_mib_sysctl.namelen = namelen; + m.m_lc_mib_sysctl.namep = (vir_bytes)name; + if (namelen <= CTL_SHORTNAME) + memcpy(m.m_lc_mib_sysctl.name, name, sizeof(*name) * namelen); + + r = _syscall(MIB_PROC_NR, MIB_SYSCTL, &m); + + /* + * We copy the NetBSD behavior of replying with the old length also if + * the call failed, typically with ENOMEM. This is undocumented + * behavior, but unfortunately relied on by sysctl(8) and other NetBSD + * userland code. If the call failed at the IPC level, the resulting + * value will be garbage, but it should then not be used anyway. + */ + if (oldlenp != NULL) + *oldlenp = m.m_mib_lc_sysctl.oldlen; + + return r; +} diff --git a/minix/lib/libminc/Makefile b/minix/lib/libminc/Makefile index 8e9cb8a7c..4375cf88b 100644 --- a/minix/lib/libminc/Makefile +++ b/minix/lib/libminc/Makefile @@ -270,6 +270,17 @@ CPPFLAGS.malloc.c+= -D_LIBSYS SECTIONIFY.malloc.c+= -sectionify-no-override \ -sectionify-data-section-map=.*/magic_malloc_data +.for f in \ + strdup.o +${f} ${f:C/\.o/.bc/}: ${LIBCDIR}/string/${f:C/\.o/.c/} +OBJS+= ${f} +CLEANFILES+= ${f} + +.if ${USE_BITCODE:Uno} == "yes" +OBJS+= ${f:C/\.o/.bc/} +CLEANFILES+= ${f:C/\.o/.bc/} +.endif # ${USE_BITCODE:Uno} == "yes" +.endfor .for f in \ access.o brk.o close.o environ.o execve.o fork.o fsync.o \ diff --git a/minix/servers/Makefile b/minix/servers/Makefile index a39b2bdab..6787aabb4 100644 --- a/minix/servers/Makefile +++ b/minix/servers/Makefile @@ -1,6 +1,6 @@ .include -SUBDIR+= ds input pm rs sched vfs vm +SUBDIR+= ds input mib pm rs sched vfs vm .if ${MKIMAGEONLY} == "no" SUBDIR+= ipc is devman diff --git a/minix/servers/mib/Makefile b/minix/servers/mib/Makefile new file mode 100644 index 000000000..ca4de7151 --- /dev/null +++ b/minix/servers/mib/Makefile @@ -0,0 +1,11 @@ +# Makefile for the Management Information Base (MIB) server + +PROG= mib +SRCS= main.c tree.c kern.c minix.c + +DPADD+= ${LIBSYS} +LDADD+= -lsys + +WARNS?= 5 + +.include diff --git a/minix/servers/mib/kern.c b/minix/servers/mib/kern.c new file mode 100644 index 000000000..688c3ca62 --- /dev/null +++ b/minix/servers/mib/kern.c @@ -0,0 +1,19 @@ +/* MIB service - kern.c - implementation of the CTL_KERN subtree */ + +#include "mib.h" + +static struct mib_node mib_kern_table[] = { +/* 8*/ [KERN_ARGMAX] = MIB_INT(_P | _RO, ARG_MAX, "argmax", + "Maximum number of bytes of arguments to " + "execve(2)"), +}; + +/* + * Initialize the CTL_KERN subtree. + */ +void +mib_kern_init(struct mib_node * node) +{ + + MIB_INIT_ENODE(node, mib_kern_table); +} diff --git a/minix/servers/mib/main.c b/minix/servers/mib/main.c new file mode 100644 index 000000000..327231a04 --- /dev/null +++ b/minix/servers/mib/main.c @@ -0,0 +1,401 @@ +/* MIB service - main.c - request abstraction and first-level tree */ +/* + * This is the Management Information Base (MIB) service. Its one and only + * task is to implement the sysctl(2) system call, which plays a fairly + * important role in parts of *BSD userland. + * + * The sysctl(2) interface is used to access a variety of information. In + * order to obtain that information, and possibly modify it, the MIB service + * calls into many other services. The MIB service must therefore not be + * called directly from other services, with the exception of ProcFS. In fact, + * ProcFS is currently the only service that is modeled as logically higher in + * the MINIX3 service stack than MIB, something that itself is possible only + * due to the nonblocking nature of VFS. MIB may issue blocking calls to VFS. + * + * The MIB service is in the boot image because even init(8) makes use of + * sysctl(2) during its own startup, so launching the MIB service at any later + * time would make a proper implementation of sysctl(2) impossible. Also, the + * service needs superuser privileges because it may need to issue privileged + * calls and obtain privileged information from other services. + * + * The MIB service was created by David van Moolenbroek . + */ + +#include "mib.h" + +/* + * Most of these initially empty nodes are filled in by their corresponding + * modules' _init calls; see mib_init below. However, CTL_USER stays empty: + * the libc sysctl(3) wrapper code takes care of that subtree. It must have + * an entry here though, or sysctl(8) will not list it. CTL_VENDOR is also + * empty, but writable, so that it may be used by third parties. + */ +static struct mib_node mib_table[] = { +/* 1*/ [CTL_KERN] = MIB_ENODE(_P | _RO, "kern", "High kernel"), +/* 8*/ [CTL_USER] = MIB_ENODE(_P | _RO, "user", "User-level"), +/*11*/ [CTL_VENDOR] = MIB_ENODE(_P | _RW, "vendor", "Vendor specific"), +/*32*/ [CTL_MINIX] = MIB_ENODE(_P | _RO, "minix", "MINIX3 specific"), +}; + +/* + * The root node of the tree. The root node is used internally only--it is + * impossible to access the root node itself from userland in any way. The + * node is writable by default, so that programs such as init(8) may create + * their own top-level entries. + */ +static struct mib_node mib_root = MIB_NODE(_RW, mib_table, "", ""); + +/* + * Structures describing old and new data as provided by userland. The primary + * advantage of these opaque structures is that we could in principle use them + * to implement storage of small data results in the sysctl reply message, so + * as to avoid the kernel copy, without changing any of the handler code. + */ +struct mib_oldp { + endpoint_t oldp_endpt; + vir_bytes oldp_addr; + size_t oldp_len; +}; +/* + * Same structure, different type: prevent accidental mixups, and avoid the + * need to use __restrict everywhere. + */ +struct mib_newp { + endpoint_t newp_endpt; + vir_bytes newp_addr; + size_t newp_len; +}; + +/* + * Return TRUE or FALSE indicating whether the given offset is within the range + * of data that is to be copied out. This call can be used to test whether + * certain bits of data need to be prepared for copying at all. + */ +int +mib_inrange(struct mib_oldp * oldp, size_t off) +{ + + if (oldp == NULL) + return FALSE; + + return (off < oldp->oldp_len); +} + +/* + * Return the total length of the requested data. This should not be used + * directly except in highly unusual cases, such as particular node requests + * where the request semantics blatantly violate overall sysctl(2) semantics. + */ +size_t +mib_getoldlen(struct mib_oldp * oldp) +{ + + if (oldp == NULL) + return 0; + + return oldp->oldp_len; +} + +/* + * Copy out (partial) data to the user. The copy is automatically limited to + * the range of data requested by the user. Return the requested length on + * success (for the caller's convenience) or an error code on failure. + */ +ssize_t +mib_copyout(struct mib_oldp * __restrict oldp, size_t off, + const void * __restrict buf, size_t size) +{ + size_t len; + int r; + + len = size; + assert(len <= SSIZE_MAX); + + if (oldp == NULL || off >= oldp->oldp_len) + return size; /* nothing to do */ + + if (len > oldp->oldp_len - off) + len = oldp->oldp_len - off; + + if ((r = sys_datacopy(SELF, (vir_bytes)buf, oldp->oldp_endpt, + oldp->oldp_addr + off, len)) != OK) + return r; + + return size; +} + +/* + * Override the oldlen value returned from the call, in situations where an + * error is thrown as well. + */ +void +mib_setoldlen(struct mib_call * call, size_t oldlen) +{ + + call->call_reslen = oldlen; +} + +/* + * Return the new data length as provided by the user, or 0 if the user did not + * supply new data. + */ +size_t +mib_getnewlen(struct mib_newp * newp) +{ + + if (newp == NULL) + return 0; + + return newp->newp_len; +} + +/* + * Copy in data from the user. The given length must match exactly the length + * given by the user. Return OK or an error code. + */ +int +mib_copyin(struct mib_newp * __restrict newp, void * __restrict buf, + size_t len) +{ + + if (newp == NULL || len != newp->newp_len) + return EINVAL; + + if (len == 0) + return OK; + + return sys_datacopy(newp->newp_endpt, newp->newp_addr, SELF, + (vir_bytes)buf, len); +} + +/* + * Copy in auxiliary data from the user, based on a user pointer obtained from + * data copied in earlier through mib_copyin(). + */ +int +mib_copyin_aux(struct mib_newp * __restrict newp, vir_bytes addr, + void * __restrict buf, size_t len) +{ + + assert(newp != NULL); + + if (len == 0) + return OK; + + return sys_datacopy(newp->newp_endpt, addr, SELF, (vir_bytes)buf, len); +} + +/* + * Check whether the user is allowed to perform privileged operations. The + * function returns a nonzero value if this is the case, and zero otherwise. + * Authorization is performed only once per call. + */ +int +mib_authed(struct mib_call * call) +{ + + if ((call->call_flags & (MIB_FLAG_AUTH | MIB_FLAG_NOAUTH)) == 0) { + /* Ask PM if this endpoint has superuser privileges. */ + if (getnuid(call->call_endpt) == SUPER_USER) + call->call_flags |= MIB_FLAG_AUTH; + else + call->call_flags |= MIB_FLAG_NOAUTH; + } + + return (call->call_flags & MIB_FLAG_AUTH); +} + +/* + * Implement the sysctl(2) system call. + */ +static int +mib_sysctl(message * __restrict m_in, message * __restrict m_out) +{ + vir_bytes oldaddr, newaddr; + size_t oldlen, newlen; + unsigned int namelen; + int s, name[CTL_MAXNAME]; + endpoint_t endpt; + struct mib_oldp oldp, *oldpp; + struct mib_newp newp, *newpp; + struct mib_call call; + ssize_t r; + + endpt = m_in->m_source; + oldaddr = m_in->m_lc_mib_sysctl.oldp; + oldlen = m_in->m_lc_mib_sysctl.oldlen; + newaddr = m_in->m_lc_mib_sysctl.newp; + newlen = m_in->m_lc_mib_sysctl.newlen; + namelen = m_in->m_lc_mib_sysctl.namelen; + + if (namelen == 0 || namelen > CTL_MAXNAME) + return EINVAL; + + /* + * In most cases, the entire name fits in the request message, so we + * can avoid a kernel copy. + */ + if (namelen > CTL_SHORTNAME) { + if ((s = sys_datacopy(endpt, m_in->m_lc_mib_sysctl.namep, SELF, + (vir_bytes)&name, sizeof(name[0]) * namelen)) != OK) + return s; + } else + memcpy(name, m_in->m_lc_mib_sysctl.name, + sizeof(name[0]) * namelen); + + /* + * Set up a structure for the old data, if any. When no old address is + * given, be forgiving if oldlen is not zero, as the user may simply + * not have initialized the variable before passing a pointer to it. + */ + if (oldaddr != 0) { + oldp.oldp_endpt = endpt; + oldp.oldp_addr = oldaddr; + oldp.oldp_len = oldlen; + oldpp = &oldp; + } else + oldpp = NULL; + + /* + * Set up a structure for the new data, if any. If one of newaddr and + * newlen is zero but not the other, we (like NetBSD) disregard both. + */ + if (newaddr != 0 && newlen != 0) { + newp.newp_endpt = endpt; + newp.newp_addr = newaddr; + newp.newp_len = newlen; + newpp = &newp; + } else + newpp = NULL; + + /* + * Set up a structure for other call parameters. Most of these should + * be used rarely, and we may want to add more later, so do not pass + * all of them around as actual function parameters all the time. + */ + call.call_endpt = endpt; + call.call_name = name; + call.call_namelen = namelen; + call.call_flags = 0; + call.call_reslen = 0; + + r = mib_dispatch(&call, &mib_root, oldpp, newpp); + + /* + * From NetBSD: we copy out as much as we can from the old data, while + * at the same time computing the full data length. Then, here at the + * end, if the entire result did not fit in the destination buffer, we + * return ENOMEM instead of success, thus also returning a partial + * result and the full data length. + * + * It is also possible that data are copied out along with a "real" + * error. In that case, we must report a nonzero resulting length + * along with that error code. This is currently the case when node + * creation resulted in a collision, in which case the error code is + * EEXIST while the existing node is copied out as well. + */ + if (r >= 0) { + m_out->m_mib_lc_sysctl.oldlen = (size_t)r; + + if (oldaddr != 0 && oldlen < (size_t)r) + r = ENOMEM; + else + r = OK; + } else + m_out->m_mib_lc_sysctl.oldlen = call.call_reslen; + + return r; +} + +/* + * Initialize the service. + */ +static int +mib_init(int type __unused, sef_init_info_t * info __unused) +{ + + /* + * Initialize pointers and sizes of subtrees in different modules. + * This is needed because we cannot use sizeof on external arrays. + * We do initialize the node entry (including any other fields) + * statically through MIB_ENODE because that forces the array to be + * large enough to store the entry. + */ + mib_kern_init(&mib_table[CTL_KERN]); + mib_minix_init(&mib_table[CTL_MINIX]); + + /* + * Now that the static tree is complete, go through the entire tree, + * initializing miscellaneous fields. + */ + mib_tree_init(&mib_root); + + return OK; +} + +/* + * Perform SEF startup. + */ +static void +mib_startup(void) +{ + + sef_setcb_init_fresh(mib_init); + /* + * If we restart we lose all dynamic state, which means we lose all + * nodes that have been created at run time. However, running with + * only the static node tree is still better than not running at all. + */ + sef_setcb_init_restart(mib_init); + + sef_startup(); +} + +/* + * The Management Information Base (MIB) service. + */ +int +main(void) +{ + message m_in, m_out; + int r, ipc_status; + + /* Perform initialization. */ + mib_startup(); + + /* The main message loop. */ + for (;;) { + /* Receive a request. */ + if ((r = sef_receive_status(ANY, &m_in, &ipc_status)) != OK) + panic("sef_receive failed: %d", r); + + /* Process the request. */ + if (is_ipc_notify(ipc_status)) { + /* We are not expecting any notifications. */ + printf("MIB: notification from %d\n", m_in.m_source); + + continue; + } + + memset(&m_out, 0, sizeof(m_out)); + + switch (m_in.m_type) { + case MIB_SYSCTL: + r = mib_sysctl(&m_in, &m_out); + + break; + + default: + r = ENOSYS; + } + + /* Send the reply. */ + m_out.m_type = r; + + if ((r = ipc_sendnb(m_in.m_source, &m_out)) != OK) + printf("MIB: ipc_sendnb failed (%d)\n", r); + } + + /* NOTREACHED */ + return 0; +} diff --git a/minix/servers/mib/mib.h b/minix/servers/mib/mib.h new file mode 100644 index 000000000..b21fd8c68 --- /dev/null +++ b/minix/servers/mib/mib.h @@ -0,0 +1,268 @@ +#ifndef _MINIX_MIB_MIB_H +#define _MINIX_MIB_MIB_H + +#include +#include +#include +#include + +/* + * The following setting toggles the existence of the minix.test subtree. For + * production environments, it should probably be disabled, although it should + * do no harm either. For development platforms, it should be enabled, or + * test87 will fail. + */ +#define MINIX_TEST_SUBTREE 1 /* include the minix.test subtree? */ + +struct mib_oldp; +struct mib_newp; + +/* + * This structure contains a number of less heavily used parameters for handler + * functions, mainly to provide extensibility while limiting argument clutter. + */ +struct mib_call { + endpoint_t call_endpt; /* endpoint of the user process */ + const int *call_name; /* remaining part of the name */ + unsigned int call_namelen; /* length of the remaining name part */ + unsigned int call_flags; /* internal call processing flags */ + size_t call_reslen; /* resulting oldlen value on error */ +}; + +/* Call flags. */ +#define MIB_FLAG_AUTH 0x01 /* user verified to be superuser */ +#define MIB_FLAG_NOAUTH 0x02 /* user verified to be regular user */ + +/* + * We reassign new meaning to two NetBSD node flags, because we do not use the + * flags in the way NetBSD does: + * + * - On NetBSD, CTLFLAG_ROOT is used to mark the root of the sysctl tree. The + * entire root node is not exposed to userland, and thus, neither is this + * flag. We do not need the flag as we do not have parent pointers. + * - On NetBSD, CTLFLAG_ALIAS is used to mark one node as an alias of another + * node, presumably to avoid having to duplicate entire subtrees. We can + * simply have two nodes point to the same subtree instead, and thus, we do + * not need to support this functionality at all. + * + * The meaning of our replacement flags is explained further below. We ensure + * that neither of these flags are ever exposed to userland. As such, our own + * definitions can be changed as necessary without breaking anything. + */ +#define CTLFLAG_PARENT CTLFLAG_ROOT /* node is a real parent node */ +#define CTLFLAG_VERIFY CTLFLAG_ALIAS /* node has verification function */ + +/* + * The following node structure definition aims to meet several goals at once: + * + * 1) it can be used for static and dynamic nodes; + * 2) it can be used to point to both static and dynamic child arrays at once; + * 3) it allows for embedded, pointed-to, and function-generated data; + * 4) its unions are compatible with magic instrumentation; + * 5) it is optimized for size, assuming many static and few dynamic nodes. + * + * All nodes have flags, a size, a version, a name, and optionally a + * description. The use of the rest of the fields depends on the type of the + * node, which is defined by part of the flags field. + * + * Data nodes, that is, nodes of type CTLTYPE_{BOOL,INT,QUAD,STRING,STRUCT}, + * have associated data. For types CTLTYPE_{BOOL,INT,QUAD}, the node may have + * immediate data (CTLFLAG_IMMEDIATE), in which case the value of the node is + * stored in the node structure itself (node_bool, node_int, node_quad). These + * node types may instead also have a pointer to data. This is always the case + * for types CTLTYPE_STRING and CTLTYPE_STRUCT. In that case, node_data is a + * valid pointer, and CTLFLAG_IMMEDIATE is not set. Either way, node_size is + * the size of the data, which for strings is the maximum string size; for + * other types, it defines the exact field size. In addition, data nodes may + * have the CTLFLAG_VERIFY flag set, which indicates that node_valid points + * to a callback function that verifies whether a newly written value is valid + * for the node. If this flag is not set, data nodes may have an associated + * function, in which case node_func is not NULL, which will be called to read + * and write data instead. The function may optionally use the node's regular + * (size, immediate and/or pointer) data fields as it sees fit. + * + * Node-type nodes, of type CTLTYPE_NODE, behave differently. Such nodes may + * have either static and dynamic child nodes, or an associated function. Such + * a function handles all access to the entire subtree. If no function is set, + * the CTLFLAG_PARENT flag is set, to indicate that this node is the root of a + * real subtree; CTLFLAG_PARENT must not be set if the node has an associated + * function. For real node-type nodes (with CTLFLAG_PARENT set), node_size is + * the number (not size!) of the array of static child nodes, which is pointed + * to by node_scptr and indexed by child identifier. Within the static array, + * child nodes with zeroed flags fields are not in use. The node_dcptr field + * points to a linked list of dynamic child nodes. The node_csize field is set + * to the size of the static array plus the number of dynamic nodes; node_clen + * is set to the number of valid entries in the static array plus the number of + * dynamic nodes. If a function is set, none of these fields are used, and the + * node_size field is typically (but not necessarily) set to zero. + * + * The structure uses unions for either only pointers or only non-pointers, to + * simplify live update support. However, this does not mean the structure is + * not fully used: real node-type nodes use node_{flags,size,ver,csize,clen, + * scptr,dcptr,name,desc}, which together add up to the full structure size. + */ +struct mib_node; +struct mib_dynode; + +typedef ssize_t (*mib_func_ptr)(struct mib_call *, struct mib_node *, + struct mib_oldp *, struct mib_newp *); +typedef int (*mib_verify_ptr)(struct mib_call *, struct mib_node *, void *, + size_t); + +struct mib_node { + uint32_t node_flags; /* CTLTYPE_ type and CTLFLAGS_ flags */ + size_t node_size; /* size of associated data (bytes) */ + uint32_t node_ver; /* node version */ + union ixfer_node_val_u { + struct { + uint32_t nvuc_csize; /* number of child slots */ + uint32_t nvuc_clen; /* number of actual children */ + } nvu_child; + int nvu_int; /* immediate integer */ + bool nvu_bool; /* immediate boolean */ + u_quad_t nvu_quad; /* immediate quad */ + } node_val_u; + union pxfer_node_ptr_u { + void *npu_data; /* struct or string data pointer */ + struct mib_node *npu_scptr; /* static child node array */ + } node_ptr_u; + union pxfer_node_aux_u { + struct mib_dynode *nau_dcptr; /* dynamic child node list */ + mib_func_ptr nau_func; /* handler function */ + mib_verify_ptr nau_verify; /* verification function */ + } node_aux_u; + const char *node_name; /* node name string */ + const char *node_desc; /* node description (may be NULL) */ +}; +#define node_csize node_val_u.nvu_child.nvuc_csize +#define node_clen node_val_u.nvu_child.nvuc_clen +#define node_int node_val_u.nvu_int +#define node_bool node_val_u.nvu_bool +#define node_quad node_val_u.nvu_quad +#define node_data node_ptr_u.npu_data +#define node_scptr node_ptr_u.npu_scptr +#define node_dcptr node_aux_u.nau_dcptr +#define node_func node_aux_u.nau_func +#define node_verify node_aux_u.nau_verify + +/* + * This structure is used for dynamically allocated nodes, that is, nodes + * created by userland at run time. It contains not only the fields below, but + * also the full name and, for leaf nodes with non-immediate data, the actual + * data area. + */ +struct mib_dynode { + struct mib_dynode *dynode_next; /* next in linked dynamic node list */ + int dynode_id; /* identifier of this node */ + struct mib_node dynode_node; /* actual node */ + char dynode_name[1]; /* node name data (variable size) */ +}; + +/* Static node initialization macros. */ +#define MIB_NODE(f,t,n,d) { \ + .node_flags = CTLTYPE_NODE | CTLFLAG_PARENT | f, \ + .node_size = __arraycount(t), \ + .node_scptr = t, \ + .node_name = n, \ + .node_desc = d \ +} +#define MIB_ENODE(f,n,d) { /* "E"mpty or "E"xternal */ \ + .node_flags = CTLTYPE_NODE | CTLFLAG_PARENT | f, \ + .node_name = n, \ + .node_desc = d \ +} +#define MIB_INT(f,i,n,d) { \ + .node_flags = CTLTYPE_INT | CTLFLAG_IMMEDIATE | f, \ + .node_size = sizeof(int), \ + .node_int = i, \ + .node_name = n, \ + .node_desc = d \ +} +#define MIB_BOOL(f,b,n,d) { \ + .node_flags = CTLTYPE_BOOL | CTLFLAG_IMMEDIATE | f, \ + .node_size = sizeof(bool), \ + .node_bool = b, \ + .node_name = n, \ + .node_desc = d \ +} +#define MIB_QUAD(f,q,n,d) { \ + .node_flags = CTLTYPE_QUAD | CTLFLAG_IMMEDIATE | f, \ + .node_size = sizeof(u_quad_t), \ + .node_quad = q, \ + .node_name = n, \ + .node_desc = d \ +} +#define MIB_DATA(f,s,n,d) { \ + .node_flags = f, \ + .node_size = sizeof(s), \ + .node_data = __UNCONST(s), \ + .node_name = n, \ + .node_desc = d \ +} +#define MIB_STRING(f,p,n,d) MIB_DATA(CTLTYPE_STRING | f, p, n, d) +#define MIB_STRUCT(f,p,n,d) MIB_DATA(CTLTYPE_STRUCT | f, p, n, d) +#define MIB_INTPTR(f,p,n,d) MIB_DATA(CTLTYPE_INT | f, p, n, d) +#define MIB_FUNC(f,s,fp,n,d) { \ + .node_flags = f, \ + .node_size = s, \ + .node_func = fp, \ + .node_name = n, \ + .node_desc = d \ +} +#define MIB_INTV(f,i,vp,n,d) { \ + .node_flags = CTLTYPE_INT | CTLFLAG_IMMEDIATE | \ + CTLFLAG_VERIFY | f, \ + .node_size = sizeof(int), \ + .node_int = i, \ + .node_verify = vp, \ + .node_name = n, \ + .node_desc = d \ +} + +/* Finalize a node initialized with MIB_ENODE. */ +#define MIB_INIT_ENODE(n,t) \ +do { \ + (n)->node_size = __arraycount(t); \ + (n)->node_scptr = t; \ +} while (0) + +/* Some convenient shortcuts for highly common flags. */ +#define _RO CTLFLAG_READONLY +#define _RW CTLFLAG_READWRITE +#define _P CTLFLAG_PERMANENT + +/* + * If this check fails, all uses of "struct sysctlnode" and "struct sysctldesc" + * need to be revised, and translation between different versions of those + * structures may have to be added for backward compatibility. + */ +#if SYSCTL_VERSION != SYSCTL_VERS_1 +#error "NetBSD sysctl headers are ahead of our implementation" +#endif + +/* main.c */ +int mib_inrange(struct mib_oldp *, size_t); +size_t mib_getoldlen(struct mib_oldp *); +ssize_t mib_copyout(struct mib_oldp *, size_t, const void * __restrict, + size_t); +void mib_setoldlen(struct mib_call *, size_t); +size_t mib_getnewlen(struct mib_newp *); +int mib_copyin(struct mib_newp * __restrict, void * __restrict, size_t); +int mib_copyin_aux(struct mib_newp * __restrict, vir_bytes, + void * __restrict, size_t); +int mib_authed(struct mib_call *); + +/* tree.c */ +ssize_t mib_readwrite(struct mib_call *, struct mib_node *, struct mib_oldp *, + struct mib_newp *, mib_verify_ptr); +ssize_t mib_dispatch(struct mib_call *, struct mib_node *, struct mib_oldp *, + struct mib_newp *); +void mib_tree_init(struct mib_node *); +extern unsigned int nodes; +extern unsigned int objects; + +/* subtree modules */ +void mib_kern_init(struct mib_node *); +void mib_minix_init(struct mib_node *); + +#endif /* !_MINIX_MIB_MIB_H */ diff --git a/minix/servers/mib/minix.c b/minix/servers/mib/minix.c new file mode 100644 index 000000000..dbcbdcdcf --- /dev/null +++ b/minix/servers/mib/minix.c @@ -0,0 +1,73 @@ +/* MIB service - minix.c - implementation of the CTL_MINIX subtree */ + +#include "mib.h" + +#if MINIX_TEST_SUBTREE + +static char test_string[16], test_struct[12]; + +static struct mib_node mib_minix_test_secret_table[] = { +/* 0*/ [SECRET_VALUE] = MIB_INT(_RO, 12345, "value", + "The combination to my luggage"), +}; + +/* + * Note that even the descriptions here have been chosen such that returned + * description array alignment is tested. Do not change existing fields + * lightly, although adding new fields is always fine. + */ +static struct mib_node mib_minix_test_table[] = { +/* 0*/ [TEST_INT] = MIB_INT(_RO | CTLFLAG_HEX, 0x01020304, "int", + "Value test field"), +/* 1*/ [TEST_BOOL] = MIB_BOOL(_RW, 0, "bool", + "Boolean test field"), +/* 2*/ [TEST_QUAD] = MIB_QUAD(_RW, 0, "quad", "Quad test field"), +/* 3*/ [TEST_STRING] = MIB_STRING(_RW, test_string, "string", + "String test field"), +/* 4*/ [TEST_STRUCT] = MIB_STRUCT(_RW, test_struct, "struct", + "Structure test field"), +/* 5*/ [TEST_PRIVATE] = MIB_INT(_RW | CTLFLAG_PRIVATE, -5375, + "private", "Private test field"), +/* 6*/ [TEST_ANYWRITE] = MIB_INT(_RW | CTLFLAG_ANYWRITE, 0, + "anywrite", "AnyWrite test field"), +/* 7*/ [TEST_DYNAMIC] = MIB_INT(_RO, 0, "deleteme", + "This node will be destroyed"), +/* 8*/ [TEST_SECRET] = MIB_NODE(_RO | CTLFLAG_PRIVATE, + mib_minix_test_secret_table, "secret", + "Private subtree"), +/* 9*/ [TEST_PERM] = MIB_INT(_P | _RO, 1, "permanent", NULL), +/*10*/ [TEST_DESTROY1] = MIB_INT(_RO, 123, "destroy1", NULL), +/*11*/ [TEST_DESTROY2] = MIB_INT(_RO, 456, "destroy2", + "This node will be destroyed"), +}; + +#endif /* MINIX_TEST_SUBTREE */ + +static struct mib_node mib_minix_mib_table[] = { +/* 1*/ [MIB_NODES] = MIB_INTPTR(_P | _RO | CTLFLAG_UNSIGNED, + &nodes, "nodes", + "Number of nodes in the MIB tree"), +/* 2*/ [MIB_OBJECTS] = MIB_INTPTR(_P | _RO | CTLFLAG_UNSIGNED, + &objects, "objects", "Number of " + "dynamically allocated MIB objects"), +}; + +static struct mib_node mib_minix_table[] = { +#if MINIX_TEST_SUBTREE +/* 0*/ [MINIX_TEST] = MIB_NODE(_RW | CTLFLAG_HIDDEN, + mib_minix_test_table, "test", + "Test87 testing ground"), +#endif /* MINIX_TEST_SUBTREE */ +/* 1*/ [MINIX_MIB] = MIB_NODE(_P | _RO, mib_minix_mib_table, + "mib", "MIB service information"), +}; + +/* + * Initialize the CTL_MINIX subtree. + */ +void +mib_minix_init(struct mib_node * node) +{ + + MIB_INIT_ENODE(node, mib_minix_table); +} diff --git a/minix/servers/mib/tree.c b/minix/servers/mib/tree.c new file mode 100644 index 000000000..2b583a626 --- /dev/null +++ b/minix/servers/mib/tree.c @@ -0,0 +1,1418 @@ +/* MIB service - tree.c - tree access and management */ + +#include "mib.h" + +/* + * Does the given identifier fall within the range of static identifiers in the + * given parent? This check can be used to enumerate all static array entries + * in the given parent, starting from zero. The check does not guarantee that + * the entry is actually for a valid node, nor does it guarantee that there is + * not a dynamic node with this identifier. + */ +#define IS_STATIC_ID(parent, id) ((unsigned int)(id) < (parent)->node_size) + +/* + * Scratch buffer, used for various cases of temporary data storage. It must + * be large enough to fit a sysctldesc structure followed by the longest + * supported description. It must also be large enough to serve as temporary + * storage for data being written in the majority of cases. Finally, it must + * be large enough to contain an entire page, for mib_copyin_str(). + */ +#define MAXDESCLEN 1024 /* from NetBSD */ +#define SCRATCH_SIZE MAX(PAGE_SIZE, sizeof(struct sysctldesc) + MAXDESCLEN) +static char scratch[SCRATCH_SIZE] __aligned(sizeof(int32_t)); + +unsigned int nodes; /* how many nodes are there in the tree? */ +unsigned int objects; /* how many allocated memory objects are there? */ + +/* + * Find a node through its parent node and identifier. Return the node if it + * was found, and optionally store a pointer to the pointer to its dynode + * superstructure (for removal). If no matching node was found, return NULL. + */ +static struct mib_node * +mib_find(struct mib_node * parent, int id, struct mib_dynode *** prevpp) +{ + struct mib_node *node; + struct mib_dynode **dynp; + + if (id < 0) + return NULL; + + /* + * Is there a static node with this identifier? The static nodes are + * all in a single array, so lookup is O(1) for these nodes. We use + * the node flags field to see whether the array entry is valid. + */ + if (IS_STATIC_ID(parent, id)) { + node = &parent->node_scptr[id]; + + if (node->node_flags != 0) { + /* Found a matching static node. */ + if (prevpp != NULL) + *prevpp = NULL; + return node; + } + } + + /* + * Is there a dynamic node with this identifier? The dynamic nodes + * form a linked list. This is predominantly because userland may pick + * the identifier number at creation time, so we cannot rely on all + * dynamically created nodes falling into a small identifier range. + * That eliminates the option of a dynamic array indexed by identifier, + * and a linked list is the simplest next option. Thus, dynamic node + * lookup is O(n). However, since the list is sorted by identifier, + * we may be able to stop the search early. + */ + for (dynp = &parent->node_dcptr; *dynp != NULL; + dynp = &((*dynp)->dynode_next)) { + if ((*dynp)->dynode_id == id) { + /* Found a matching dynamic node. */ + if (prevpp != NULL) + *prevpp = dynp; + return &(*dynp)->dynode_node; + } else if ((*dynp)->dynode_id > id) + break; /* no need to look further */ + } + + return NULL; +} + +/* + * Copy out a node to userland, using the exchange format for nodes (namely, + * a sysctlnode structure). Return the size of the object that is (or, if the + * node falls outside the requested data range, would be) copied out on + * success, or a negative error code on failure. The function may return 0 + * to indicate that nothing was copied out after all (this is unused here). + */ +static ssize_t +mib_copyout_node(struct mib_call * call, struct mib_oldp * oldp, size_t off, + int id, const struct mib_node * node) +{ + struct sysctlnode scn; + int visible; + + if (!mib_inrange(oldp, off)) + return sizeof(scn); /* nothing to do */ + + memset(&scn, 0, sizeof(scn)); + + /* + * We use CTLFLAG_PARENT and CTLFLAG_VERIFY internally only. NetBSD + * uses the values of these flags for different purposes. Either way, + * do not expose them to userland. + */ + scn.sysctl_flags = SYSCTL_VERSION | + (node->node_flags & ~(CTLFLAG_PARENT | CTLFLAG_VERIFY)); + scn.sysctl_num = id; + strlcpy(scn.sysctl_name, node->node_name, sizeof(scn.sysctl_name)); + scn.sysctl_ver = node->node_ver; + scn.sysctl_size = node->node_size; + + /* Some information is only visible if the user can access the node. */ + visible = (!(node->node_flags & CTLFLAG_PRIVATE) || mib_authed(call)); + + /* + * For immediate types, store the immediate value in the resulting + * structure, unless the caller is not authorized to obtain the value. + */ + if ((node->node_flags & CTLFLAG_IMMEDIATE) && visible) { + switch (SYSCTL_TYPE(node->node_flags)) { + case CTLTYPE_BOOL: + scn.sysctl_bdata = node->node_bool; + break; + case CTLTYPE_INT: + scn.sysctl_idata = node->node_int; + break; + case CTLTYPE_QUAD: + scn.sysctl_qdata = node->node_quad; + } + } + + /* Special rules apply to parent nodes. */ + if (SYSCTL_TYPE(node->node_flags) == CTLTYPE_NODE) { + /* Report the node size the way NetBSD does, just in case. */ + scn.sysctl_size = sizeof(scn); + + /* If this is a real parent node, report child information. */ + if ((node->node_flags & CTLFLAG_PARENT) && visible) { + scn.sysctl_csize = node->node_csize; + scn.sysctl_clen = node->node_clen; + } + + /* + * If this is a function-driven node, indicate this by setting + * a nonzero function address. This allows trace(1) to + * determine that it should not attempt to descend into this + * part of the tree as usual, because a) accessing subnodes may + * have side effects, and b) meta-identifiers may not work as + * expected in these parts of the tree. Do not return the real + * function pointer, as this would leak anti-ASR information. + */ + if (!(node->node_flags & CTLFLAG_PARENT)) + scn.sysctl_func = SYSCTL_NODE_FN; + } + + /* Copy out the resulting node. */ + return mib_copyout(oldp, off, &scn, sizeof(scn)); +} + +/* + * Given a query on a non-leaf (parent) node, provide the user with an array of + * this node's children. + */ +static ssize_t +mib_query(struct mib_call * call, struct mib_node * parent, + struct mib_oldp * oldp, struct mib_newp * newp, struct mib_node * root) +{ + struct sysctlnode scn; + struct mib_node *node; + struct mib_dynode *dynode; + size_t off; + int r, id; + + /* If the user passed in version numbers, check them. */ + if (newp != NULL) { + if ((r = mib_copyin(newp, &scn, sizeof(scn))) != OK) + return r; + + if (SYSCTL_VERS(scn.sysctl_flags) != SYSCTL_VERSION) + return EINVAL; + + /* + * If a node version number is given, it must match the version + * of the parent or the root. + */ + if (scn.sysctl_ver != 0 && scn.sysctl_ver != root->node_ver && + scn.sysctl_ver != parent->node_ver) + return EINVAL; + } + + /* + * We need not return the nodes strictly in ascending order of + * identifiers, as this is not expected by userland. For example, + * sysctlgetmibinfo(3) performs its own sorting after a query. + * Thus, we can go through the static and dynamic nodes separately. + */ + off = 0; + + /* First enumerate the static nodes. */ + for (id = 0; IS_STATIC_ID(parent, id); id++) { + node = &parent->node_scptr[id]; + + if (node->node_flags == 0) + continue; + + if ((r = mib_copyout_node(call, oldp, off, id, node)) < 0) + return r; + off += r; + } + + /* Then enumerate the dynamic nodes. */ + for (dynode = parent->node_dcptr; dynode != NULL; + dynode = dynode->dynode_next) { + node = &dynode->dynode_node; + + if ((r = mib_copyout_node(call, oldp, off, dynode->dynode_id, + node)) < 0) + return r; + off += r; + } + + return off; +} + +/* + * Scan a parent node's children, as part of new node creation. Search for + * either a free node identifier (if given_id < 0) or collisions with the node + * identifier to use (if given_id >= 0). Also check for name collisions. Upon + * success, return OK, with the resulting node identifier stored in 'idp' and a + * pointer to the pointer for the new dynamic node stored in 'prevpp'. Upon + * failure, return an error code. If the failure is EEXIST, 'idp' will contain + * the ID of the conflicting node, and 'nodep' will point to the node. + */ +static int +mib_scan(struct mib_node * parent, int given_id, const char * name, int * idp, + struct mib_dynode *** prevpp, struct mib_node ** nodep) +{ + struct mib_dynode **prevp, **dynp; + struct mib_node *node; + int id; + + /* + * We must verify that no entry already exists with the given name. In + * addition, if a nonnegative identifier is given, we should use that + * identifier and make sure it does not already exist. Otherwise, we + * must find a free identifier. Finally, we sort the dynamic nodes in + * ascending identifier order, so we must find the right place at which + * to insert the new node. + * + * For finding a free identifier, choose an ID that falls (well) + * outside the static range, both to avoid accidental retrieval by an + * application that uses a static ID, and to simplify verifying that + * the ID is indeed free. The sorting of dynamic nodes by identifier + * ensures that searching for a free identifier is O(n). + * + * At this time, we do not support some NetBSD features. We do not + * force success if the new node is sufficiently like an existing one. + * Also, we do not use global autoincrement for dynamic identifiers, + * although that could easily be changed. + */ + + /* First check the static node array, just for collisions. */ + for (id = 0; IS_STATIC_ID(parent, id); id++) { + node = &parent->node_scptr[id]; + if (node->node_flags == 0) + continue; + if (id == given_id || !strcmp(name, node->node_name)) { + *idp = id; + *nodep = node; + return EEXIST; + } + } + + /* + * Then try to find the place to insert a new dynamic node. At the + * same time, check for both identifier and name collisions. + */ + if (given_id >= 0) + id = given_id; + else + id = MAX(CREATE_BASE, parent->node_size); + + for (prevp = &parent->node_dcptr; *prevp != NULL; + prevp = &((*prevp)->dynode_next)) { + if ((*prevp)->dynode_id > id) + break; + if ((*prevp)->dynode_id == id) { + if (given_id >= 0) { + *idp = id; + *nodep = &(*prevp)->dynode_node; + return EEXIST; + } else + id++; + } + if (!strcmp(name, (*prevp)->dynode_node.node_name)) { + *idp = (*prevp)->dynode_id; + *nodep = &(*prevp)->dynode_node; + return EEXIST; + } + } + + /* Finally, check the rest of the dynamic nodes for name collisions. */ + for (dynp = prevp; *dynp != NULL; dynp = &((*dynp)->dynode_next)) { + assert((*dynp)->dynode_id > id); + + if (!strcmp(name, (*dynp)->dynode_node.node_name)) { + *idp = (*dynp)->dynode_id; + *nodep = &(*dynp)->dynode_node; + return EEXIST; + } + } + + *idp = id; + *prevpp = prevp; + return OK; +} + +/* + * Copy in a string from the user process, located at the given remote address, + * into the given local buffer. If no buffer is given, just compute the length + * of the string. On success, return OK. If 'sizep' is not NULL, it will be + * filled with the string size, including the null terminator. If a non-NULL + * buffer was given, the string will be copied into the provided buffer (also + * including null terminator). Return an error code on failure, which includes + * the case that no null terminator was found within the range of bytes that + * would fit in the given buffer. + */ +static int +mib_copyin_str(struct mib_newp * __restrict newp, vir_bytes addr, + char * __restrict buf, size_t bufsize, size_t * __restrict sizep) +{ + char *ptr, *endp; + size_t chunk, len; + int r; + + assert(newp != NULL); + assert(bufsize <= SSIZE_MAX); + + if (addr == 0) + return EINVAL; + + /* + * NetBSD has a kernel routine for copying in a string from userland. + * MINIX3 does not, since its system call interface has always relied + * on userland passing in string lengths. The sysctl(2) API does not + * provide the string length, and thus, we have to do a bit of guess + * work. If we copy too little at once, performance suffers. If we + * copy too much at once, we may trigger an unneeded page fault. Make + * use of page boundaries to strike a balance between those two. If we + * are requested to just get the string length, use the scratch buffer. + */ + len = 0; + + while (bufsize > 0) { + chunk = PAGE_SIZE - (addr % PAGE_SIZE); + if (chunk > bufsize) + chunk = bufsize; + + ptr = (buf != NULL) ? &buf[len] : scratch; + if ((r = mib_copyin_aux(newp, addr, ptr, chunk)) != OK) + return r; + + if ((endp = memchr(ptr, '\0', chunk)) != NULL) { + /* A null terminator was found - success. */ + if (sizep != NULL) + *sizep = len + (size_t)(endp - ptr) + 1; + return OK; + } + + addr += chunk; + len += chunk; + bufsize -= chunk; + } + + /* No null terminator found. */ + return EINVAL; +} + +/* + * Increase the version of the root node, and copy this new version to all + * nodes on the path to a node, as well as (optionally) that node itself. + */ +static void +mib_upgrade(struct mib_node ** stack, int depth, struct mib_node * node) +{ + uint32_t ver; + + /* + * The bottom of the stack is always the root node, which determines + * the version of the entire tree. Do not use version number 0, as a + * zero version number indicates no interest in versions elsewhere. + */ + assert(depth > 0); + + ver = stack[0]->node_ver + 1; + if (ver == 0) + ver = 1; + + /* Copy the new version to all the nodes on the path. */ + while (depth-- > 0) + stack[depth]->node_ver = ver; + + if (node != NULL) + node->node_ver = stack[0]->node_ver; +} + +/* + * Create a node. + */ +static ssize_t +mib_create(struct mib_call * call, struct mib_node * parent, + struct mib_oldp * oldp, struct mib_newp * newp, + struct mib_node ** stack, int depth) +{ + struct mib_dynode *dynode, **prevp; + struct mib_node *node; + struct sysctlnode scn; + size_t namelen, size; + ssize_t len; + bool b; + char c; + int r, id; + + /* This is a privileged operation. */ + if (!mib_authed(call)) + return EPERM; + + /* The parent node must not be marked as read-only. */ + if (!(parent->node_flags & CTLFLAG_READWRITE)) + return EPERM; + + /* + * Has the parent reached its child node limit? This check is entirely + * theoretical as long as we support only 32-bit virtual memory. + */ + if (parent->node_csize == INT_MAX) + return EINVAL; + assert(parent->node_clen <= parent->node_csize); + + /* The caller must supply information on the child node to create. */ + if (newp == NULL) + return EINVAL; + + if ((r = mib_copyin(newp, &scn, sizeof(scn))) != OK) + return r; + + /* + * We perform as many checks as possible before we start allocating + * memory. Then again, after allocation, copying in data may still + * fail. Unlike when setting values, we do not first copy data into a + * temporary buffer here, because we do not need to: if the copy fails, + * the entire create operation fails, so atomicity is not an issue. + */ + if (SYSCTL_VERS(scn.sysctl_flags) != SYSCTL_VERSION) + return EINVAL; + + /* + * If a node version number is given, it must match the version of the + * parent or the root (which is always the bottom of the node stack). + * The given version number is *not* used for the node being created. + */ + assert(depth > 0); + + if (scn.sysctl_ver != 0 && scn.sysctl_ver != stack[0]->node_ver && + scn.sysctl_ver != parent->node_ver) + return EINVAL; + + /* + * Validate the node flags. In addition to the NetBSD-allowed flags, + * we also allow UNSIGNED, and leave its interpretation to userland. + */ + if (SYSCTL_FLAGS(scn.sysctl_flags) & + ~(SYSCTL_USERFLAGS | CTLFLAG_UNSIGNED)) + return EINVAL; + + if (!(scn.sysctl_flags & CTLFLAG_IMMEDIATE)) { + /* + * Without either IMMEDIATE or OWNDATA, data pointers are + * actually kernel addresses--a concept we do not support. + * Otherwise, if IMMEDIATE is not set, we are going to have to + * allocate extra memory for the data, so force OWNDATA to be. + * set. Node-type nodes have no data, though. + */ + if (SYSCTL_TYPE(scn.sysctl_flags) != CTLTYPE_NODE) { + if (!(scn.sysctl_flags & CTLFLAG_OWNDATA) && + scn.sysctl_data != NULL) + return EINVAL; /* not meaningful on MINIX3 */ + + scn.sysctl_flags |= CTLFLAG_OWNDATA; + } + } else if (scn.sysctl_flags & CTLFLAG_OWNDATA) + return EINVAL; + + /* The READWRITE flag consists of multiple bits. Sanitize. */ + if (scn.sysctl_flags & CTLFLAG_READWRITE) + scn.sysctl_flags |= CTLFLAG_READWRITE; + + /* Validate the node type and size, and do some additional checks. */ + switch (SYSCTL_TYPE(scn.sysctl_flags)) { + case CTLTYPE_BOOL: + if (scn.sysctl_size != sizeof(bool)) + return EINVAL; + break; + case CTLTYPE_INT: + if (scn.sysctl_size != sizeof(int)) + return EINVAL; + break; + case CTLTYPE_QUAD: + if (scn.sysctl_size != sizeof(u_quad_t)) + return EINVAL; + break; + case CTLTYPE_STRING: + /* + * For strings, a zero length means that we are supposed to + * allocate a buffer size based on the given string size. + */ + if (scn.sysctl_size == 0 && scn.sysctl_data != NULL) { + if ((r = mib_copyin_str(newp, + (vir_bytes)scn.sysctl_data, NULL, SSIZE_MAX, + &size)) != OK) + return r; + scn.sysctl_size = size; + } + /* FALLTHROUGH */ + case CTLTYPE_STRUCT: + /* + * We do not set an upper size on the data size, since it would + * still be possible to create a large number of nodes, and + * this is a privileged operation ayway. + */ + if (scn.sysctl_size == 0 || scn.sysctl_size > SSIZE_MAX) + return EINVAL; + if (scn.sysctl_flags & CTLFLAG_IMMEDIATE) + return EINVAL; + break; + case CTLTYPE_NODE: + /* + * The zero size is an API requirement, but we also rely on the + * zero value internally, as the node has no static children. + */ + if (scn.sysctl_size != 0) + return EINVAL; + if (scn.sysctl_flags & (CTLFLAG_IMMEDIATE | CTLFLAG_OWNDATA)) + return EINVAL; + if (scn.sysctl_csize != 0 || scn.sysctl_clen != 0 || + scn.sysctl_child != NULL) + return EINVAL; + break; + default: + return EINVAL; + } + + if (scn.sysctl_func != NULL || scn.sysctl_parent != NULL) + return EINVAL; + + /* Names must be nonempty, null terminated, C symbol style strings. */ + for (namelen = 0; namelen < sizeof(scn.sysctl_name); namelen++) { + if ((c = scn.sysctl_name[namelen]) == '\0') + break; + /* A-Z, a-z, 0-9, _ only, and no digit as first character. */ + if (!((c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') || + c == '_' || (c >= '0' && c <= '9' && namelen > 0))) + return EINVAL; + } + if (namelen == 0 || namelen == sizeof(scn.sysctl_name)) + return EINVAL; + + /* + * Find a free identifier, or check for ID collisions if a specific + * identifier was given. At the same time, scan for name collisions, + * and find the location at which to insert the new node in the list. + */ + r = mib_scan(parent, scn.sysctl_num, scn.sysctl_name, &id, &prevp, + &node); + + if (r != OK) { + /* + * On collisions, if requested, copy out the existing node. + * This does not quite fit the general interaction model, as + * the service must now return a nonzero old length from a call + * that actually failed (in contrast to ENOMEM failures). + */ + if (r == EEXIST && oldp != NULL) { + len = mib_copyout_node(call, oldp, 0, id, node); + + if (len > 0) + mib_setoldlen(call, len); + } + + return r; + } + + /* + * All checks so far have passed. "id" now contains the new node + * identifier, and "prevp" points to the pointer at which to insert the + * new node in its parent's linked list of dynamic nodes. + * + * We can now attempt to create and initialize a new dynamic node. + * Allocating nodes this way may cause heavy memory fragmentation over + * time, but we do not expect the tree to see heavy modification at run + * time, and the superuser has easier ways to get the MIB service in + * trouble. We note that even in low-memory conditions, the MIB + * service is always able to provide basic functionality. + */ + size = sizeof(*dynode) + namelen; + if (!(scn.sysctl_flags & CTLFLAG_IMMEDIATE)) + size += scn.sysctl_size; + + if ((dynode = malloc(size)) == NULL) + return EINVAL; /* do not return ENOMEM */ + objects++; + + /* From here on, we have to free "dynode" before returning an error. */ + r = OK; + + memset(dynode, 0, sizeof(*dynode)); /* no need to zero all of "size" */ + dynode->dynode_id = id; + strlcpy(dynode->dynode_name, scn.sysctl_name, namelen + 1); + + node = &dynode->dynode_node; + node->node_flags = scn.sysctl_flags & ~SYSCTL_VERS_MASK; + if (SYSCTL_TYPE(scn.sysctl_flags) == CTLTYPE_NODE) + node->node_flags |= CTLFLAG_PARENT; + node->node_size = scn.sysctl_size; + node->node_name = dynode->dynode_name; + + /* Initialize the node value. */ + if (scn.sysctl_flags & CTLFLAG_IMMEDIATE) { + switch (SYSCTL_TYPE(scn.sysctl_flags)) { + case CTLTYPE_BOOL: + /* Sanitize booleans. See the C99 _Bool comment. */ + memcpy(&c, &scn.sysctl_bdata, sizeof(c)); + node->node_bool = (bool)c; + break; + case CTLTYPE_INT: + node->node_int = scn.sysctl_idata; + break; + case CTLTYPE_QUAD: + node->node_quad = scn.sysctl_qdata; + break; + default: + assert(0); + } + } else if (SYSCTL_TYPE(scn.sysctl_flags) != CTLTYPE_NODE) { + node->node_data = dynode->dynode_name + namelen + 1; + + /* Did the user supply initial data? If not, use zeroes. */ + if (scn.sysctl_data != NULL) { + /* + * For strings, do not copy in more than needed. This + * is just a nice feature which allows initialization + * of large string buffers with short strings. + */ + if (SYSCTL_TYPE(scn.sysctl_flags) == CTLTYPE_STRING) + r = mib_copyin_str(newp, + (vir_bytes)scn.sysctl_data, + node->node_data, scn.sysctl_size, NULL); + else + r = mib_copyin_aux(newp, + (vir_bytes)scn.sysctl_data, + node->node_data, scn.sysctl_size); + } else + memset(node->node_data, 0, scn.sysctl_size); + + /* + * Sanitize booleans. See the C99 _Bool comment elsewhere. + * In this case it is not as big of a deal, as we will not be + * accessing the boolean value directly ourselves. + */ + if (r == OK && SYSCTL_TYPE(scn.sysctl_flags) == CTLTYPE_BOOL) { + b = (bool)*(char *)node->node_data; + memcpy(node->node_data, &b, sizeof(b)); + } + } + + /* + * Even though it would be entirely possible to set a description right + * away as well, this does not seem to be supported on NetBSD at all. + */ + + /* Deal with earlier failures now. */ + if (r != OK) { + free(dynode); + objects--; + + return r; + } + + /* At this point, actual creation can no longer fail. */ + + /* Link the dynamic node into the list, in the right place. */ + assert(prevp != NULL); + dynode->dynode_next = *prevp; + *prevp = dynode; + + /* The parent node now has one more child. */ + parent->node_csize++; + parent->node_clen++; + + nodes++; + + /* + * Bump the version of all nodes on the path to the new node, including + * the node itself. + */ + mib_upgrade(stack, depth, node); + + /* + * Copy out the newly created node as resulting ("old") data. Do not + * undo the creation if this fails, though. + */ + return mib_copyout_node(call, oldp, 0, id, node); +} + +/* + * Destroy a node. + */ +static ssize_t +mib_destroy(struct mib_call * call, struct mib_node * parent, + struct mib_oldp * oldp, struct mib_newp * newp, + struct mib_node ** stack, int depth) +{ + struct mib_dynode *dynode, **prevp; + struct mib_node *node; + struct sysctlnode scn; + ssize_t r; + + /* This is a privileged operation. */ + if (!mib_authed(call)) + return EPERM; + + /* The parent node must not be marked as read-only. */ + if (!(parent->node_flags & CTLFLAG_READWRITE)) + return EPERM; + + /* The caller must specify which child node to destroy. */ + if (newp == NULL) + return EINVAL; + + if ((r = mib_copyin(newp, &scn, sizeof(scn))) != OK) + return r; + + if (SYSCTL_VERS(scn.sysctl_flags) != SYSCTL_VERSION) + return EINVAL; + + /* Locate the child node. */ + if ((node = mib_find(parent, scn.sysctl_num, &prevp)) == NULL) + return ENOENT; + + /* The node must not be marked as permanent. */ + if (node->node_flags & CTLFLAG_PERMANENT) + return EPERM; + + /* For node-type nodes, extra rules apply. */ + if (SYSCTL_TYPE(node->node_flags) == CTLTYPE_NODE) { + /* The node must not have an associated function. */ + if (!(node->node_flags & CTLFLAG_PARENT)) + return EPERM; + + /* The target node must itself not have child nodes. */ + if (node->node_clen != 0) + return ENOTEMPTY; + } + + /* If the user supplied a version, it must match the node version. */ + if (scn.sysctl_ver != 0 && scn.sysctl_ver != node->node_ver) + return EINVAL; /* NetBSD inconsistently throws ENOENT here */ + + /* If the user supplied a name, it must match the node name. */ + if (scn.sysctl_name[0] != '\0') { + if (memchr(scn.sysctl_name, '\0', + sizeof(scn.sysctl_name)) == NULL) + return EINVAL; + if (strcmp(scn.sysctl_name, node->node_name)) + return EINVAL; /* also ENOENT on NetBSD */ + } + + /* + * Copy out the old node if requested, otherwise return the length + * anyway. The node will be destroyed even if this call fails, + * because that is how NetBSD behaves. + */ + r = mib_copyout_node(call, oldp, 0, scn.sysctl_num, node); + + /* If the description was allocated, free it. */ + if (node->node_flags & CTLFLAG_OWNDESC) { + free(__UNCONST(node->node_desc)); + objects--; + } + + /* + * Static nodes only use static memory, and dynamic nodes have the data + * area embedded in the dynode object. In neither case is data memory + * allocated separately, and thus, it need never be freed separately. + * Therefore we *must not* check CTLFLAG_OWNDATA here. + */ + + assert(parent->node_csize > 0); + assert(parent->node_clen > 0); + + /* + * Dynamic nodes must be freed. Freeing the dynode object also frees + * the node name and any associated data. Static nodes are zeroed out, + * and the static memory they referenced will become inaccessible. + */ + if (prevp != NULL) { + dynode = *prevp; + *prevp = dynode->dynode_next; + + free(dynode); + objects--; + + parent->node_csize--; + } else + memset(node, 0, sizeof(*node)); + + parent->node_clen--; + + nodes--; + + /* Bump the version of all nodes on the path to the destroyed node. */ + mib_upgrade(stack, depth, NULL); + + return r; +} + +/* + * Copy out a node description to userland, using the exchange format for node + * descriptions (namely, a sysctldesc structure). Return the size of the + * object that is (or, if the description falls outside the requested data + * range, would be) copied out on success, or a negative error code on failure. + * The function may return 0 to indicate that nothing was copied out after all. + */ +static ssize_t +mib_copyout_desc(struct mib_call * call, struct mib_oldp * oldp, size_t off, + int id, const struct mib_node * node) +{ + struct sysctldesc *scd; + size_t size; + int r; + + /* Descriptions of private nodes are considered private too. */ + if ((node->node_flags & CTLFLAG_PRIVATE) && !mib_authed(call)) + return 0; + + /* The description length includes the null terminator. */ + if (node->node_desc != NULL) + size = strlen(node->node_desc) + 1; + else + size = 1; + + assert(sizeof(*scd) + size <= sizeof(scratch)); + + scd = (struct sysctldesc *)scratch; + memset(scd, 0, sizeof(*scd)); + scd->descr_num = id; + scd->descr_ver = node->node_ver; + scd->descr_len = size; + if (node->node_desc != NULL) + strlcpy(scd->descr_str, node->node_desc, + sizeof(scratch) - sizeof(*scd)); + else + scd->descr_str[0] = '\0'; + + size += offsetof(struct sysctldesc, descr_str); + + if ((r = mib_copyout(oldp, off, scratch, size)) < 0) + return r; + + /* + * By aligning just the size, we may leave garbage between the entries + * copied out, which is fine because it is userland's own data. + */ + return roundup2(size, sizeof(int32_t)); +} + +/* + * Retrieve node descriptions in bulk, or retrieve or assign a particular + * node's description. + */ +static ssize_t +mib_describe(struct mib_call * call, struct mib_node * parent, + struct mib_oldp * oldp, struct mib_newp * newp) +{ + struct sysctlnode scn; + struct mib_node *node; + struct mib_dynode *dynode; + size_t off; + int r, id; + + /* If new data are given, they identify a particular target node. */ + if (newp != NULL) { + if ((r = mib_copyin(newp, &scn, sizeof(scn))) != OK) + return r; + + if (SYSCTL_VERS(scn.sysctl_flags) != SYSCTL_VERSION) + return EINVAL; + + /* Locate the child node. */ + if ((node = mib_find(parent, scn.sysctl_num, NULL)) == NULL) + return ENOENT; + + /* Descriptions of private nodes are considered private too. */ + if ((node->node_flags & CTLFLAG_PRIVATE) && !mib_authed(call)) + return EPERM; + + /* + * If a description pointer was given, this is a request to + * set the node's description. + */ + if (scn.sysctl_desc != NULL) { + /* Such a request requires superuser privileges. */ + if (!mib_authed(call)) + return EPERM; + + /* The node must not already have a description. */ + if (node->node_desc != NULL) + return EPERM; + + /* The node must not be marked as permanent. */ + if (node->node_flags & CTLFLAG_PERMANENT) + return EPERM; + + /* + * If the user supplied a version, it must match. + * NetBSD performs this check only when setting + * descriptions, and thus, so do we.. + */ + if (scn.sysctl_ver != 0 && + scn.sysctl_ver != node->node_ver) + return EINVAL; + + /* + * Copy in the description to the scratch buffer. + * The length of the description must be reasonable. + */ + if ((r = mib_copyin_str(newp, + (vir_bytes)scn.sysctl_desc, scratch, MAXDESCLEN, + NULL)) != OK) + return r; + + /* Allocate memory and store the description. */ + if ((node->node_desc = strdup(scratch)) == NULL) { + printf("MIB: out of memory!\n"); + + return EINVAL; /* do not return ENOMEM */ + } + objects++; + + /* The description must now be freed with the node. */ + node->node_flags |= CTLFLAG_OWNDESC; + } + + /* + * Either way, copy out the requested node's description, which + * should indeed be the new description if one was just set. + * Note that we have already performed the permission check + * that could make this call return zero, so here it will not. + */ + return mib_copyout_desc(call, oldp, 0, scn.sysctl_num, node); + } + + /* See also the considerations laid out in mib_query(). */ + off = 0; + + /* First describe the static nodes. */ + for (id = 0; IS_STATIC_ID(parent, id); id++) { + node = &parent->node_scptr[id]; + + if (node->node_flags == 0) + continue; + + if ((r = mib_copyout_desc(call, oldp, off, id, node)) < 0) + return r; + off += r; + } + + /* Then describe the dynamic nodes. */ + for (dynode = parent->node_dcptr; dynode != NULL; + dynode = dynode->dynode_next) { + node = &dynode->dynode_node; + + if ((r = mib_copyout_desc(call, oldp, off, dynode->dynode_id, + node)) < 0) + return r; + off += r; + } + + return off; +} + +/* + * Return a pointer to the data associated with the given node, or NULL if the + * node has no associated data. Actual calls to this function should never + * result in NULL - as long as the proper rules are followed elsewhere. + */ +static void * +mib_getptr(struct mib_node * node) +{ + + switch (SYSCTL_TYPE(node->node_flags)) { + case CTLTYPE_BOOL: + if (node->node_flags & CTLFLAG_IMMEDIATE) + return &node->node_bool; + break; + case CTLTYPE_INT: + if (node->node_flags & CTLFLAG_IMMEDIATE) + return &node->node_int; + break; + case CTLTYPE_QUAD: + if (node->node_flags & CTLFLAG_IMMEDIATE) + return &node->node_quad; + break; + case CTLTYPE_STRING: + case CTLTYPE_STRUCT: + if (node->node_flags & CTLFLAG_IMMEDIATE) + return NULL; + break; + default: + return NULL; + } + + return node->node_data; +} + +/* + * Read current (old) data from a regular data node, if requested. Return the + * old data length. + */ +static ssize_t +mib_read(struct mib_node * node, struct mib_oldp * oldp) +{ + void *ptr; + size_t oldlen; + int r; + + if ((ptr = mib_getptr(node)) == NULL) + return EINVAL; + + if (SYSCTL_TYPE(node->node_flags) == CTLTYPE_STRING) + oldlen = strlen(node->node_data) + 1; + else + oldlen = node->node_size; + + if (oldlen > SSIZE_MAX) + return EINVAL; + + /* Copy out the current data, if requested at all. */ + if (oldp != NULL && (r = mib_copyout(oldp, 0, ptr, oldlen)) < 0) + return r; + + /* Return the current length in any case. */ + return (ssize_t)oldlen; +} + +/* + * Write new data into a regular data node, if requested. + */ +static int +mib_write(struct mib_call * call, struct mib_node * node, + struct mib_newp * newp, mib_verify_ptr verify) +{ + bool b[(sizeof(bool) == sizeof(char)) ? 1 : -1]; /* explained below */ + char *src, *dst; + size_t newlen; + int r; + + if (newp == NULL) + return OK; /* nothing to do */ + + /* + * When setting a new value, we cannot risk doing an in-place update: + * the copy from userland may fail halfway through, in which case an + * in-place update could leave the node value in a corrupted state. + * Thus, we must first fetch any new data into a temporary buffer. + * + * Given that we use intermediate data storage, we could support value + * swapping, where the user provides the same buffer for new and old + * data. We choose not to: NetBSD does not support it, it would make + * trace(1)'s job a lot harder, and it would convolute the code here. + */ + newlen = mib_getnewlen(newp); + + if ((dst = mib_getptr(node)) == NULL) + return EINVAL; + + switch (SYSCTL_TYPE(node->node_flags)) { + case CTLTYPE_STRING: + /* + * Strings must not exceed their buffer size. There is a + * second check further below, because we allow userland to + * give us an unterminated string. In that case we terminate + * it ourselves, but then the null terminator must fit as well. + */ + if (newlen > node->node_size) + return EINVAL; + break; + case CTLTYPE_BOOL: + case CTLTYPE_INT: + case CTLTYPE_QUAD: + case CTLTYPE_STRUCT: + /* Non-string types must have an exact size match. */ + if (newlen != node->node_size) + return EINVAL; + break; + default: + return EINVAL; + } + + /* + * If we cannot fit the data in the small stack buffer, then allocate a + * temporary buffer. We add one extra byte so that we can add a null + * terminator at the end of strings in case userland did not supply + * one. Either way, we must free the temporary buffer later! + * + * The alternative is to ensure that the given memory is accessible + * before starting the copy, but that would break if we ever add kernel + * threads or anything that allows asynchronous memory unmapping, etc. + */ + if (newlen + 1 > sizeof(scratch)) { + /* + * In practice, the temporary buffer is at least an entire + * memory page, which is reasonable by any standard. As a + * result, we can get away with refusing to perform dynamic + * allocation for unprivileged users. This limits the impact + * that unprivileged users can have on our memory space. + */ + if (!mib_authed(call)) + return EPERM; + + /* + * Do not return ENOMEM on allocation failure, because ENOMEM + * implies that a valid old length was returned. + */ + if ((src = malloc(newlen + 1)) == NULL) { + printf("MIB: out of memory!\n"); + + return EINVAL; + } + objects++; + } else + src = scratch; + + /* Copy in the data. Note that newlen may be zero. */ + r = mib_copyin(newp, src, newlen); + + if (r == OK && verify != NULL && !verify(call, node, src, newlen)) + r = EINVAL; + + if (r == OK) { + /* Check and, if acceptable, store the new value. */ + switch (SYSCTL_TYPE(node->node_flags)) { + case CTLTYPE_BOOL: + /* + * Due to the nature of the C99 _Bool type, we can not + * test directly whether the given boolean value is a + * value that is not "true" and not "false". In the + * worst case, another value could invoke undefined + * behavior. We try our best to sanitize the value + * without looking at it directly, which unfortunately + * requires us to test for the size of the bool type. + * We do that at compile time, hence the 'b' "array". + * Any size other than one byte is an ABI violation. + */ + b[0] = (bool)src[0]; + memcpy(dst, &b[0], sizeof(b[0])); + break; + case CTLTYPE_INT: + case CTLTYPE_QUAD: + case CTLTYPE_STRUCT: + memcpy(dst, src, node->node_size); + break; + case CTLTYPE_STRING: + if (newlen == node->node_size && + src[newlen - 1] != '\0') { + /* Our null terminator does not fit! */ + r = EINVAL; + break; + } + /* + * We do not mind null characters in the middle. In + * general, the buffer may contain garbage after the + * first null terminator, but such garbage will never + * end up being copied out. + */ + src[newlen] = '\0'; + strlcpy(dst, src, node->node_size); + break; + default: + r = EINVAL; + } + } + + if (src != scratch) { + free(src); + objects--; + } + + return r; +} + +/* + * Read and/or write the value of a regular data node. A regular data node is + * a leaf node. Typically, a leaf node has no associated function, in which + * case this function will be used instead. In addition, this function may be + * used from handler functions as part of their functionality. + */ +ssize_t +mib_readwrite(struct mib_call * call, struct mib_node * node, + struct mib_oldp * oldp, struct mib_newp * newp, mib_verify_ptr verify) +{ + ssize_t len; + int r; + + /* Copy out old data, if requested. Always get the old data length. */ + if ((r = len = mib_read(node, oldp)) < 0) + return r; + + /* Copy in new data, if requested. */ + if ((r = mib_write(call, node, newp, verify)) != OK) + return r; + + /* Return the old data length. */ + return len; +} + +/* + * Dispatch a sysctl call, by looking up the target node by its MIB name and + * taking the appropriate action on the resulting node, if found. Return the + * old data length on success, or a negative error code on failure. + */ +ssize_t +mib_dispatch(struct mib_call * call, struct mib_node * root, + struct mib_oldp * oldp, struct mib_newp * newp) +{ + struct mib_node *stack[CTL_MAXNAME]; + struct mib_node *parent, *node; + int id, depth, is_leaf, has_verify, has_func; + + assert(call->call_namelen <= CTL_MAXNAME); + + /* + * Resolve the name by descending into the node tree, level by level, + * starting at the MIB root. + */ + depth = 0; + + for (parent = root; call->call_namelen > 0; parent = node) { + /* + * For node creation and destruction, build a node stack, to + * allow for up-propagation of new node version numbers. + */ + stack[depth++] = parent; + + id = call->call_name[0]; + call->call_name++; + call->call_namelen--; + + assert(SYSCTL_TYPE(parent->node_flags) == CTLTYPE_NODE); + assert(parent->node_flags & CTLFLAG_PARENT); + + /* + * Check for meta-identifiers. Regular identifiers are never + * negative, although node handler functions may take subpaths + * with negative identifiers that are not meta-identifiers + * (e.g., see KERN_PROC2). + */ + if (id < 0) { + /* + * A meta-identifier must always be the last name + * component. + */ + if (call->call_namelen > 0) + return EINVAL; + + switch (id) { + case CTL_QUERY: + return mib_query(call, parent, oldp, newp, + root); + case CTL_CREATE: + return mib_create(call, parent, oldp, newp, + stack, depth); + case CTL_DESTROY: + return mib_destroy(call, parent, oldp, newp, + stack, depth); + case CTL_DESCRIBE: + return mib_describe(call, parent, oldp, newp); + case CTL_CREATESYM: + case CTL_MMAP: + default: + return EOPNOTSUPP; + } + } + + /* Locate the child node. */ + if ((node = mib_find(parent, id, NULL /*prevp*/)) == NULL) + return ENOENT; + + /* Check if access is permitted at this level. */ + if ((node->node_flags & CTLFLAG_PRIVATE) && !mib_authed(call)) + return EPERM; + + /* + * Is this a leaf node, and/or is this node handled by a + * function? If either is true, resolution ends at this level. + * In order to save a few bytes of memory per node, we use + * different ways to determine whether there is a function + * depending on whether the node is a leaf or not. + */ + is_leaf = (SYSCTL_TYPE(node->node_flags) != CTLTYPE_NODE); + if (is_leaf) { + has_verify = (node->node_flags & CTLFLAG_VERIFY); + has_func = (!has_verify && node->node_func != NULL); + } else { + has_verify = FALSE; + has_func = !(node->node_flags & CTLFLAG_PARENT); + } + + /* + * The name may be longer only if the node is not a leaf. That + * also applies to leaves with functions, so check this first. + */ + if (is_leaf && call->call_namelen > 0) + return ENOTDIR; + + /* + * If resolution indeed ends here, and the user supplied new + * data, check if writing is allowed. For functions, it is + * arguable whether we should do this check here already. + * However, for now, this approach covers all our use cases. + */ + if ((is_leaf || has_func) && newp != NULL) { + if (!(node->node_flags & CTLFLAG_READWRITE)) + return EPERM; + + /* + * Unless nonprivileged users may write to this node, + * ensure that the user has superuser privileges. The + * ANYWRITE flag does not override the READWRITE flag. + */ + if (!(node->node_flags & CTLFLAG_ANYWRITE) && + !mib_authed(call)) + return EPERM; + } + + /* If this node has a handler function, let it do the work. */ + if (has_func) + return node->node_func(call, node, oldp, newp); + + /* For regular data leaf nodes, handle generic access. */ + if (is_leaf) + return mib_readwrite(call, node, oldp, newp, + has_verify ? node->node_verify : NULL); + + /* No function and not a leaf? Descend further. */ + } + + /* If we get here, the name refers to a node array. */ + return EISDIR; +} + +/* + * Recursively initialize the static tree at initialization time. + */ +static void +mib_tree_recurse(struct mib_node * parent) +{ + struct mib_node *node; + int id; + + assert(SYSCTL_TYPE(parent->node_flags) == CTLTYPE_NODE); + assert(parent->node_flags & CTLFLAG_PARENT); + + /* + * Later on, node_csize and node_clen will also include dynamically + * created nodes. This means that we cannot use node_csize to iterate + * over the static nodes. + */ + parent->node_csize = parent->node_size; + + node = parent->node_scptr; + + for (id = 0; IS_STATIC_ID(parent, id); id++, node++) { + if (node->node_flags == 0) + continue; + + nodes++; + + parent->node_clen++; + + node->node_ver = parent->node_ver; + + /* Recursively apply this function to all node children. */ + if (SYSCTL_TYPE(node->node_flags) == CTLTYPE_NODE && + (node->node_flags & CTLFLAG_PARENT)) + mib_tree_recurse(node); + } +} + +/* + * Go through the entire static tree, recursively, initializing some values + * that could not be assigned at compile time. + */ +void +mib_tree_init(struct mib_node * root) +{ + + /* Initialize some variables. */ + nodes = 1; /* the root node itself */ + objects = 0; + + /* The entire tree starts with the same, nonzero node version. */ + root->node_ver = 1; + + /* Recursively initialize the static tree. */ + mib_tree_recurse(root); +} diff --git a/minix/servers/rs/table.c b/minix/servers/rs/table.c index 52cd40490..f2d177fea 100644 --- a/minix/servers/rs/table.c +++ b/minix/servers/rs/table.c @@ -22,8 +22,9 @@ struct boot_image_priv boot_image_priv_table[] = { {DS_PROC_NR, "ds", SRV_F }, {TTY_PROC_NR, "tty", SRV_F }, {MEM_PROC_NR, "memory", SRV_F }, -{MFS_PROC_NR,"fs_imgrd", SRV_F }, +{MIB_PROC_NR, "mib", SRV_F }, {PFS_PROC_NR, "pfs", SRV_F }, +{MFS_PROC_NR,"fs_imgrd", SRV_F }, {INIT_PROC_NR, "init", USR_F }, {NULL_BOOT_NR, "", 0, } /* null entry */ }; @@ -37,7 +38,6 @@ struct boot_image_sys boot_image_sys_table[] = { { SCHED_PROC_NR, SRVR_SF }, { VFS_PROC_NR, SRVR_SF }, { MFS_PROC_NR, 0 }, - { PFS_PROC_NR, SRV_SF }, { DEFAULT_BOOT_NR, SRV_SF } /* default entry */ }; diff --git a/minix/tests/Makefile b/minix/tests/Makefile index 2543b0b6f..4e6e5363f 100644 --- a/minix/tests/Makefile +++ b/minix/tests/Makefile @@ -59,7 +59,7 @@ MINIX_TESTS= \ 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 \ 41 42 43 44 45 46 48 49 50 52 53 54 55 56 58 59 60 \ 61 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 \ -81 82 83 84 85 86 +81 82 83 84 85 86 87 FILES += t84_h_nonexec.sh diff --git a/minix/tests/run b/minix/tests/run index 8beeaa4de..37e854146 100755 --- a/minix/tests/run +++ b/minix/tests/run @@ -22,7 +22,7 @@ export USENETWORK # set to "yes" for test48+82 to use the network # Programs that require setuid setuids="test11 test33 test43 test44 test46 test56 test60 test61 test65 \ - test69 test73 test74 test78 test83 test85" + test69 test73 test74 test78 test83 test85 test87" # Scripts that require to be run as root rootscripts="testisofs testvnd testrelpol" @@ -30,7 +30,7 @@ alltests="1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 \ 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 \ 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 \ 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 \ - 81 82 83 84 85 86 sh1 sh2 interp mfs isofs vnd" + 81 82 83 84 85 86 87 sh1 sh2 interp mfs isofs vnd" tests_no=`expr 0` # If root, make sure the setuid tests have the correct permissions diff --git a/minix/tests/test87.c b/minix/tests/test87.c new file mode 100644 index 000000000..3c68fa40c --- /dev/null +++ b/minix/tests/test87.c @@ -0,0 +1,3657 @@ +/* Tests for sysctl(2) and the MIB service - by D.C. van Moolenbroek */ +/* This test needs to run as root: many sysctl(2) calls are privileged. */ +#include +#include +#include +#include +#include +#include +#include +#include + +#define ITERATIONS 2 + +#include "common.h" + +#define NONROOT_USER "bin" /* name of any unprivileged user */ + +#define NEXT_VER(n) (((n) + 1 == 0) ? 1 : ((n) + 1)) /* node version + 1 */ + +static void *bad_ptr; /* a pointer to unmapped memory */ +static unsigned int nodes, objects; /* stats for pre/post test check */ + +/* + * Spawn a child process that drops privileges and then executes the given + * procedure. The returned PID value is of the dead, cleaned-up child, and + * should be used only to check whether the child could store its own PID. + */ +static pid_t +test_nonroot(void (* proc)(void)) +{ + struct passwd *pw; + pid_t pid; + int status; + + pid = fork(); + + switch (pid) { + case -1: + e(0); + break; + case 0: + errct = 0; + + if ((pw = getpwnam(NONROOT_USER)) == NULL) e(0); + + /* FIXME: this may rely on a MINIXism. */ + if (setuid(pw->pw_uid) != 0) e(0); + if (seteuid(pw->pw_uid) != 0) e(0); + + proc(); + + exit(errct); + default: + if (wait(&status) != pid) e(0); + if (!WIFEXITED(status)) e(0); + if (WEXITSTATUS(status) != 0) e(0); + } + + return pid; +} + +/* + * Test basic operations from an unprivileged process. + */ +static void +sub87a(void) +{ + size_t oldlen; + pid_t pid; + bool b; + int i, mib[4]; + + pid = getpid(); + + mib[0] = CTL_MINIX; + mib[1] = MINIX_TEST; + + /* Regular reads should succeed. */ + mib[2] = TEST_INT; + oldlen = sizeof(i); + if (sysctl(mib, 3, &i, &oldlen, NULL, 0) != 0) e(0); + if (oldlen != sizeof(i)) e(0); + if (i != 0x01020304) e(0); + + mib[2] = TEST_BOOL; + oldlen = sizeof(b); + if (sysctl(mib, 3, &b, &oldlen, NULL, 0) != 0) e(0); + if (oldlen != sizeof(b)) e(0); + if (b != false) e(0); + + /* Regular writes should fail. */ + b = true; + if (sysctl(mib, 3, NULL, NULL, &b, sizeof(b)) != -1) e(0); + if (errno != EPERM) e(0); + + oldlen = sizeof(b); + if (sysctl(mib, 3, &b, &oldlen, NULL, 0) != 0) e(0); + if (oldlen != sizeof(b)) e(0); + if (b != false) e(0); + + /* Privileged reads and writes should fail. */ + mib[2] = TEST_PRIVATE; + if (sysctl(mib, 3, NULL, &oldlen, NULL, 0) != -1) e(0); + if (errno != EPERM) e(0); + + oldlen = sizeof(i); + i = 1; + if (sysctl(mib, 3, &i, &oldlen, NULL, 0) != -1) e(0); + if (errno != EPERM) e(0); + if (i != 1) e(0); + + if (sysctl(mib, 3, NULL, NULL, &i, sizeof(i)) != -1) e(0); + if (errno != EPERM) e(0); + + mib[2] = TEST_SECRET; + mib[3] = SECRET_VALUE; + i = 0; + oldlen = sizeof(i); + if (sysctl(mib, 4, &i, &oldlen, NULL, 0) != -1) e(0); + if (errno != EPERM) e(0); + if (i == 12345) e(0); + + mib[3]++; + i = 0; + oldlen = sizeof(i); + if (sysctl(mib, 4, &i, &oldlen, NULL, 0) != -1) e(0); + if (errno != EPERM) e(0); + + /* Free-for-all writes should succeed. */ + mib[2] = TEST_ANYWRITE; + if (sysctl(mib, 3, NULL, &oldlen, NULL, 0) != 0) e(0); + if (oldlen != sizeof(i)) e(0); + + i = pid; + if (sysctl(mib, 3, NULL, NULL, &i, sizeof(i)) != 0) e(0); + + i = 0; + oldlen = sizeof(i); + if (sysctl(mib, 3, &i, &oldlen, NULL, 0) != 0) e(0); + if (oldlen != sizeof(i)) e(0); + if (i != pid) e(0); +} + +/* + * Test the basic sysctl(2) interface. + */ +static void +test87a(void) +{ + char buf[32]; + size_t len, oldlen; + pid_t pid; + u_quad_t q; + bool b, b2; + int i, va[2], lastva, mib[CTL_MAXNAME + 1]; + + subtest = 0; + + mib[0] = INT_MAX; /* some root-level identifier that does not exist */ + for (i = 1; i <= CTL_MAXNAME; i++) + mib[i] = i; + + /* + * We cannot test for invalid 'name' and 'oldlenp' pointers, because + * those may be accessed directly by the libc system call stub. The + * NetBSD part of the stub even accesses name[0] without checking + * namelen first. + */ + if (sysctl(mib, 0, NULL, NULL, NULL, 0) != -1) e(0); + if (errno != EINVAL) e(0); + if (sysctl(mib, INT_MAX, NULL, NULL, NULL, 0) != -1) e(0); + if (errno != EINVAL) e(0); + if (sysctl(mib, UINT_MAX, NULL, NULL, NULL, 0) != -1) e(0); + if (errno != EINVAL) e(0); + for (i = 1; i <= CTL_MAXNAME; i++) { + if (sysctl(mib, i, NULL, NULL, NULL, 0) != -1) e(i); + if (errno != ENOENT) e(i); + } + if (sysctl(mib, i, NULL, NULL, NULL, 0) != -1) e(0); + if (errno != EINVAL) e(0); + + /* Test names that are too short, right, and too long. */ + mib[0] = CTL_MINIX; + if (sysctl(mib, 1, NULL, NULL, NULL, 0) != -1) e(0); + if (errno != EISDIR) e(0); + mib[1] = MINIX_TEST; + if (sysctl(mib, 2, NULL, NULL, NULL, 0) != -1) e(0); + if (errno != EISDIR) e(0); + mib[2] = TEST_INT; + if (sysctl(mib, 3, NULL, NULL, NULL, 0) != 0) e(0); + mib[3] = 0; + if (sysctl(mib, 4, NULL, NULL, NULL, 0) != -1) e(0); + if (errno != ENOTDIR) e(0); + + /* Do some tests with meta-identifiers (special keys). */ + mib[3] = CTL_QUERY; + if (sysctl(mib, 4, NULL, NULL, NULL, 0) != -1) e(0); + if (errno != ENOTDIR) e(0); + + mib[2] = CTL_QUERY; + mib[3] = 0; + if (sysctl(mib, 4, NULL, NULL, NULL, 0) != -1) e(0); + if (errno != EINVAL) e(0); + + mib[2] = CTL_EOL; /* a known-invalid meta-identifier */ + if (sysctl(mib, 3, NULL, NULL, NULL, 0) != -1) e(0); + if (errno != EOPNOTSUPP) e(0); + + /* This case returns EINVAL now but might as well return EOPNOTSUPP. */ + mib[3] = 0; + if (sysctl(mib, 4, NULL, NULL, NULL, 0) != -1) e(0); + if (errno != EOPNOTSUPP && errno != EINVAL) e(0); + + /* Make sure the given oldlen value is ignored when unused. */ + mib[2] = TEST_INT; + oldlen = 0; + if (sysctl(mib, 3, NULL, &oldlen, NULL, 0) != 0) e(0); + if (oldlen != sizeof(int)) e(0); + oldlen = 1; + if (sysctl(mib, 3, NULL, &oldlen, NULL, 0) != 0) e(0); + if (oldlen != sizeof(int)) e(0); + oldlen = SSIZE_MAX; + if (sysctl(mib, 3, NULL, &oldlen, NULL, 0) != 0) e(0); + if (oldlen != sizeof(int)) e(0); + oldlen = SIZE_MAX; + if (sysctl(mib, 3, NULL, &oldlen, NULL, 0) != 0) e(0); + if (oldlen != sizeof(int)) e(0); + + /* Test retrieval with the exact length. */ + oldlen = sizeof(va[0]); + va[0] = va[1] = -1; + if (sysctl(mib, 3, va, &oldlen, NULL, 0) != 0) e(0); + if (oldlen != sizeof(va[0])) e(0); + if (va[0] != 0x01020304) e(0); + if (va[1] != -1) e(0); + + /* Test retrieval with a length that is too short. */ + for (i = 0; i < sizeof(va[0]); i++) { + va[0] = -1; + oldlen = i; + if (sysctl(mib, 3, va, &oldlen, NULL, 0) != -1) e(0); + if (errno != ENOMEM) e(0); + if (oldlen != sizeof(va[0])) e(0); + if (i == 0 && va[0] != -1) e(0); + if (i > 0 && va[0] >= lastva) e(0); + if (va[1] != -1) e(0); + lastva = va[0]; + } + + /* Test retrieval with a length that is too long. */ + oldlen = sizeof(va[0]) + 1; + va[0] = -1; + if (sysctl(mib, 3, va, &oldlen, NULL, 0) != 0) e(0); + if (oldlen != sizeof(va[0])) e(0); + if (va[0] != 0x01020304) e(0); + if (va[1] != -1) e(0); + + oldlen = SSIZE_MAX; + va[0] = -1; + if (sysctl(mib, 3, va, &oldlen, NULL, 0) != 0) e(0); + if (oldlen != sizeof(va[0])) e(0); + if (va[0] != 0x01020304) e(0); + if (va[1] != -1) e(0); + + oldlen = SIZE_MAX; + va[0] = -1; + if (sysctl(mib, 3, va, &oldlen, NULL, 0) != 0) e(0); + if (oldlen != sizeof(va[0])) e(0); + if (va[0] != 0x01020304) e(0); + if (va[1] != -1) e(0); + + /* + * Ensure that we cannot overwrite this read-only integer. A write + * request must have both a pointer and a nonzero length, though. + */ + va[0] = 0x05060708; + if (sysctl(mib, 3, NULL, NULL, NULL, 1) != 0) e(0); + if (sysctl(mib, 3, NULL, NULL, va, 0) != 0) e(0); + if (sysctl(mib, 3, NULL, NULL, va, sizeof(va[0])) != -1) e(0); + if (errno != EPERM) e(0); + + oldlen = sizeof(va[0]); + va[0] = -1; + if (sysctl(mib, 3, va, &oldlen, NULL, 0) != 0) e(0); + if (oldlen != sizeof(va[0])) e(0); + if (va[0] != 0x01020304) e(0); + if (va[1] != -1) e(0); + + /* Test retrieval into a bad pointer. */ + oldlen = sizeof(int); + if (sysctl(mib, 3, bad_ptr, &oldlen, NULL, 0) != -1) e(0); + if (errno != EFAULT) e(0); + + /* + * Test reading and writing booleans. Booleans may actually be an int, + * a char, or just one bit of a char. As a result, the MIB service can + * not test properly for non-bool values being passed in bool fields, + * and we can not do effective testing on this either, because in both + * cases our efforts may simply be optimized away, and result in + * unexpected success. + */ + mib[2] = TEST_BOOL; + oldlen = sizeof(b); + if (sysctl(mib, 3, &b, &oldlen, NULL, 0) != 0) e(0); + if (oldlen != sizeof(b)) e(0); + if (b != false && b != true) e(0); + + b = true; + if (sysctl(mib, 3, NULL, &oldlen, &b, sizeof(b)) != 0) e(0); + if (oldlen != sizeof(b)) e(0); + + b = false; + if (sysctl(mib, 3, &b, &oldlen, NULL, 0) != 0) e(0); + if (oldlen != sizeof(b)) e(0); + if (b != true) e(0); + + b = false; + b2 = false; + oldlen = sizeof(b2); + if (sysctl(mib, 3, &b2, &oldlen, &b, sizeof(b)) != 0) e(0); + if (oldlen != sizeof(b2)) e(0); + if (b != false) e(0); + if (b2 != true) e(0); + + if (sysctl(mib, 3, NULL, NULL, &b, sizeof(b) + 1) != -1) e(0); + if (errno != EINVAL) e(0); + + /* + * The MIB service does not support value swaps. If we pass in the + * same buffer for old and new data, we expect that the old data stays. + */ + b = true; + oldlen = sizeof(b); + if (sysctl(mib, 3, &b, &oldlen, &b, sizeof(b)) != 0) e(0); + if (oldlen != sizeof(b)) e(0); + if (b != false) e(0); + + b = true; + oldlen = sizeof(b); + if (sysctl(mib, 3, &b, &oldlen, NULL, 0) != 0) e(0); + if (oldlen != sizeof(b)) e(0); + if (b != false) e(0); + + /* Test reading and writing a quad. */ + mib[2] = TEST_QUAD; + if (sysctl(mib, 3, NULL, &oldlen, NULL, 0) != 0) e(0); + if (oldlen != sizeof(q)) e(0); + + q = 0x1234567890abcdefULL; + if (sysctl(mib, 3, NULL, NULL, &q, sizeof(q)) != 0) e(0); + + q = 0ULL; + oldlen = sizeof(q); + if (sysctl(mib, 3, &q, &oldlen, NULL, 0) != 0) e(0); + if (oldlen != sizeof(q)) e(0); + if (q != 0x1234567890abcdefULL) e(0); + + q = ~0ULL; + if (sysctl(mib, 3, NULL, NULL, &q, sizeof(q)) != 0) e(0); + + /* Test writing with a bad pointer. The value must stay. */ + if (sysctl(mib, 3, NULL, NULL, bad_ptr, sizeof(q)) != -1) e(0); + if (errno != EFAULT) e(0); + + q = 0ULL; + oldlen = sizeof(q); + if (sysctl(mib, 3, &q, &oldlen, NULL, 0) != 0) e(0); + if (oldlen != sizeof(q)) e(0); + if (q != ~0ULL) e(0); + + q = 0ULL; + if (sysctl(mib, 3, NULL, NULL, &q, sizeof(q)) != 0) e(0); + + q = 1ULL; + oldlen = sizeof(q); + if (sysctl(mib, 3, &q, &oldlen, NULL, 0) != 0) e(0); + if (oldlen != sizeof(q)) e(0); + if (q != 0ULL) e(0); + + /* Test reading and writing a string. */ + mib[2] = TEST_STRING; + strlcpy(buf, "test", sizeof(buf)); + len = strlen(buf); + if (sysctl(mib, 3, NULL, NULL, buf, len + 1) != 0) e(0); + + oldlen = sizeof(buf); + if (sysctl(mib, 3, NULL, &oldlen, NULL, 0) != 0) e(0); + if (oldlen != len + 1) e(0); + + memset(buf, 0x07, sizeof(buf)); + oldlen = sizeof(buf); + if (sysctl(mib, 3, buf, &oldlen, NULL, 0) != 0) e(0); + if (strcmp(buf, "test")) e(0); + if (oldlen != len + 1) e(0); + if (buf[len + 1] != 0x07) e(0); + + strlcpy(buf, "abc123", sizeof(buf)); + oldlen = 2; + if (sysctl(mib, 3, NULL, &oldlen, buf, strlen(buf) + 1) != 0) e(0); + if (oldlen != len + 1) e(0); + len = strlen(buf); + + memset(buf, 0x07, sizeof(buf)); + oldlen = len - 1; + if (sysctl(mib, 3, buf, &oldlen, NULL, 0) != -1) e(0); + if (errno != ENOMEM) e(0); + if (oldlen != len + 1) e(0); + if (strncmp(buf, "abc12", len - 1)) e(0); + if (buf[len - 1] != 0x07 || buf[len] != 0x07) e(0); + + memset(buf, 0x07, sizeof(buf)); + oldlen = len + 1; + if (sysctl(mib, 3, buf, &oldlen, NULL, 0) != 0) e(0); + if (oldlen != len + 1) e(0); + if (strcmp(buf, "abc123")) e(0); + + /* + * Now put in a shorter string, without null terminator. The string + * must be accepted; the null terminator must be added automatically. + */ + strlcpy(buf, "foolproof", sizeof(buf)); + len = strlen("foo"); + if (sysctl(mib, 3, NULL, NULL, buf, len) != 0) e(0); + + if (sysctl(mib, 3, NULL, &oldlen, NULL, 0) != 0) e(0); + if (oldlen != len + 1) e(0); + + memset(buf, 0x07, sizeof(buf)); + oldlen = len; + if (sysctl(mib, 3, buf, &oldlen, NULL, 0) != -1) e(0); + if (errno != ENOMEM) e(0); + if (oldlen != len + 1) e(0); + if (strncmp(buf, "foo", len)) e(0); + if (buf[len] != 0x07) e(0); + + memset(buf, 0x07, sizeof(buf)); + oldlen = sizeof(buf); + if (sysctl(mib, 3, buf, &oldlen, NULL, 0) != 0) e(0); + if (oldlen != len + 1) e(0); + if (strcmp(buf, "foo")) e(0); + if (buf[len + 1] != 0x07) e(0); + + /* + * Passing in more data after the string is fine, but whatever comes + * after the first null terminator is disregarded. + */ + strlcpy(buf, "barbapapa", sizeof(buf)); + len = strlen(buf); + buf[3] = '\0'; + if (sysctl(mib, 3, NULL, NULL, buf, len + 1)) e(0); + len = strlen(buf); + + if (sysctl(mib, 3, NULL, &oldlen, NULL, 0) != 0) e(0); + if (oldlen != len + 1) e(0); + + memset(buf, 0x07, sizeof(buf)); + oldlen = sizeof(buf); + if (sysctl(mib, 3, buf, &oldlen, NULL, 0) != 0) e(0); + if (oldlen != len + 1) e(0); + if (strcmp(buf, "bar")) e(0); + if (buf[len + 1] != 0x07) e(0); + + /* Test the maximum string length. */ + strlcpy(buf, "0123456789abcdef", sizeof(buf)); + len = strlen(buf); + if (sysctl(mib, 3, NULL, NULL, buf, len + 1) != -1) e(0); + if (errno != EINVAL) e(0); + if (sysctl(mib, 3, NULL, NULL, buf, len) != -1) e(0); + if (errno != EINVAL) e(0); + + buf[--len] = '\0'; + if (sysctl(mib, 3, NULL, NULL, buf, len + 1) != 0) e(0); + memset(buf, 0x07, sizeof(buf)); + oldlen = sizeof(buf); + if (sysctl(mib, 3, buf, &oldlen, NULL, 0) != 0) e(0); + if (oldlen != len + 1) e(0); + if (strcmp(buf, "0123456789abcde")) e(0); + if (buf[len + 1] != 0x07) e(0); + + /* + * Clearing out the field with zero-length data is not possible, + * because zero-length updates are disregarded at a higher level. + */ + if (sysctl(mib, 3, NULL, NULL, "", 0) != 0) e(0); + memset(buf, 0x07, sizeof(buf)); + oldlen = sizeof(buf); + if (sysctl(mib, 3, buf, &oldlen, NULL, 0) != 0) e(0); + if (oldlen != len + 1) e(0); + if (strcmp(buf, "0123456789abcde")) e(0); + + /* To clear the field, the null terminator is required. */ + if (sysctl(mib, 3, NULL, NULL, "", 1) != 0) e(0); + memset(buf, 0x07, sizeof(buf)); + oldlen = sizeof(buf); + if (sysctl(mib, 3, buf, &oldlen, NULL, 0) != 0) e(0); + if (oldlen != 1) e(0); + if (buf[0] != '\0') e(0); + if (buf[1] != 0x07) e(0); + + /* + * Test reading and writing structures. Structures are just blobs of + * data, with no special handling by default. They can only be read + * and written all at once. + */ + mib[2] = TEST_STRUCT; + if (sysctl(mib, 3, NULL, &oldlen, NULL, 0) != 0) e(0); + if (oldlen != 12) e(0); + len = oldlen; + + for (i = 0; i < len + 1; i++) + buf[i] = i + 1; + if (sysctl(mib, 3, NULL, NULL, buf, len - 1) != -1) e(0); + if (errno != EINVAL) e(0); + if (sysctl(mib, 3, NULL, NULL, buf, len + 1) != -1) e(0); + if (errno != EINVAL) e(0); + if (sysctl(mib, 3, NULL, NULL, buf, len) != 0) e(0); + + memset(buf, 0x7f, sizeof(buf)); + oldlen = len - 1; + if (sysctl(mib, 3, buf, &oldlen, NULL, 0) != -1) e(0); + if (errno != ENOMEM) e(0); + if (oldlen != len) e(0); + for (i = 0; i < len - 1; i++) + if (buf[i] != i + 1) e(0); + if (buf[i] != 0x7f) e(0); + + memset(buf, 0x7f, sizeof(buf)); + oldlen = len + 1; + if (sysctl(mib, 3, buf, &oldlen, NULL, 0) != 0) e(0); + if (oldlen != len) e(0); + for (i = 0; i < len; i++) + if (buf[i] != i + 1) e(0); + if (buf[i] != 0x7f) e(0); + + memset(buf, 0x7f, sizeof(buf)); + oldlen = len; + if (sysctl(mib, 3, buf, &oldlen, NULL, 0) != 0) e(0); + for (i = 0; i < len; i++) + if (buf[i] != i + 1) e(0); + if (buf[len] != 0x7f) e(0); + + /* Null characters are not treated in any special way. */ + for (i = 0; i < len; i++) + buf[i] = !!i; + if (sysctl(mib, 3, NULL, NULL, buf, len) != 0) e(0); + + oldlen = len; + if (sysctl(mib, 3, buf, &oldlen, NULL, 0) != 0) e(0); + if (oldlen != len) e(0); + for (i = 0; i < len; i++) + if (buf[i] != !!i) e(0); + if (buf[len] != 0x7f) e(0); + + memset(buf, 0, len); + if (sysctl(mib, 3, NULL, NULL, buf, len) != 0) e(0); + + oldlen = len; + if (sysctl(mib, 3, buf, &oldlen, NULL, 0) != 0) e(0); + if (oldlen != len) e(0); + for (i = 0; i < len; i++) + if (buf[i] != 0) e(0); + if (buf[len] != 0x7f) e(0); + + /* + * Test private read and free-for-all write operations. For starters, + * this test should run with superuser privileges, and thus should be + * able to read and write private fields. + */ + mib[2] = TEST_PRIVATE; + if (sysctl(mib, 3, NULL, &oldlen, NULL, 0) != 0) e(0); + if (oldlen != sizeof(va[0])) e(0); + if (sysctl(mib, 3, va, &oldlen, NULL, 0) != 0) e(0); + if (va[0] != -5375) e(0); + if (sysctl(mib, 3, NULL, NULL, va, sizeof(va[0])) != 0) e(0); + + mib[2] = TEST_SECRET; + mib[3] = SECRET_VALUE; + oldlen = sizeof(va[0]); + if (sysctl(mib, 4, va, &oldlen, NULL, 0) != 0) e(0); + if (va[0] != 12345) e(0); + if (sysctl(mib, 4, NULL, NULL, va, sizeof(va[0])) != -1) e(0); + if (errno != EPERM) e(0); + + mib[3]++; + i = 0; + oldlen = sizeof(i); + if (sysctl(mib, 4, &i, &oldlen, NULL, 0) != -1) e(0); + if (errno != ENOENT) e(0); + + /* Use a child process to test operations without root privileges. */ + pid = test_nonroot(sub87a); + + /* The change made by the child should be visible to the parent. */ + mib[2] = TEST_ANYWRITE; + va[0] = 0; + oldlen = sizeof(va[0]); + if (sysctl(mib, 3, va, &oldlen, NULL, 0) != 0) e(0); + if (oldlen != sizeof(va[0])) e(0); + if (va[0] != pid) e(0); +} + +/* + * Test queries from an unprivileged process. + */ +static void +sub87b(void) +{ + struct sysctlnode scn[32]; + unsigned int count; + size_t oldlen; + int i, mib[4]; + + /* Query minix.test and make sure we do not get privileged values. */ + mib[0] = CTL_MINIX; + mib[1] = MINIX_TEST; + mib[2] = CTL_QUERY; + + oldlen = sizeof(scn); + if (sysctl(mib, 3, scn, &oldlen, NULL, 0) != 0) e(0); + if (oldlen % sizeof(scn[0])) e(0); + count = oldlen / sizeof(scn[0]); + if (count < 8) e(0); + + /* + * Do not bother doing the entire check again, but test enough to + * inspire confidence that only the right values are hidden. + */ + if (scn[0].sysctl_num != TEST_INT) e(0); + if (SYSCTL_TYPE(scn[0].sysctl_flags) != CTLTYPE_INT) e(0); + if (SYSCTL_FLAGS(scn[0].sysctl_flags) != + (CTLFLAG_READONLY | CTLFLAG_IMMEDIATE | CTLFLAG_HEX)) e(0); + if (SYSCTL_VERS(scn[0].sysctl_flags) != SYSCTL_VERSION) e(0); + if (strcmp(scn[0].sysctl_name, "int")) e(0); + if (scn[0].sysctl_ver == 0) e(0); + if (scn[0].sysctl_size != sizeof(int)) e(0); + if (scn[0].sysctl_idata != 0x01020304) e(0); + + for (i = 0; i < count; i++) + if (scn[i].sysctl_num == TEST_PRIVATE) + break; + if (i == count) e(0); + if (SYSCTL_TYPE(scn[i].sysctl_flags) != CTLTYPE_INT) e(0); + if (SYSCTL_FLAGS(scn[i].sysctl_flags) != + (CTLFLAG_READWRITE | CTLFLAG_PRIVATE | CTLFLAG_IMMEDIATE)) e(0); + if (SYSCTL_VERS(scn[i].sysctl_flags) != SYSCTL_VERSION) e(0); + if (strcmp(scn[i].sysctl_name, "private")) e(0); + if (scn[i].sysctl_size != sizeof(int)) e(0); + if (scn[i].sysctl_idata != 0) e(0); /* private */ + + for (i = 0; i < count; i++) + if (scn[i].sysctl_num == TEST_SECRET) + break; + if (i == count) e(0); + if (SYSCTL_TYPE(scn[i].sysctl_flags) != CTLTYPE_NODE) e(0); + if (SYSCTL_FLAGS(scn[i].sysctl_flags) != + (CTLFLAG_READONLY | CTLFLAG_PRIVATE)) e(0); + if (SYSCTL_VERS(scn[i].sysctl_flags) != SYSCTL_VERSION) e(0); + if (strcmp(scn[i].sysctl_name, "secret")) e(0); + if (scn[i].sysctl_ver == 0) e(0); + if (scn[i].sysctl_size != sizeof(scn[0])) e(0); + if (scn[i].sysctl_csize != 0) e(0); /* private */ + if (scn[i].sysctl_clen != 0) e(0); /* private */ + + /* Make sure that a query on minix.test.secret fails. */ + mib[2] = TEST_SECRET; + mib[3] = CTL_QUERY; + if (sysctl(mib, 4, NULL, &oldlen, NULL, 0) != -1) e(0); + if (errno != EPERM) e(0); +} + +/* + * Test sysctl(2) queries. + */ +static void +test87b(void) +{ + struct sysctlnode scn[32]; + unsigned int count; + size_t len, oldlen; + u_quad_t q; + bool b; + int i, mib[4]; + + subtest = 1; + + /* We should be able to query the root key. */ + mib[0] = CTL_QUERY; + + oldlen = 0; + if (sysctl(mib, 1, NULL, &oldlen, NULL, 0) != 0) e(0); + if (oldlen <= sizeof(scn[0])) e(0); + if (oldlen % sizeof(scn[0])) e(0); + + oldlen = sizeof(scn[0]); + if (sysctl(mib, 1, scn, &oldlen, NULL, 0) != -1) e(0); + if (errno != ENOMEM); + if (oldlen <= sizeof(scn[0])) e(0); + if (oldlen % sizeof(scn[0])) e(0); + + /* + * We assume that the root node's first child is always CTL_KERN, which + * must be read-only and may have only the CTLFLAG_PERMANENT flag set. + */ + if (scn[0].sysctl_num != CTL_KERN) e(0); + if (SYSCTL_TYPE(scn[0].sysctl_flags) != CTLTYPE_NODE) e(0); + if ((SYSCTL_FLAGS(scn[0].sysctl_flags) & ~CTLFLAG_PERMANENT) != + CTLFLAG_READONLY) e(0); + if (SYSCTL_VERS(scn[0].sysctl_flags) != SYSCTL_VERSION) e(0); + if (strcmp(scn[0].sysctl_name, "kern")) e(0); + if (scn[0].sysctl_ver == 0) e(0); + if (scn[0].sysctl_size != sizeof(scn[0])) e(0); + if ((int)scn[0].sysctl_csize <= 0) e(0); + if ((int)scn[0].sysctl_clen <= 0) e(0); + if (scn[0].sysctl_csize < scn[0].sysctl_clen) e(0); + + /* Now do a more complete test on the minix.test subtree. */ + mib[0] = CTL_MINIX; + mib[1] = MINIX_TEST; + + /* + * Initialize a few immediate fields to nonzero so that we can test + * that their values are returned as a result of the query. + */ + mib[2] = TEST_BOOL; + b = true; + if (sysctl(mib, 3, NULL, NULL, &b, sizeof(b)) != 0) e(0); + + mib[2] = TEST_QUAD; + q = ~0; + if (sysctl(mib, 3, NULL, NULL, &q, sizeof(q)) != 0) e(0); + + mib[2] = CTL_QUERY; + + oldlen = 1; + if (sysctl(mib, 3, NULL, &oldlen, NULL, 0) != 0) e(0); + if (oldlen % sizeof(scn[0])) e(0); + if (oldlen >= sizeof(scn)) e(0); + len = oldlen; + count = len / sizeof(scn[0]); + if (count < 8) e(0); + + memset(scn, 0x7e, sizeof(scn)); + if (sysctl(mib, 3, scn, &oldlen, NULL, 0) != 0) e(0); + if (oldlen != len) e(0); + if (scn[count].sysctl_name[0] != 0x7e) e(0); + + /* + * Again, we rely on the MIB service returning entries in ascending + * order for at least the static nodes. We do not make assumptions + * about whether dynamic nodes are merged in or (as is the case as of + * writing) returned after the static nodes. At this point there + * should be no dynamic nodes here yet anyway. + */ + if (scn[0].sysctl_num != TEST_INT) e(0); + if (SYSCTL_TYPE(scn[0].sysctl_flags) != CTLTYPE_INT) e(0); + if (SYSCTL_FLAGS(scn[0].sysctl_flags) != + (CTLFLAG_READONLY | CTLFLAG_IMMEDIATE | CTLFLAG_HEX)) e(0); + if (SYSCTL_VERS(scn[0].sysctl_flags) != SYSCTL_VERSION) e(0); + if (strcmp(scn[0].sysctl_name, "int")) e(0); + if (scn[0].sysctl_ver == 0) e(0); + if (scn[0].sysctl_size != sizeof(int)) e(0); + if (scn[0].sysctl_idata != 0x01020304) e(0); + + if (scn[1].sysctl_num != TEST_BOOL) e(0); + if (SYSCTL_TYPE(scn[1].sysctl_flags) != CTLTYPE_BOOL) e(0); + if (SYSCTL_FLAGS(scn[1].sysctl_flags) != + (CTLFLAG_READWRITE | CTLFLAG_IMMEDIATE)) e(0); + if (SYSCTL_VERS(scn[1].sysctl_flags) != SYSCTL_VERSION) e(0); + if (strcmp(scn[1].sysctl_name, "bool")) e(0); + if (scn[1].sysctl_ver == 0) e(0); + if (scn[1].sysctl_size != sizeof(bool)) e(0); + if (scn[1].sysctl_bdata != true) e(0); + + if (scn[2].sysctl_num != TEST_QUAD) e(0); + if (SYSCTL_TYPE(scn[2].sysctl_flags) != CTLTYPE_QUAD) e(0); + if (SYSCTL_FLAGS(scn[2].sysctl_flags) != + (CTLFLAG_READWRITE | CTLFLAG_IMMEDIATE)) e(0); + if (SYSCTL_VERS(scn[2].sysctl_flags) != SYSCTL_VERSION) e(0); + if (strcmp(scn[2].sysctl_name, "quad")) e(0); + if (scn[2].sysctl_ver == 0) e(0); + if (scn[2].sysctl_size != sizeof(u_quad_t)) e(0); + if (scn[2].sysctl_qdata != q) e(0); + + if (scn[3].sysctl_num != TEST_STRING) e(0); + if (SYSCTL_TYPE(scn[3].sysctl_flags) != CTLTYPE_STRING) e(0); + if (SYSCTL_FLAGS(scn[3].sysctl_flags) != CTLFLAG_READWRITE) e(0); + if (SYSCTL_VERS(scn[3].sysctl_flags) != SYSCTL_VERSION) e(0); + if (strcmp(scn[3].sysctl_name, "string")) e(0); + if (scn[3].sysctl_ver == 0) e(0); + if (scn[3].sysctl_size != 16) e(0); + + if (scn[4].sysctl_num != TEST_STRUCT) e(0); + if (SYSCTL_TYPE(scn[4].sysctl_flags) != CTLTYPE_STRUCT) e(0); + if (SYSCTL_FLAGS(scn[4].sysctl_flags) != CTLFLAG_READWRITE) e(0); + if (SYSCTL_VERS(scn[4].sysctl_flags) != SYSCTL_VERSION) e(0); + if (strcmp(scn[4].sysctl_name, "struct")) e(0); + if (scn[4].sysctl_ver == 0) e(0); + if (scn[4].sysctl_size != 12) e(0); + + if (scn[5].sysctl_num != TEST_PRIVATE) e(0); + if (SYSCTL_TYPE(scn[5].sysctl_flags) != CTLTYPE_INT) e(0); + if (SYSCTL_FLAGS(scn[5].sysctl_flags) != + (CTLFLAG_READWRITE | CTLFLAG_PRIVATE | CTLFLAG_IMMEDIATE)) e(0); + if (SYSCTL_VERS(scn[5].sysctl_flags) != SYSCTL_VERSION) e(0); + if (strcmp(scn[5].sysctl_name, "private")) e(0); + if (scn[5].sysctl_ver == 0) e(0); + if (scn[5].sysctl_size != sizeof(int)) e(0); + if (scn[5].sysctl_idata != -5375) e(0); + + if (scn[6].sysctl_num != TEST_ANYWRITE) e(0); + if (SYSCTL_TYPE(scn[6].sysctl_flags) != CTLTYPE_INT) e(0); + if (SYSCTL_FLAGS(scn[6].sysctl_flags) != + (CTLFLAG_READWRITE | CTLFLAG_ANYWRITE | CTLFLAG_IMMEDIATE)) e(0); + if (SYSCTL_VERS(scn[6].sysctl_flags) != SYSCTL_VERSION) e(0); + if (strcmp(scn[6].sysctl_name, "anywrite")) e(0); + if (scn[6].sysctl_ver == 0) e(0); + if (scn[6].sysctl_size != sizeof(int)) e(0); + + i = (scn[7].sysctl_num == TEST_DYNAMIC) ? 8 : 7; + + if (scn[i].sysctl_num != TEST_SECRET) e(0); + if (SYSCTL_TYPE(scn[i].sysctl_flags) != CTLTYPE_NODE) e(0); + if (SYSCTL_FLAGS(scn[i].sysctl_flags) != + (CTLFLAG_READONLY | CTLFLAG_PRIVATE)) e(0); + if (SYSCTL_VERS(scn[i].sysctl_flags) != SYSCTL_VERSION) e(0); + if (strcmp(scn[i].sysctl_name, "secret")) e(0); + if (scn[i].sysctl_ver == 0) e(0); + if (scn[i].sysctl_size != sizeof(scn[0])) e(0); + if (scn[i].sysctl_csize != 1) e(0); + if (scn[i].sysctl_clen != 1) e(0); + + /* + * Now that we know how many entries there are in minix.test, also look + * at whether the right child length is returned in a query on its + * parent. While doing that, see whether data structure versioning + * works as expected as well. MINIX_TEST is hardcoded to zero so we + * expect it to be the first entry returned from a query. + */ + mib[1] = CTL_QUERY; + + memset(scn, 0, sizeof(scn)); + scn[1].sysctl_flags = SYSCTL_VERS_0; + if (sysctl(mib, 2, NULL, &oldlen, &scn[1], sizeof(scn[1])) != -1) e(0); + if (errno != EINVAL) e(0); + scn[1].sysctl_flags = SYSCTL_VERS_1; + if (sysctl(mib, 2, NULL, &oldlen, &scn[1], sizeof(scn[1]) - 1) != -1) + e(0); + if (errno != EINVAL) e(0); + if (sysctl(mib, 2, NULL, &oldlen, &scn[1], sizeof(scn[1]) + 1) != -1) + e(0); + if (errno != EINVAL) e(0); + if (sysctl(mib, 2, NULL, &oldlen, &scn[1], sizeof(scn[1])) != 0) e(0); + if (oldlen == 0) e(0); + if (oldlen % sizeof(scn[0])) e(0); + + oldlen = sizeof(scn[0]); + scn[1].sysctl_flags = SYSCTL_VERS_0; + if (sysctl(mib, 2, scn, &oldlen, &scn[1], sizeof(scn[1])) != -1) e(0); + if (errno != EINVAL) e(0); + oldlen = sizeof(scn[0]); + scn[1].sysctl_flags = SYSCTL_VERS_1; + if (sysctl(mib, 2, scn, &oldlen, &scn[1], sizeof(scn[1])) != 0 && + errno != ENOMEM) e(0); + if (oldlen == 0) e(0); + if (oldlen % sizeof(scn[0])) e(0); + + if (scn[0].sysctl_num != MINIX_TEST) e(0); + if (SYSCTL_TYPE(scn[0].sysctl_flags) != CTLTYPE_NODE) e(0); + if ((SYSCTL_FLAGS(scn[0].sysctl_flags) & ~CTLFLAG_PERMANENT) != + (CTLFLAG_READWRITE | CTLFLAG_HIDDEN)) e(0); + if (SYSCTL_VERS(scn[0].sysctl_flags) != SYSCTL_VERSION) e(0); + if (strcmp(scn[0].sysctl_name, "test")) e(0); + if (scn[0].sysctl_ver == 0) e(0); + if (scn[0].sysctl_size != sizeof(scn[0])) e(0); + if ((int)scn[0].sysctl_clen != count) e(0); + if (scn[0].sysctl_csize < scn[0].sysctl_clen) e(0); + + /* + * Test querying minix.test.secret, which should have exactly one node. + * At the same time, test bad pointers. + */ + mib[1] = MINIX_TEST; + mib[2] = TEST_SECRET; + mib[3] = CTL_QUERY; + oldlen = sizeof(scn); + if (sysctl(mib, 4, NULL, &oldlen, bad_ptr, sizeof(scn[0])) != -1) e(0); + if (errno != EFAULT) e(0); + + oldlen = sizeof(scn[0]) * 2; + if (sysctl(mib, 4, bad_ptr, &oldlen, NULL, 0) != -1) e(0); + if (errno != EFAULT) e(0); + + memset(scn, 0x7, sizeof(scn[0]) * 2); + oldlen = sizeof(scn[0]) * 2; + if (sysctl(mib, 4, scn, &oldlen, NULL, 0) != 0) e(0); + if (oldlen != sizeof(scn[0])) e(0); + + if (scn[0].sysctl_num != SECRET_VALUE) e(0); + if (SYSCTL_TYPE(scn[0].sysctl_flags) != CTLTYPE_INT) e(0); + if (SYSCTL_FLAGS(scn[0].sysctl_flags) != + (CTLFLAG_READONLY | CTLFLAG_IMMEDIATE)) e(0); + if (SYSCTL_VERS(scn[0].sysctl_flags) != SYSCTL_VERSION) e(0); + if (strcmp(scn[0].sysctl_name, "value")) e(0); + if (scn[0].sysctl_ver == 0) e(0); + if (scn[0].sysctl_size != sizeof(int)) e(0); + if (scn[0].sysctl_idata != 12345) e(0); + if (scn[1].sysctl_name[0] != 0x07) e(0); + + /* Use a child process to test queries without root privileges. */ + (void)test_nonroot(sub87b); + + /* Do some more path-related error code tests unrelated to the rest. */ + mib[1] = INT_MAX; + mib[2] = CTL_QUERY; + oldlen = sizeof(scn[0]); + if (sysctl(mib, 3, &scn, &oldlen, NULL, 0) != -1) e(0); + if (errno != ENOENT) e(0); + + mib[1] = MINIX_TEST; + mib[2] = TEST_INT; + mib[3] = CTL_QUERY; + oldlen = sizeof(scn[0]); + if (sysctl(mib, 4, &scn, &oldlen, NULL, 0) != -1) e(0); + if (errno != ENOTDIR) e(0); /* ..and not EPERM (_INT is read-only) */ + + mib[2] = TEST_BOOL; + oldlen = sizeof(scn[0]); + if (sysctl(mib, 4, &scn, &oldlen, NULL, 0) != -1) e(0); + if (errno != ENOTDIR) e(0); /* (_BOOL is read-write) */ + + mib[2] = CTL_QUERY; + oldlen = sizeof(scn[0]); + if (sysctl(mib, 4, &scn, &oldlen, NULL, 0) != -1) e(0); + if (errno != EINVAL) e(0); +} + +/* + * Attempt to create a node, using a given node template, identifier, and name + * string. If other_id is nonnegative, the creation is expected to fail due to + * a collision with an existing node, which should have the ID other_id and the + * name string in other_name. Otherwise, the creation may succeed or fail, and + * the caller must perform the appropriate checks. On success, return the new + * node identifier. On failure, return -1, with errno set. + */ +static int +create_node(const int * path, unsigned int pathlen, struct sysctlnode * tmpscn, + int id, const char * name, int other_id, const char * other_name) +{ + struct sysctlnode scn, oldscn; + size_t oldlen; + int r, mib[CTL_MAXNAME]; + + assert(pathlen < CTL_MAXNAME); + memcpy(mib, path, sizeof(mib[0]) * pathlen); + mib[pathlen] = CTL_CREATE; + + memcpy(&scn, tmpscn, sizeof(scn)); + scn.sysctl_num = id; + strlcpy(scn.sysctl_name, name, sizeof(scn.sysctl_name)); + oldlen = sizeof(oldscn); + r = sysctl(mib, pathlen + 1, &oldscn, &oldlen, &scn, sizeof(scn)); + if (other_id >= 0) { /* conflict expected */ + if (oldlen != sizeof(oldscn)) e(0); + if (r != -1) e(0); + if (errno != EEXIST) e(0); + if (oldscn.sysctl_num != other_id) e(0); + if (strcmp(oldscn.sysctl_name, other_name)) e(0); + return -1; + } else { + if (r != 0) + return r; + if (oldlen != sizeof(oldscn)) e(0); + return oldscn.sysctl_num; + } +} + +/* + * Destroy a node by identifier in the given named node directory. Return 0 on + * success. Return -1 on failure, with errno set. + */ +static int +destroy_node(const int * path, unsigned int pathlen, int id) +{ + struct sysctlnode scn; + int mib[CTL_MAXNAME]; + + assert(pathlen < CTL_MAXNAME); + memcpy(mib, path, sizeof(mib[0]) * pathlen); + mib[pathlen] = CTL_DESTROY; + + memset(&scn, 0, sizeof(scn)); + scn.sysctl_flags = SYSCTL_VERSION; + scn.sysctl_num = id; + + return sysctl(mib, pathlen + 1, NULL, NULL, &scn, sizeof(scn)); +} + +/* + * Obtain the node data for one particular node in a node directory, by its + * parent path and identifier. Return 0 on success, with the node details + * stored in 'scn', or -1 on failure. + */ +static int +query_node(const int * path, unsigned int pathlen, int id, + struct sysctlnode * scn) +{ + struct sysctlnode scnset[32]; + size_t oldlen; + unsigned int i; + int r, mib[CTL_MAXNAME]; + + assert(pathlen < CTL_MAXNAME); + memcpy(mib, path, sizeof(mib[0]) * pathlen); + mib[pathlen] = CTL_QUERY; + + oldlen = sizeof(scnset); + if ((r = sysctl(mib, pathlen + 1, scnset, &oldlen, NULL, 0)) != 0 && + errno != ENOMEM) e(0); + if (oldlen == 0 || oldlen % sizeof(scnset[0])) e(0); + for (i = 0; i < oldlen / sizeof(scnset[0]); i++) + if (scnset[i].sysctl_num == id) + break; + if (i == oldlen / sizeof(scnset[0])) { + if (r != 0) e(0); /* if this triggers, make scnset[] bigger! */ + return -1; + } + memcpy(scn, &scnset[i], sizeof(*scn)); + return 0; +} + +/* + * Test unprivileged node creation. + */ +static void +sub87c(void) +{ + struct sysctlnode scn; + int mib[4]; + + mib[0] = CTL_MINIX; + mib[1] = MINIX_TEST; + mib[2] = TEST_DYNAMIC; + mib[3] = CTL_CREATE; + + memset(&scn, 0, sizeof(scn)); + scn.sysctl_flags = SYSCTL_VERSION | CTLFLAG_IMMEDIATE | + CTLFLAG_READONLY | CTLTYPE_INT; + scn.sysctl_size = sizeof(int); + scn.sysctl_num = CTL_CREATE; + scn.sysctl_idata = 777; + strlcpy(scn.sysctl_name, "nonroot", sizeof(scn.sysctl_name)); + if (sysctl(mib, 4, NULL, NULL, &scn, sizeof(scn)) != -1) e(0); + if (errno != EPERM) e(0); + + mib[0] = CTL_CREATE; + scn.sysctl_num = CTL_MINIX + 1; + if (sysctl(mib, 1, NULL, NULL, &scn, sizeof(scn)) != -1) e(0); + if (errno != EPERM) e(0); +} + +/* + * Test sysctl(2) node creation. + */ +static void +test87c(void) +{ + static const uint32_t badflags[] = { + SYSCTL_VERS_MASK, SYSCTL_TYPEMASK, CTLFLAG_PERMANENT, + CTLFLAG_ROOT, CTLFLAG_ANYNUMBER, CTLFLAG_ALIAS, CTLFLAG_MMAP, + CTLFLAG_OWNDESC + }; + static const size_t badintsizes[] = { + 0, 1, sizeof(int) - 1, sizeof(int) + 1, sizeof(int) * 2, + sizeof(int) * 4, SSIZE_MAX, SIZE_MAX + }; + static const char *goodnames[] = { + "_", "a", "test_name", "_____foo", "bar_0_1_2_3", "_2bornot2b", + "abcdefghijklmnopqrstuvwxyz12345", + "ABCDEFGHIJKLMNOPQRSTUVWXYZ67890", + }; + static const char *badnames[] = { + "", "0", "test.name", "2bornot2b", "@a", "b[", "c`d", "{", + "\n", "\xff", "dir/name", "foo:bar", + "abcdefghijklmnopqrstuvwxyz123456" + }; + struct sysctlnode scn, pscn, oldscn, newscn, tmpscn, scnset[32]; + size_t oldlen, len; + char buf[32], seen[5]; + bool b; + u_quad_t q; + int i, mib[CTL_MAXNAME], id[3]; + + subtest = 2; + + /* + * On the first run of this test, this call with actually destroy a + * static node. On subsequent runs, it may clean up the most likely + * leftover from a previous failed test. + */ + mib[0] = CTL_MINIX; + mib[1] = MINIX_TEST; + (void)destroy_node(mib, 2, TEST_DYNAMIC); + + /* Get child statistics about the parent node, for later comparison. */ + if (query_node(mib, 1, MINIX_TEST, &pscn) != 0) e(0); + if (pscn.sysctl_clen == 0) e(0); + if (pscn.sysctl_csize <= pscn.sysctl_clen) e(0); + + /* Start by testing if we can actually create a node at all. */ + mib[2] = CTL_CREATE; + memset(&scn, 0, sizeof(scn)); + scn.sysctl_flags = SYSCTL_VERSION | CTLFLAG_IMMEDIATE | + CTLFLAG_READONLY | CTLTYPE_INT; + scn.sysctl_size = sizeof(int); + scn.sysctl_num = TEST_DYNAMIC; + scn.sysctl_idata = 777; + strlcpy(scn.sysctl_name, "dynamic", sizeof(scn.sysctl_name)); + oldlen = sizeof(newscn); + if (sysctl(mib, 3, &newscn, &oldlen, &scn, sizeof(scn)) != 0) e(0); + if (oldlen != sizeof(newscn)) e(0); + + memcpy(&tmpscn, &scn, sizeof(scn)); + + if (newscn.sysctl_num != TEST_DYNAMIC) e(0); + if (SYSCTL_TYPE(newscn.sysctl_flags) != CTLTYPE_INT) e(0); + if (SYSCTL_FLAGS(newscn.sysctl_flags) != + (CTLFLAG_READONLY | CTLFLAG_IMMEDIATE)) e(0); + if (SYSCTL_VERS(newscn.sysctl_flags) != SYSCTL_VERSION) e(0); + if (strcmp(newscn.sysctl_name, "dynamic")) e(0); + if (newscn.sysctl_ver == 0) e(0); + if (newscn.sysctl_size != sizeof(int)) e(0); + if (newscn.sysctl_idata != 777) e(0); + + /* Can we also read its value? */ + mib[2] = TEST_DYNAMIC; + i = 0; + oldlen = sizeof(i); + if (sysctl(mib, 3, &i, &oldlen, NULL, 0) != 0) e(0); + if (oldlen != sizeof(i)) e(0); + if (i != 777) e(0); + + /* For now, we assume that basic node destruction works. */ + if (destroy_node(mib, 2, TEST_DYNAMIC) != 0) e(0); + + /* Try some variants of invalid new node data. */ + mib[2] = CTL_CREATE; + memcpy(&scn, &tmpscn, sizeof(scn)); + if (sysctl(mib, 3, NULL, NULL, NULL, 0) != -1) e(0); + if (errno != EINVAL) e(0); + if (sysctl(mib, 3, NULL, NULL, &scn, sizeof(scn) - 1) != -1) e(0); + if (errno != EINVAL) e(0); + if (sysctl(mib, 3, NULL, NULL, &scn, sizeof(scn) + 1) != -1) e(0); + if (errno != EINVAL) e(0); + if (sysctl(mib, 3, NULL, NULL, bad_ptr, sizeof(scn)) != -1) e(0); + if (errno != EFAULT) e(0); + + /* Try with an invalid flags field. */ + scn.sysctl_flags = + (scn.sysctl_flags & ~SYSCTL_VERS_MASK) | SYSCTL_VERS_0; + if (sysctl(mib, 3, NULL, NULL, &scn, sizeof(scn)) != -1) e(0); + if (errno != EINVAL) e(0); + + scn.sysctl_flags &= ~SYSCTL_TYPEMASK; /* type 0 does not exist */ + if (sysctl(mib, 3, NULL, NULL, &scn, sizeof(scn)) != -1) e(0); + if (errno != EINVAL) e(0); + + for (i = 0; i < __arraycount(badflags); i++) { + memcpy(&scn, &tmpscn, sizeof(scn)); + scn.sysctl_flags |= badflags[i]; + if (sysctl(mib, 3, NULL, NULL, &scn, sizeof(scn)) != -1) e(i); + if (errno != EINVAL) e(i); + } + + /* Try successful creation (and destruction) once more. */ + memcpy(&scn, &tmpscn, sizeof(scn)); + if (sysctl(mib, 3, NULL, NULL, &scn, sizeof(scn)) != 0) e(0); + + if (destroy_node(mib, 2, TEST_DYNAMIC) != 0) e(0); + + /* Try a combination of most valid flags. */ + memcpy(&scn, &tmpscn, sizeof(scn)); + scn.sysctl_flags &= ~CTLFLAG_READONLY; /* noop */ + scn.sysctl_flags |= CTLFLAG_READWRITE | CTLFLAG_ANYWRITE | + CTLFLAG_PRIVATE | CTLFLAG_HEX | CTLFLAG_HIDDEN | CTLFLAG_UNSIGNED; + if (sysctl(mib, 3, NULL, NULL, &scn, sizeof(scn)) != 0) e(0); + + if (destroy_node(mib, 2, TEST_DYNAMIC) != 0) e(0); + + /* Try invalid integer sizes. We will get to other types in a bit. */ + for (i = 0; i < __arraycount(badintsizes); i++) { + memcpy(&scn, &tmpscn, sizeof(scn)); + scn.sysctl_size = badintsizes[i]; + if (sysctl(mib, 3, NULL, NULL, &scn, sizeof(scn)) != -1) e(i); + if (errno != EINVAL) e(i); + } + + /* + * For the value, we can supply IMMEDIATE, OWNDATA, or neither. For + * IMMEDIATE, the integer value is taken directly from sysctl_idata. + * If OWNDATA is set, sysctl_data may be set, in which case the integer + * value is copied in from there. If sysctl_data is NULL, the integer + * is initalized to zero. If neither flag is set, sysctl_data must be + * NULL, since we do not support kernel addresses, and the integer will + * similarly be initialized to zero. If both flags are set, the call + * fails with EINVAL. + */ + memcpy(&scn, &tmpscn, sizeof(scn)); + scn.sysctl_flags |= CTLFLAG_OWNDATA; /* both flags are now set */ + if (sysctl(mib, 3, NULL, NULL, &scn, sizeof(scn)) != -1) e(0); + if (errno != EINVAL) e(0); + + scn.sysctl_flags &= ~(CTLFLAG_IMMEDIATE | CTLFLAG_OWNDATA); + scn.sysctl_data = &i; + if (sysctl(mib, 3, NULL, NULL, &scn, sizeof(scn)) != -1) e(0); + if (errno != EINVAL) e(0); + + scn.sysctl_data = NULL; + oldlen = sizeof(newscn); + if (sysctl(mib, 3, &newscn, &oldlen, &scn, sizeof(scn)) != 0) e(0); + if (oldlen != sizeof(newscn)) e(0); + if (newscn.sysctl_flags & CTLFLAG_IMMEDIATE) e(0); + if (!(newscn.sysctl_flags & CTLFLAG_OWNDATA)) e(0); /* auto-set */ + if (newscn.sysctl_idata != 0) e(0); + + mib[2] = TEST_DYNAMIC; + oldlen = sizeof(i); + if (sysctl(mib, 3, &i, &oldlen, NULL, 0) != 0) e(0); + if (oldlen != sizeof(i)) e(0); + if (i != 0) e(0); + + if (destroy_node(mib, 2, TEST_DYNAMIC) != 0) e(0); + + mib[2] = CTL_CREATE; + scn.sysctl_flags |= CTLFLAG_OWNDATA; + scn.sysctl_data = NULL; + if (sysctl(mib, 3, NULL, NULL, &scn, sizeof(scn)) != 0) e(0); + + mib[2] = TEST_DYNAMIC; + i = -1; + oldlen = sizeof(i); + if (sysctl(mib, 3, &i, &oldlen, NULL, 0) != 0) e(0); + if (oldlen != sizeof(i)) e(0); + if (i != 0) e(0); + + if (destroy_node(mib, 2, TEST_DYNAMIC) != 0) e(0); + + mib[2] = CTL_CREATE; + scn.sysctl_data = bad_ptr; + if (sysctl(mib, 3, NULL, NULL, &scn, sizeof(scn)) != -1) e(0); + if (errno != EFAULT) e(0); + + i = 999; + scn.sysctl_data = (void *)&i; + oldlen = sizeof(newscn); + if (sysctl(mib, 3, &newscn, &oldlen, &scn, sizeof(scn)) != 0) e(0); + if (oldlen != sizeof(newscn)) e(0); + if ((newscn.sysctl_flags & (CTLFLAG_IMMEDIATE | CTLFLAG_OWNDATA)) != + CTLFLAG_OWNDATA) e(0); + if (newscn.sysctl_idata != 0) e(0); + + mib[2] = TEST_DYNAMIC; + oldlen = sizeof(i); + if (sysctl(mib, 3, &i, &oldlen, NULL, 0) != 0) e(0); + if (oldlen != sizeof(i)) e(0); + if (i != 999) e(0); + + if (destroy_node(mib, 2, TEST_DYNAMIC) != 0) e(0); + + /* The user may never supply a function pointer or a parent. */ + mib[2] = CTL_CREATE; + memcpy(&scn, &tmpscn, sizeof(scn)); + scn.sysctl_func = (sysctlfn)test87c; + if (sysctl(mib, 3, NULL, NULL, &scn, sizeof(scn)) != -1) e(0); + if (errno != EINVAL) e(0); + + memcpy(&scn, &tmpscn, sizeof(scn)); + scn.sysctl_parent = &scn; + if (sysctl(mib, 3, NULL, NULL, &scn, sizeof(scn)) != -1) e(0); + if (errno != EINVAL) e(0); + + /* Test some good and bad node names. */ + for (i = 0; i < __arraycount(goodnames); i++) { + memcpy(&scn, &tmpscn, sizeof(scn)); + len = strlen(goodnames[i]); + memcpy(scn.sysctl_name, goodnames[i], len); + memset(&scn.sysctl_name[len], 0, SYSCTL_NAMELEN - len); + if (sysctl(mib, 3, NULL, NULL, &scn, sizeof(scn)) != 0) e(i); + + if (destroy_node(mib, 2, TEST_DYNAMIC) != 0) e(i); + } + + for (i = 0; i < __arraycount(badnames); i++) { + memcpy(&scn, &tmpscn, sizeof(scn)); + len = strlen(badnames[i]); + memcpy(scn.sysctl_name, badnames[i], len); + memset(&scn.sysctl_name[len], 0, SYSCTL_NAMELEN - len); + if (sysctl(mib, 3, NULL, NULL, &scn, sizeof(scn)) != -1) e(i); + if (errno != EINVAL) e(i); + } + + /* + * Check for ID and name conflicts with existing nodes, starting with + * the basics. + */ + memcpy(&scn, &tmpscn, sizeof(scn)); + if (sysctl(mib, 3, NULL, NULL, &scn, sizeof(scn)) != 0) e(0); + + if (sysctl(mib, 3, NULL, NULL, &scn, sizeof(scn)) != -1) e(0); + if (errno != EEXIST) e(0); + + oldlen = sizeof(oldscn); + if (sysctl(mib, 3, &oldscn, &oldlen, &scn, sizeof(scn)) != -1) e(0); + if (errno != EEXIST) e(0); + if (oldlen != sizeof(oldscn)) e(0); + if (oldscn.sysctl_ver == 0) e(0); + oldscn.sysctl_ver = 0; + if (memcmp(&oldscn, &tmpscn, sizeof(oldscn))) e(0); + + oldlen = sizeof(oldscn) - 1; + if (sysctl(mib, 3, &oldscn, &oldlen, &scn, sizeof(scn)) != -1) e(0); + if (errno != EEXIST) e(0); /* ..we should not get ENOMEM now */ + if (oldlen != sizeof(oldscn)) e(0); + + oldlen = sizeof(oldscn); + if (sysctl(mib, 3, bad_ptr, &oldlen, &scn, sizeof(scn)) != -1) e(0); + if (errno != EEXIST) e(0); /* ..we should not get EFAULT now */ + if (oldlen != 0) e(0); /* this is arguably an implementation detail */ + + if (destroy_node(mib, 2, TEST_DYNAMIC) != 0) e(0); + + /* Test ID and name conflicts against static nodes. */ + if (create_node(mib, 2, &tmpscn, TEST_INT, "dynamic", TEST_INT, + "int") != -1) e(0); + if (create_node(mib, 2, &tmpscn, TEST_SECRET, "dynamic", TEST_SECRET, + "secret") != -1) e(0); + if (create_node(mib, 2, &tmpscn, TEST_DYNAMIC, "quad", TEST_QUAD, + "quad") != -1) e(0); + + if (create_node(mib, 2, &tmpscn, TEST_DYNAMIC, "dynamic", -1, + NULL) != TEST_DYNAMIC) e(0); + if (destroy_node(mib, 2, TEST_DYNAMIC) != 0) e(0); + + /* Test unique ID generation and LL back insertion. */ + if ((id[0] = create_node(mib, 2, &tmpscn, CTL_CREATE, "id0", -1, + NULL)) == -1) e(0); + if ((id[1] = create_node(mib, 2, &tmpscn, CTL_CREATE, "id1", -1, + NULL)) == -1) e(0); + if ((id[2] = create_node(mib, 2, &tmpscn, CTL_CREATE, "id2", -1, + NULL)) == -1) e(0); + if (id[0] < CREATE_BASE || id[1] < CREATE_BASE || id[2] < CREATE_BASE) + e(0); + if (id[0] == id[1] || id[1] == id[2] || id[0] == id[2]) e(0); + + if (destroy_node(mib, 2, id[1]) != 0) e(0); + + /* Test ID and name conflicts against dynamic nodes. */ + if (create_node(mib, 2, &tmpscn, id[0], "id1", id[0], + "id0") != -1) e(0); + if (create_node(mib, 2, &tmpscn, id[2], "id1", id[2], + "id2") != -1) e(0); + if (create_node(mib, 2, &tmpscn, id[1], "id0", id[0], + "id0") != -1) e(0); + if (create_node(mib, 2, &tmpscn, id[1], "id2", id[2], + "id2") != -1) e(0); + + /* Test name conflicts before and after LL insertion point. */ + if (create_node(mib, 2, &tmpscn, CTL_CREATE, "id0", id[0], + "id0") != -1) e(0); + if (create_node(mib, 2, &tmpscn, CTL_CREATE, "id2", id[2], + "id2") != -1) e(0); + + /* Test recreation by ID and LL middle insertion. */ + if (create_node(mib, 2, &tmpscn, id[1], "id1", -1, NULL) == -1) e(0); + if (destroy_node(mib, 2, id[1]) != 0) e(0); + + /* Test dynamic recreation and more LL middle insertion. */ + if ((id[1] = create_node(mib, 2, &tmpscn, CTL_CREATE, "id1", -1, + NULL)) == -1) e(0); + if (id[1] < CREATE_BASE) e(0); + if (id[1] == id[0] || id[1] == id[2]) e(0); + + /* Test LL front insertion. */ + if (create_node(mib, 2, &tmpscn, TEST_DYNAMIC, "dynamic", -1, + NULL) == -1) e(0); + + /* Ensure that all dynamic nodes show up in a query. */ + mib[2] = CTL_QUERY; + oldlen = sizeof(scnset); + memset(seen, 0, sizeof(seen)); + memset(scnset, 0, sizeof(scnset)); + if (sysctl(mib, 3, scnset, &oldlen, NULL, 0) != 0) e(0); + if (oldlen % sizeof(scn)) e(0); + for (i = 0; (unsigned int)i < oldlen / sizeof(scn); i++) { + if (scnset[i].sysctl_num == TEST_INT) { + if (strcmp(scnset[i].sysctl_name, "int")) e(0); + seen[0]++; + } else if (scnset[i].sysctl_num == TEST_DYNAMIC) { + if (strcmp(scnset[i].sysctl_name, "dynamic")) e(0); + seen[1]++; + } else if (scnset[i].sysctl_num == id[0]) { + if (strcmp(scnset[i].sysctl_name, "id0")) e(0); + seen[2]++; + } else if (scnset[i].sysctl_num == id[1]) { + if (strcmp(scnset[i].sysctl_name, "id1")) e(0); + seen[3]++; + } else if (scnset[i].sysctl_num == id[2]) { + if (strcmp(scnset[i].sysctl_name, "id2")) e(0); + seen[4]++; + } + } + for (i = 0; i < 5; i++) + if (seen[i] != 1) e(i); + + /* Compare the parent's statistics with those obtained earlier. */ + if (query_node(mib, 1, MINIX_TEST, &scn) != 0) e(0); + if (scn.sysctl_clen != pscn.sysctl_clen + 4) e(0); + if (scn.sysctl_csize != pscn.sysctl_csize + 4) e(0); + + /* Clean up. */ + if (destroy_node(mib, 2, id[0]) != 0) e(0); + if (destroy_node(mib, 2, id[1]) != 0) e(0); + if (destroy_node(mib, 2, id[2]) != 0) e(0); + if (destroy_node(mib, 2, TEST_DYNAMIC) != 0) e(0); + + /* Copy-out errors should not result in the node not being created. */ + mib[2] = CTL_CREATE; + memcpy(&scn, &tmpscn, sizeof(scn)); + oldlen = sizeof(newscn) - 1; + if (sysctl(mib, 3, &newscn, &oldlen, &scn, sizeof(scn)) != -1) e(0); + if (errno != ENOMEM) e(0); + if (oldlen != sizeof(newscn)) e(0); + + if (destroy_node(mib, 2, TEST_DYNAMIC) != 0) e(0); + + oldlen = sizeof(newscn); + if (sysctl(mib, 3, bad_ptr, &oldlen, &scn, sizeof(scn)) != -1) e(0); + if (errno != EFAULT) e(0); + + if (destroy_node(mib, 2, TEST_DYNAMIC) != 0) e(0); + + oldlen = sizeof(newscn) + 1; + if (sysctl(mib, 3, &newscn, &oldlen, &scn, sizeof(scn)) != 0) e(0); + if (oldlen != sizeof(newscn)) e(0); + + if (destroy_node(mib, 2, TEST_DYNAMIC) != 0) e(0); + + /* + * Now that we are done with the integer template, try other data + * types, starting with booleans. A big part of these tests is that + * the creation results in a usable node, regardless of the way its + * contents were initialized. + */ + tmpscn.sysctl_flags = + SYSCTL_VERSION | CTLFLAG_READWRITE | CTLTYPE_BOOL; + tmpscn.sysctl_size = sizeof(b); + tmpscn.sysctl_data = NULL; + + mib[2] = CTL_CREATE; + memcpy(&scn, &tmpscn, sizeof(scn)); + if (sysctl(mib, 3, NULL, NULL, &scn, sizeof(scn)) != 0) e(0); + + mib[2] = TEST_DYNAMIC; + oldlen = sizeof(b); + if (sysctl(mib, 3, &b, &oldlen, NULL, 0) != 0) e(0); + if (oldlen != sizeof(b)) e(0); + if (b != false) e(0); + + b = true; + if (sysctl(mib, 3, NULL, NULL, &b, sizeof(b)) != 0) e(0); + + oldlen = sizeof(b); + b = false; + if (sysctl(mib, 3, &b, &oldlen, NULL, 0) != 0) e(0); + if (oldlen != sizeof(b)) e(0); + if (b != true) e(0); + + if (destroy_node(mib, 2, TEST_DYNAMIC) != 0) e(0); + + mib[2] = CTL_CREATE; + memcpy(&scn, &tmpscn, sizeof(scn)); + scn.sysctl_flags |= CTLFLAG_IMMEDIATE; + scn.sysctl_bdata = true; + if (sysctl(mib, 3, NULL, NULL, &scn, sizeof(scn)) != 0) e(0); + + mib[2] = TEST_DYNAMIC; + oldlen = sizeof(b); + if (sysctl(mib, 3, &b, &oldlen, NULL, 0) != 0) e(0); + if (oldlen != sizeof(b)) e(0); + if (b != true) e(0); + + if (destroy_node(mib, 2, TEST_DYNAMIC) != 0) e(0); + + mib[2] = CTL_CREATE; + scn.sysctl_bdata = false; + if (sysctl(mib, 3, NULL, NULL, &scn, sizeof(scn)) != 0) e(0); + + mib[2] = TEST_DYNAMIC; + oldlen = sizeof(b); + if (sysctl(mib, 3, &b, &oldlen, NULL, 0) != 0) e(0); + if (oldlen != sizeof(b)) e(0); + if (b != false) e(0); + + if (destroy_node(mib, 2, TEST_DYNAMIC) != 0) e(0); + + mib[2] = CTL_CREATE; + memcpy(&scn, &tmpscn, sizeof(scn)); + scn.sysctl_data = &b; + if (sysctl(mib, 3, NULL, NULL, &scn, sizeof(scn)) != -1) e(0); + if (errno != EINVAL) e(0); + + scn.sysctl_flags |= CTLFLAG_OWNDATA; + scn.sysctl_size++; + if (sysctl(mib, 3, NULL, NULL, &scn, sizeof(scn)) != -1) e(0); + if (errno != EINVAL) e(0); + + scn.sysctl_size--; + scn.sysctl_data = bad_ptr; + if (sysctl(mib, 3, NULL, NULL, &scn, sizeof(scn)) != -1) e(0); + if (errno != EFAULT) e(0); + + b = true; + scn.sysctl_data = &b; + if (sysctl(mib, 3, NULL, NULL, &scn, sizeof(scn)) != 0) e(0); + + mib[2] = TEST_DYNAMIC; + oldlen = sizeof(b); + if (sysctl(mib, 3, &b, &oldlen, NULL, 0) != 0) e(0); + if (oldlen != sizeof(b)) e(0); + if (b != true) e(0); + + b = false; + oldlen = sizeof(b); + if (sysctl(mib, 3, &b, &oldlen, NULL, 0) != 0) e(0); + if (oldlen != sizeof(b)) e(0); + if (b != true) e(0); + + b = false; + if (sysctl(mib, 3, NULL, NULL, &b, sizeof(b)) != 0) e(0); + + oldlen = sizeof(b); + b = true; + if (sysctl(mib, 3, &b, &oldlen, NULL, 0) != 0) e(0); + if (oldlen != sizeof(b)) e(0); + if (b != false) e(0); + + if (destroy_node(mib, 2, TEST_DYNAMIC) != 0) e(0); + + /* Test quads next. */ + tmpscn.sysctl_flags = + SYSCTL_VERSION | CTLFLAG_READWRITE | CTLTYPE_QUAD; + tmpscn.sysctl_size = sizeof(q); + + mib[2] = CTL_CREATE; + memcpy(&scn, &tmpscn, sizeof(scn)); + if (sysctl(mib, 3, NULL, NULL, &scn, sizeof(scn)) != 0) e(0); + + mib[2] = TEST_DYNAMIC; + oldlen = sizeof(q); + if (sysctl(mib, 3, &q, &oldlen, NULL, 0) != 0) e(0); + if (oldlen != sizeof(q)) e(0); + if (q != 0) e(0); + + q = ~0ULL; + if (sysctl(mib, 3, NULL, NULL, &q, sizeof(q)) != 0) e(0); + + oldlen = sizeof(q); + q = 0; + if (sysctl(mib, 3, &q, &oldlen, NULL, 0) != 0) e(0); + if (oldlen != sizeof(q)) e(0); + if (q != ~0ULL) e(0); + + if (destroy_node(mib, 2, TEST_DYNAMIC) != 0) e(0); + + mib[2] = CTL_CREATE; + memcpy(&scn, &tmpscn, sizeof(scn)); + scn.sysctl_flags |= CTLFLAG_IMMEDIATE; + scn.sysctl_qdata = 1ULL << 48; + if (sysctl(mib, 3, NULL, NULL, &scn, sizeof(scn)) != 0) e(0); + + mib[2] = TEST_DYNAMIC; + oldlen = sizeof(q); + if (sysctl(mib, 3, &q, &oldlen, NULL, 0) != 0) e(0); + if (oldlen != sizeof(q)) e(0); + if (q != (1ULL << 48)) e(0); + + if (destroy_node(mib, 2, TEST_DYNAMIC) != 0) e(0); + + mib[2] = CTL_CREATE; + memcpy(&scn, &tmpscn, sizeof(scn)); + scn.sysctl_data = &q; + if (sysctl(mib, 3, NULL, NULL, &scn, sizeof(scn)) != -1) e(0); + if (errno != EINVAL) e(0); + + scn.sysctl_flags |= CTLFLAG_OWNDATA; + scn.sysctl_size <<= 1; + if (sysctl(mib, 3, NULL, NULL, &scn, sizeof(scn)) != -1) e(0); + if (errno != EINVAL) e(0); + + scn.sysctl_size >>= 1; + scn.sysctl_data = bad_ptr; + if (sysctl(mib, 3, NULL, NULL, &scn, sizeof(scn)) != -1) e(0); + if (errno != EFAULT) e(0); + + q = 123ULL << 31; + scn.sysctl_data = &q; + if (sysctl(mib, 3, NULL, NULL, &scn, sizeof(scn)) != 0) e(0); + + mib[2] = TEST_DYNAMIC; + oldlen = sizeof(q); + if (sysctl(mib, 3, &q, &oldlen, NULL, 0) != 0) e(0); + if (oldlen != sizeof(q)) e(0); + if (q != (123ULL << 31)) e(0); + + if (destroy_node(mib, 2, TEST_DYNAMIC) != 0) e(0); + + /* Test strings. */ + tmpscn.sysctl_flags = + SYSCTL_VERSION | CTLFLAG_READWRITE | CTLTYPE_STRING; + tmpscn.sysctl_size = 7; + + mib[2] = CTL_CREATE; + memcpy(&scn, &tmpscn, sizeof(scn)); + scn.sysctl_data = buf; + if (sysctl(mib, 3, NULL, NULL, &scn, sizeof(scn)) != -1) e(0); + if (errno != EINVAL) e(0); + + scn.sysctl_data = NULL; + if (sysctl(mib, 3, NULL, NULL, &scn, sizeof(scn)) != 0) e(0); + + mib[2] = TEST_DYNAMIC; + memset(buf, 0x7f, sizeof(buf)); + oldlen = sizeof(buf); + if (sysctl(mib, 3, buf, &oldlen, NULL, 0) != 0) e(0); + if (oldlen != 1) e(0); + if (buf[0] != '\0') e(0); + if (buf[1] != 0x7f) e(0); + + if (sysctl(mib, 3, NULL, NULL, "woobie!", 8) != -1) e(0); + if (errno != EINVAL) e(0); + if (sysctl(mib, 3, NULL, NULL, "woobie!", 7) != -1) e(0); + if (errno != EINVAL) e(0); + if (sysctl(mib, 3, NULL, NULL, "woobie", 7) != 0) e(0); + + memset(buf, 0x7f, sizeof(buf)); + oldlen = sizeof(buf); + if (sysctl(mib, 3, buf, &oldlen, NULL, 0) != 0) e(0); + if (oldlen != 7) e(0); + if (strcmp(buf, "woobie")) e(0); + if (buf[7] != 0x7f) e(0); + + if (destroy_node(mib, 2, TEST_DYNAMIC) != 0) e(0); + + mib[2] = CTL_CREATE; + memcpy(&scn, &tmpscn, sizeof(scn)); + scn.sysctl_flags |= CTLFLAG_IMMEDIATE; + if (sysctl(mib, 3, NULL, NULL, &scn, sizeof(scn)) != -1) e(0); + if (errno != EINVAL) e(0); + + memcpy(&scn, &tmpscn, sizeof(scn)); + scn.sysctl_size = 0; + if (sysctl(mib, 3, NULL, NULL, &scn, sizeof(scn)) != -1) e(0); + if (errno != EINVAL) e(0); + + scn.sysctl_data = buf; + if (sysctl(mib, 3, NULL, NULL, &scn, sizeof(scn)) != -1) e(0); + if (errno != EINVAL) e(0); + + memcpy(&scn, &tmpscn, sizeof(scn)); + scn.sysctl_size = SSIZE_MAX + 1; + if (sysctl(mib, 3, NULL, NULL, &scn, sizeof(scn)) != -1) e(0); + if (errno != EINVAL) e(0); + + memcpy(&scn, &tmpscn, sizeof(scn)); + scn.sysctl_flags |= CTLFLAG_OWNDATA; + scn.sysctl_data = bad_ptr; + if (sysctl(mib, 3, NULL, NULL, &scn, sizeof(scn)) != -1) e(0); + if (errno != EFAULT) e(0); + + memcpy(&scn, &tmpscn, sizeof(scn)); + scn.sysctl_flags |= CTLFLAG_OWNDATA; + scn.sysctl_data = "abc123?"; + if (sysctl(mib, 3, NULL, NULL, &scn, sizeof(scn)) != -1) e(0); + if (errno != EINVAL) e(0); + + scn.sysctl_data = "abc123"; + if (sysctl(mib, 3, NULL, NULL, &scn, sizeof(scn)) != 0) e(0); + + mib[2] = TEST_DYNAMIC; + memset(buf, 0x7f, sizeof(buf)); + oldlen = sizeof(buf); + if (sysctl(mib, 3, buf, &oldlen, NULL, 0) != 0) e(0); + if (oldlen != 7) e(0); + if (strcmp(buf, "abc123")) e(0); + if (buf[7] != 0x7f) e(0); + + if (sysctl(mib, 3, NULL, NULL, "", 1) != 0) e(0); + + memset(buf, 0x7f, sizeof(buf)); + oldlen = sizeof(buf); + if (sysctl(mib, 3, buf, &oldlen, NULL, 0) != 0) e(0); + if (oldlen != 1) e(0); + if (buf[0] != '\0') e(0); + if (buf[1] != 0x7f) e(0); + + if (destroy_node(mib, 2, TEST_DYNAMIC) != 0) e(0); + + mib[2] = CTL_CREATE; + scn.sysctl_data = ""; + if (sysctl(mib, 3, NULL, NULL, &scn, sizeof(scn)) != 0) e(0); + + mib[2] = TEST_DYNAMIC; + memset(buf, 0x7f, sizeof(buf)); + oldlen = sizeof(buf); + if (sysctl(mib, 3, buf, &oldlen, NULL, 0) != 0) e(0); + if (oldlen != 1) e(0); + if (buf[0] != '\0') e(0); + if (buf[7] != 0x7f) e(0); + + if (destroy_node(mib, 2, TEST_DYNAMIC) != 0) e(0); + + /* + * For strings, a zero node size means that the string length + * determines the buffer size. + */ + mib[2] = CTL_CREATE; + scn.sysctl_size = 0; + scn.sysctl_data = NULL; + if (sysctl(mib, 3, NULL, NULL, &scn, sizeof(scn)) != -1) e(0); + if (errno != EINVAL) e(0); + + scn.sysctl_data = bad_ptr; + if (sysctl(mib, 3, NULL, NULL, &scn, sizeof(scn)) != -1) e(0); + if (errno != EFAULT) e(0); + + scn.sysctl_data = "This is a string initializer."; /* size 29+1 */ + if (sysctl(mib, 3, NULL, NULL, &scn, sizeof(scn)) != 0) e(0); + + mib[2] = TEST_DYNAMIC; + memset(buf, 0x7f, sizeof(buf)); + oldlen = sizeof(buf); + if (sysctl(mib, 3, buf, &oldlen, NULL, 0) != 0) e(0); + if (oldlen != strlen(scn.sysctl_data) + 1) e(0); + if (buf[oldlen - 1] != '\0') e(0); + if (buf[oldlen] != 0x7f) e(0); + + if (query_node(mib, 2, TEST_DYNAMIC, &newscn) != 0) e(0); + if (newscn.sysctl_size != strlen(scn.sysctl_data) + 1) e(0); + + if (destroy_node(mib, 2, TEST_DYNAMIC) != 0) e(0); + + /* Test structs. */ + tmpscn.sysctl_flags = + SYSCTL_VERSION | CTLFLAG_READWRITE | CTLTYPE_STRUCT; + tmpscn.sysctl_size = 21; + + mib[2] = CTL_CREATE; + memcpy(&scn, &tmpscn, sizeof(scn)); + scn.sysctl_data = buf; + if (sysctl(mib, 3, NULL, NULL, &scn, sizeof(scn)) != -1) e(0); + if (errno != EINVAL) e(0); + + scn.sysctl_data = NULL; + if (sysctl(mib, 3, NULL, NULL, &scn, sizeof(scn)) != 0) e(0); + + mib[2] = TEST_DYNAMIC; + memset(buf, 0x7f, sizeof(buf)); + oldlen = sizeof(buf); + if (sysctl(mib, 3, buf, &oldlen, NULL, 0) != 0) e(0); + if (oldlen != 21) e(0); + for (i = 0; i < 21; i++) + if (buf[i] != 0) e(i); + if (buf[i] != 0x7f) e(0); + + memset(buf, 'x', 32); + if (sysctl(mib, 3, NULL, NULL, buf, 20) != -1) e(0); + if (errno != EINVAL) e(0); + if (sysctl(mib, 3, NULL, NULL, buf, 22) != -1) e(0); + if (errno != EINVAL) e(0); + if (sysctl(mib, 3, NULL, NULL, buf, 21) != 0) e(0); + + memset(buf, 0x7f, sizeof(buf)); + oldlen = sizeof(buf); + if (sysctl(mib, 3, buf, &oldlen, NULL, 0) != 0) e(0); + if (oldlen != 21) e(0); + for (i = 0; i < 21; i++) + if (buf[i] != 'x') e(i); + if (buf[i] != 0x7f) e(0); + + if (destroy_node(mib, 2, TEST_DYNAMIC) != 0) e(0); + + mib[2] = CTL_CREATE; + memcpy(&scn, &tmpscn, sizeof(scn)); + scn.sysctl_flags |= CTLFLAG_IMMEDIATE; + if (sysctl(mib, 3, NULL, NULL, &scn, sizeof(scn)) != -1) e(0); + if (errno != EINVAL) e(0); + + memcpy(&scn, &tmpscn, sizeof(scn)); + scn.sysctl_size = 0; + if (sysctl(mib, 3, NULL, NULL, &scn, sizeof(scn)) != -1) e(0); + if (errno != EINVAL) e(0); + + scn.sysctl_data = buf; + if (sysctl(mib, 3, NULL, NULL, &scn, sizeof(scn)) != -1) e(0); + if (errno != EINVAL) e(0); + + memcpy(&scn, &tmpscn, sizeof(scn)); + scn.sysctl_size = SSIZE_MAX + 1; + if (sysctl(mib, 3, NULL, NULL, &scn, sizeof(scn)) != -1) e(0); + if (errno != EINVAL) e(0); + + memcpy(&scn, &tmpscn, sizeof(scn)); + scn.sysctl_flags |= CTLFLAG_OWNDATA; + scn.sysctl_data = bad_ptr; + if (sysctl(mib, 3, NULL, NULL, &scn, sizeof(scn)) != -1) e(0); + if (errno != EFAULT) e(0); + + memcpy(&scn, &tmpscn, sizeof(scn)); + scn.sysctl_flags |= CTLFLAG_OWNDATA; + for (i = 0; i < sizeof(buf); i++) + buf[i] = i; + scn.sysctl_data = buf; + if (sysctl(mib, 3, NULL, NULL, &scn, sizeof(scn)) != 0) e(0); + + mib[2] = TEST_DYNAMIC; + memset(buf, 0x7f, sizeof(buf)); + oldlen = sizeof(buf); + if (sysctl(mib, 3, buf, &oldlen, NULL, 0) != 0) e(0); + if (oldlen != 21) e(0); + for (i = 0; i < 21; i++) + if (buf[i] != i) e(i); + if (buf[i] != 0x7f) e(0); + + if (destroy_node(mib, 2, TEST_DYNAMIC) != 0) e(0); + + /* Finally, test node-type nodes. */ + tmpscn.sysctl_flags = + SYSCTL_VERSION | CTLFLAG_READWRITE | CTLTYPE_NODE; + tmpscn.sysctl_size = 0; + + mib[2] = CTL_CREATE; + memcpy(&scn, &tmpscn, sizeof(scn)); + if (sysctl(mib, 3, NULL, NULL, &scn, sizeof(scn)) != 0) e(0); + + if (destroy_node(mib, 2, TEST_DYNAMIC) != 0) e(0); + + scn.sysctl_flags |= CTLFLAG_IMMEDIATE; + if (sysctl(mib, 3, NULL, NULL, &scn, sizeof(scn)) != -1) e(0); + if (errno != EINVAL) e(0); + + memcpy(&scn, &tmpscn, sizeof(scn)); + scn.sysctl_flags |= CTLFLAG_IMMEDIATE; + if (sysctl(mib, 3, NULL, NULL, &scn, sizeof(scn)) != -1) e(0); + if (errno != EINVAL) e(0); + + scn.sysctl_size = sizeof(scn); + if (sysctl(mib, 3, NULL, NULL, &scn, sizeof(scn)) != -1) e(0); + if (errno != EINVAL) e(0); + + scn.sysctl_flags &= ~CTLFLAG_IMMEDIATE; + if (sysctl(mib, 3, NULL, NULL, &scn, sizeof(scn)) != -1) e(0); + if (errno != EINVAL) e(0); + + scn.sysctl_flags |= CTLFLAG_OWNDATA; + if (sysctl(mib, 3, NULL, NULL, &scn, sizeof(scn)) != -1) e(0); + if (errno != EINVAL) e(0); + + memcpy(&scn, &tmpscn, sizeof(scn)); + scn.sysctl_csize = 8; + if (sysctl(mib, 3, NULL, NULL, &scn, sizeof(scn)) != -1) e(0); + if (errno != EINVAL) e(0); + + memcpy(&scn, &tmpscn, sizeof(scn)); + scn.sysctl_clen = 1; + if (sysctl(mib, 3, NULL, NULL, &scn, sizeof(scn)) != -1) e(0); + if (errno != EINVAL) e(0); + + memcpy(&scn, &tmpscn, sizeof(scn)); + scn.sysctl_child = &scn; + if (sysctl(mib, 3, NULL, NULL, &scn, sizeof(scn)) != -1) e(0); + if (errno != EINVAL) e(0); + + memcpy(&scn, &tmpscn, sizeof(scn)); + scn.sysctl_parent = &scn; + if (sysctl(mib, 3, NULL, NULL, &scn, sizeof(scn)) != -1) e(0); + if (errno != EINVAL) e(0); + + memcpy(&scn, &tmpscn, sizeof(scn)); + scn.sysctl_func = (sysctlfn)test87c; + if (sysctl(mib, 3, NULL, NULL, &scn, sizeof(scn)) != -1) e(0); + if (errno != EINVAL) e(0); + + memcpy(&scn, &tmpscn, sizeof(scn)); + if (sysctl(mib, 3, NULL, NULL, &scn, sizeof(scn)) != 0) e(0); + + if (query_node(mib, 2, TEST_DYNAMIC, &scn) != 0) e(0); + if (scn.sysctl_csize != 0) e(0); + if (scn.sysctl_clen != 0) e(0); + + mib[2] = TEST_DYNAMIC; + + for (i = 3; i < CTL_MAXNAME; i++) { + memcpy(&scn, &tmpscn, sizeof(scn)); + if (i % 2) + scn.sysctl_num = i - 3; + else + scn.sysctl_num = CTL_CREATE; + /* + * Test both names with different length (depthN vs depthNN) + * and cross-directory name duplicates (depth7.depth7). + */ + snprintf(scn.sysctl_name, sizeof(scn.sysctl_name), "depth%u", + 7 + i / 2); + mib[i] = CTL_CREATE; + + oldlen = sizeof(newscn); + if (sysctl(mib, i + 1, &newscn, &oldlen, &scn, + sizeof(scn)) != 0) e(0); + mib[i] = newscn.sysctl_num; + } + + id[0] = mib[i - 1]; + mib[i - 1] = CTL_CREATE; + memset(&scn, 0, sizeof(scn)); + scn.sysctl_flags = SYSCTL_VERSION | CTLFLAG_READONLY | + CTLFLAG_OWNDATA | CTLTYPE_STRING; + scn.sysctl_num = id[0] + 1; + scn.sysctl_data = "bar"; + scn.sysctl_size = strlen(scn.sysctl_data) + 1; + strlcpy(scn.sysctl_name, "foo", sizeof(scn.sysctl_name)); + if (sysctl(mib, i, NULL, NULL, &scn, sizeof(scn)) != 0) e(0); + mib[i - 1] = id[0] + 1; + + oldlen = sizeof(buf); + if (sysctl(mib, i, buf, &oldlen, NULL, 0) != 0) e(0); + if (oldlen != strlen(scn.sysctl_data) + 1) e(0); + if (strcmp(buf, scn.sysctl_data)) e(0); + + if (query_node(mib, i - 2, mib[i - 2], &scn) != 0) e(0); + if (scn.sysctl_csize != 2) e(0); + if (scn.sysctl_clen != 2) e(0); + + if (query_node(mib, 2, TEST_DYNAMIC, &scn) != 0) e(0); + if (scn.sysctl_csize != 1) e(0); + if (scn.sysctl_clen != 1) e(0); + + if (destroy_node(mib, i - 1, mib[i - 1]) != 0) e(0); + mib[i - 1]--; + + for (i--; i > 2; i--) + if (destroy_node(mib, i, mib[i]) != 0) e(0); + + if (query_node(mib, 2, TEST_DYNAMIC, &scn) != 0) e(0); + if (scn.sysctl_csize != 0) e(0); + if (scn.sysctl_clen != 0) e(0); + + if (destroy_node(mib, 2, TEST_DYNAMIC) != 0) e(0); + + /* + * Finally, ensure that unprivileged processes cannot create nodes, + * even in the most friendly place possible. + */ + mib[2] = CTL_CREATE; + memcpy(&scn, &tmpscn, sizeof(scn)); + scn.sysctl_flags |= CTLFLAG_ANYWRITE; + if (sysctl(mib, 3, NULL, NULL, &scn, sizeof(scn)) != 0) e(0); + + (void)test_nonroot(sub87c); + + if (destroy_node(mib, 2, TEST_DYNAMIC) != 0) e(0); + + /* + * Now that we are done, compare the parent's statistics with those + * obtained earlier once more. There must be no differences. + */ + if (query_node(mib, 1, MINIX_TEST, &scn) != 0) e(0); + if (scn.sysctl_clen != pscn.sysctl_clen) e(0); + if (scn.sysctl_csize != pscn.sysctl_csize) e(0); + + /* Do some more path-related error code tests unrelated to the rest. */ + memcpy(&scn, &tmpscn, sizeof(scn)); + mib[1] = INT_MAX; + if (create_node(mib, 2, &scn, TEST_DYNAMIC, "d", -1, NULL) != -1) e(0); + if (errno != ENOENT) e(0); + + mib[1] = MINIX_TEST; + mib[2] = TEST_INT; + if (create_node(mib, 3, &scn, TEST_DYNAMIC, "d", -1, NULL) != -1) e(0); + if (errno != ENOTDIR) e(0); + + mib[2] = TEST_BOOL; + if (create_node(mib, 3, &scn, TEST_DYNAMIC, "d", -1, NULL) != -1) e(0); + if (errno != ENOTDIR) e(0); + + mib[2] = CTL_CREATE; + if (create_node(mib, 3, &scn, TEST_DYNAMIC, "d", -1, NULL) != -1) e(0); + if (errno != EINVAL) e(0); + + /* Finally, try to create a node in a read-only directory node. */ + mib[2] = TEST_SECRET; + if (create_node(mib, 3, &scn, -1, "d", -1, NULL) != -1) e(0); + if (errno != EPERM) e(0); +} + +/* + * Test unprivileged node destruction. + */ +static void +sub87d(void) +{ + struct sysctlnode scn; + int mib[3]; + + mib[0] = CTL_MINIX; + mib[1] = MINIX_TEST; + mib[2] = CTL_DESTROY; + + memset(&scn, 0, sizeof(scn)); + scn.sysctl_flags = SYSCTL_VERSION; + scn.sysctl_num = TEST_ANYWRITE; + + if (sysctl(mib, 3, NULL, NULL, &scn, sizeof(scn)) != -1) e(0); + if (errno != EPERM) e(0); + + mib[0] = CTL_DESTROY; + scn.sysctl_num = CTL_MINIX; + if (sysctl(mib, 1, NULL, NULL, &scn, sizeof(scn)) != -1) e(0); + if (errno != EPERM) e(0); +} + +/* + * Test sysctl(2) node destruction. + */ +static void +test87d(void) +{ + struct sysctlnode scn, oldscn, newscn, tmpscn; + size_t oldlen; + char buf[16]; + int i, r, mib[4], id[15]; + + subtest = 3; + + mib[0] = CTL_MINIX; + mib[1] = MINIX_TEST; + (void)destroy_node(mib, 2, TEST_DYNAMIC); + + /* Start with the path-related error code tests this time. */ + mib[1] = INT_MAX; + if (destroy_node(mib, 2, TEST_DYNAMIC) != -1) e(0); + if (errno != ENOENT) e(0); + + mib[1] = MINIX_TEST; + mib[2] = TEST_INT; + if (destroy_node(mib, 3, TEST_DYNAMIC) != -1) e(0); + if (errno != ENOTDIR) e(0); + + mib[2] = TEST_BOOL; + if (destroy_node(mib, 3, TEST_DYNAMIC) != -1) e(0); + if (errno != ENOTDIR) e(0); + + mib[2] = CTL_DESTROY; + if (destroy_node(mib, 3, TEST_DYNAMIC) != -1) e(0); + if (errno != EINVAL) e(0); + + /* Actual API tests. */ + mib[1] = MINIX_TEST; + mib[2] = CTL_CREATE; + memset(&scn, 0, sizeof(scn)); + scn.sysctl_flags = SYSCTL_VERSION | CTLFLAG_IMMEDIATE | + CTLFLAG_READONLY | CTLTYPE_INT; + scn.sysctl_size = sizeof(int); + scn.sysctl_num = TEST_DYNAMIC; + scn.sysctl_idata = 31415926; + strlcpy(scn.sysctl_name, "dynamic", sizeof(scn.sysctl_name)); + if (sysctl(mib, 3, NULL, NULL, &scn, sizeof(scn)) != 0) e(0); + + memcpy(&tmpscn, &scn, sizeof(scn)); + + mib[2] = CTL_DESTROY; + if (sysctl(mib, 3, NULL, NULL, NULL, 0) != -1) e(0); + if (errno != EINVAL) e(0); + + if (sysctl(mib, 3, NULL, NULL, bad_ptr, sizeof(scn)) != -1) e(0); + if (errno != EFAULT) e(0); + + memset(&scn, 0, sizeof(scn)); + scn.sysctl_flags = SYSCTL_VERS_0; + scn.sysctl_num = TEST_DYNAMIC; + if (sysctl(mib, 3, NULL, NULL, &scn, sizeof(scn)) != -1) e(0); + if (errno != EINVAL) e(0); + + scn.sysctl_flags = SYSCTL_VERSION; + scn.sysctl_num = INT_MAX; /* anything not valid */ + oldlen = sizeof(scn); + if (sysctl(mib, 3, NULL, &oldlen, &scn, sizeof(scn)) != -1) e(0); + if (errno != ENOENT) e(0); + if (oldlen != 0) e(0); + + scn.sysctl_num = TEST_PERM; + oldlen = sizeof(scn); + if (sysctl(mib, 3, &oldscn, &oldlen, &scn, sizeof(scn)) != -1) e(0); + if (errno != EPERM) e(0); + if (oldlen != 0) e(0); + + scn.sysctl_num = TEST_SECRET; + if (sysctl(mib, 3, NULL, NULL, &scn, sizeof(scn)) != -1) e(0); + if (errno != ENOTEMPTY) e(0); + + scn.sysctl_num = -1; + strlcpy(scn.sysctl_name, "dynamic", sizeof(scn.sysctl_name)); + if (sysctl(mib, 3, NULL, NULL, &scn, sizeof(scn)) != -1) e(0); + if (errno != ENOENT) e(0); + + scn.sysctl_num = TEST_DYNAMIC; + strlcpy(scn.sysctl_name, "dynami", sizeof(scn.sysctl_name)); + if (sysctl(mib, 3, NULL, NULL, &scn, sizeof(scn)) != -1) e(0); + if (errno != EINVAL) e(0); + + strlcpy(scn.sysctl_name, "dynamic2", sizeof(scn.sysctl_name)); + if (sysctl(mib, 3, NULL, NULL, &scn, sizeof(scn)) != -1) e(0); + if (errno != EINVAL) e(0); + + memset(scn.sysctl_name, 'd', sizeof(scn.sysctl_name)); + if (sysctl(mib, 3, NULL, NULL, &scn, sizeof(scn)) != -1) e(0); + if (errno != EINVAL) e(0); + + strlcpy(scn.sysctl_name, "dynamic", sizeof(scn.sysctl_name)); + oldlen = sizeof(oldscn); + if (sysctl(mib, 3, &oldscn, &oldlen, &scn, sizeof(scn)) != 0) e(0); + if (oldlen != sizeof(oldscn)) e(0); + if (oldscn.sysctl_ver == 0) e(0); + oldscn.sysctl_ver = 0; + if (memcmp(&oldscn, &tmpscn, sizeof(oldscn))) e(0); + + if (sysctl(mib, 3, NULL, NULL, &scn, sizeof(scn)) != -1) e(0); + if (errno != ENOENT) e(0); + + /* + * We already tested destruction of one static node, by destroying + * TEST_DYNAMIC on the first run. We now do a second deletion of a + * static node, TEST_DESTROY2, to test proper adjustment of parent + * stats. We do a third static node deletion (on TEST_DESTROY1) later, + * to see that static nodes with dynamic descriptions can be freed. + */ + if (query_node(mib, 1, MINIX_TEST, &oldscn) != 0) e(0); + + memset(&scn, 0, sizeof(scn)); + scn.sysctl_flags = SYSCTL_VERSION; + scn.sysctl_num = TEST_DESTROY2; + r = sysctl(mib, 3, NULL, NULL, &scn, sizeof(scn)); + if (r != 0 && r != -1) e(0); + if (r == -1 && errno != ENOENT) e(0); + + if (query_node(mib, 1, MINIX_TEST, &newscn) != 0) e(0); + + if (newscn.sysctl_csize != oldscn.sysctl_csize) e(0); + if (newscn.sysctl_clen != oldscn.sysctl_clen - !r) e(0); + + /* Try to destroy a (static) node in a read-only directory node. */ + mib[2] = TEST_SECRET; + if (destroy_node(mib, 3, SECRET_VALUE) != -1) e(0); + if (errno != EPERM) e(0); + + /* + * Errors during data copy-out of the destroyed node should not undo + * its destruction. + */ + mib[2] = CTL_CREATE; + memcpy(&scn, &tmpscn, sizeof(scn)); + if (sysctl(mib, 3, NULL, NULL, &scn, sizeof(scn)) != 0) e(0); + + mib[2] = TEST_DYNAMIC; + i = 0; + oldlen = sizeof(i); + if (sysctl(mib, 3, &i, &oldlen, NULL, 0) != 0) e(0); + if (oldlen != sizeof(i)) e(0); + if (i != 31415926) e(0); + + mib[2] = CTL_DESTROY; + oldlen = sizeof(scn); + if (sysctl(mib, 3, bad_ptr, &oldlen, &scn, sizeof(scn)) != -1) e(0); + if (errno != EFAULT) e(0); + + mib[2] = TEST_DYNAMIC; + i = 0; + oldlen = sizeof(i); + if (sysctl(mib, 3, &i, &oldlen, NULL, 0) != -1) e(0); + if (errno != ENOENT) e(0); + if (oldlen != 0) e(0); + if (i != 0) e(0); + + mib[2] = CTL_CREATE; + memcpy(&scn, &tmpscn, sizeof(scn)); + if (sysctl(mib, 3, NULL, NULL, &scn, sizeof(scn)) != 0) e(0); + + mib[2] = CTL_DESTROY; + oldlen = sizeof(scn) - 1; + if (sysctl(mib, 3, &scn, &oldlen, &scn, sizeof(scn)) != -1) e(0); + if (errno != ENOMEM) e(0); + + mib[2] = TEST_DYNAMIC; + oldlen = sizeof(i); + if (sysctl(mib, 3, &i, &oldlen, NULL, 0) != -1) e(0); + if (errno != ENOENT) e(0); + + /* + * Now create and destroy a whole bunch of nodes in a subtree, mostly + * test linked list manipulation, but also to ensure that a nonempty + * tree node cannot be destroyed. + */ + memset(&scn, 0, sizeof(scn)); + scn.sysctl_flags = SYSCTL_VERSION | CTLFLAG_READWRITE | CTLTYPE_NODE; + if (create_node(mib, 2, &scn, TEST_DYNAMIC, "dynamic", -1, NULL) == -1) + e(0); + + for (i = 0; i < 15; i++) { + snprintf(buf, sizeof(buf), "node%d", i); + if ((id[i] = create_node(mib, 3, &scn, -1, buf, -1, + NULL)) == -1) e(i); + + if (destroy_node(mib, 2, TEST_DYNAMIC) != -1) e(i); + if (errno != ENOTEMPTY) e(i); + } + + for (i = 0; i < 15; i += 2) + if (destroy_node(mib, 3, id[i]) != 0) e(i); + + if (destroy_node(mib, 2, TEST_DYNAMIC) != -1) e(0); + if (errno != ENOTEMPTY) e(0); + + for (i = 0; i < 15; i += 2) { + snprintf(buf, sizeof(buf), "node%d", i); + if ((id[i] = create_node(mib, 3, &scn, -1, buf, -1, + NULL)) == -1) e(i); + } + + for (i = 0; i < 3; i++) + if (destroy_node(mib, 3, id[i]) != 0) e(i); + + if (destroy_node(mib, 2, TEST_DYNAMIC) != -1) e(0); + if (errno != ENOTEMPTY) e(0); + + for (i = 12; i < 15; i++) + if (destroy_node(mib, 3, id[i]) != 0) e(i); + + if (destroy_node(mib, 2, TEST_DYNAMIC) != -1) e(0); + if (errno != ENOTEMPTY) e(0); + + for (i = 6; i < 9; i++) + if (destroy_node(mib, 3, id[i]) != 0) e(i); + + if (destroy_node(mib, 2, TEST_DYNAMIC) != -1) e(0); + if (errno != ENOTEMPTY) e(0); + + for (i = 3; i < 6; i++) + if (destroy_node(mib, 3, id[i]) != 0) e(i); + + if (destroy_node(mib, 2, TEST_DYNAMIC) != -1) e(0); + if (errno != ENOTEMPTY) e(0); + + for (i = 9; i < 12; i++) + if (destroy_node(mib, 3, id[i]) != 0) e(i); + + if (destroy_node(mib, 2, TEST_DYNAMIC) != 0) e(0); + + /* Finally, ensure that unprivileged users cannot destroy nodes. */ + (void)test_nonroot(sub87d); +} + +/* + * Get or a set the description for a particular node. Compare the results + * with the given description. Return 0 on success, or -1 on failure with + * errno set. + */ +static int +describe_node(const int * path, unsigned int pathlen, int id, + const char * desc, int set) +{ + char buf[256], *p; + struct sysctlnode scn; + struct sysctldesc *scd; + size_t oldlen; + int mib[CTL_MAXNAME]; + + if (pathlen >= CTL_MAXNAME) e(0); + memcpy(mib, path, sizeof(mib[0]) * pathlen); + mib[pathlen] = CTL_DESCRIBE; + + memset(&scn, 0, sizeof(scn)); + memset(buf, 0, sizeof(buf)); + oldlen = sizeof(buf); + scn.sysctl_flags = SYSCTL_VERSION; + scn.sysctl_num = id; + if (set) + scn.sysctl_desc = desc; + if (sysctl(mib, pathlen + 1, buf, &oldlen, &scn, sizeof(scn)) != 0) + return -1; + + scd = (struct sysctldesc *)buf; + if (scd->descr_num != id) e(0); + if (scd->descr_ver == 0) e(0); + if (scd->descr_str[scd->descr_len - 1] != '\0') e(0); + if (scd->descr_len != strlen(scd->descr_str) + 1) e(0); + if (strcmp(scd->descr_str, desc)) e(0); + if (oldlen != (size_t)((char *)NEXT_DESCR(scd) - buf)) e(0); + for (p = scd->descr_str + scd->descr_len; p != &buf[oldlen]; p++) + if (*p != '\0') e(0); + return 0; +} + +/* + * Test getting descriptions from an unprivileged process. + */ +static void +sub87e(void) +{ + static char buf[2048]; + char seen[32], *p; + struct sysctldesc *scd, *endscd; + size_t oldlen; + int mib[4]; + + mib[0] = CTL_MINIX; + mib[1] = MINIX_TEST; + mib[2] = CTL_DESCRIBE; + + memset(buf, 0, sizeof(buf)); + oldlen = sizeof(buf); + if (sysctl(mib, 3, buf, &oldlen, NULL, 0) != 0) e(0); + if (oldlen == 0) e(0); + + scd = (struct sysctldesc *)buf; + endscd = (struct sysctldesc *)&buf[oldlen]; + memset(seen, 0, sizeof(seen)); + + while (scd < endscd) { + if (scd->descr_num >= __arraycount(seen)) e(0); + if (seen[scd->descr_num]++) e(0); + + if (scd->descr_ver == 0) e(0); + if (scd->descr_str[scd->descr_len - 1] != '\0') e(0); + if (scd->descr_len != strlen(scd->descr_str) + 1) e(0); + + p = scd->descr_str + scd->descr_len; + while (p != (char *)NEXT_DESCR(scd)) + if (*p++ != '\0') e(0); + + scd = NEXT_DESCR(scd); + } + if (scd != endscd) e(0); + + if (!seen[TEST_INT]) e(0); + if (!seen[TEST_BOOL]) e(0); + if (!seen[TEST_QUAD]) e(0); + if (!seen[TEST_STRING]) e(0); + if (!seen[TEST_STRUCT]) e(0); + if (seen[TEST_PRIVATE]) e(0); + if (!seen[TEST_ANYWRITE]) e(0); + if (seen[TEST_SECRET]) e(0); + if (!seen[TEST_PERM]) e(0); + + if (describe_node(mib, 2, TEST_INT, "Value test field", 0) != 0) e(0); + if (describe_node(mib, 2, TEST_PRIVATE, "", 0) != -1) e(0); + if (errno != EPERM) e(0); + if (describe_node(mib, 2, TEST_SECRET, "", 0) != -1) e(0); + if (errno != EPERM) e(0); + if (describe_node(mib, 2, TEST_PERM, "", 0) != 0) e(0); + + mib[2] = TEST_SECRET; + mib[3] = CTL_DESCRIBE; + if (sysctl(mib, 3, NULL, NULL, NULL, 0) != -1) e(0); + if (errno != EPERM) e(0); + + if (describe_node(mib, 3, SECRET_VALUE, "", 0) != -1) e(0); + if (errno != EPERM) e(0); +} + +/* + * Test sysctl(2) node descriptions, part 1: getting descriptions. + */ +static void +test87e(void) +{ + static char buf[2048]; + char seen[32], *p; + struct sysctldesc *scd, *endscd; + struct sysctlnode scn; + size_t oldlen, len, sublen; + int mib[4]; + + subtest = 4; + + mib[0] = CTL_MINIX; + mib[1] = MINIX_TEST; + mib[2] = CTL_DESCRIBE; + memset(&scn, 0, sizeof(scn)); + + /* Start with tests for getting a description listing. */ + if (sysctl(mib, 3, NULL, NULL, NULL, 0) != 0) e(0); + + if (sysctl(mib, 3, NULL, &oldlen, NULL, 0) != 0) e(0); + if (oldlen == 0) e(0); + len = oldlen; + + memset(buf, 0, sizeof(buf)); + if (sysctl(mib, 3, buf, &oldlen, NULL, 0) != 0) e(0); + if (oldlen != len) e(0); + + scd = (struct sysctldesc *)buf; + endscd = (struct sysctldesc *)&buf[len]; + memset(seen, 0, sizeof(seen)); + + sublen = (size_t)((char *)NEXT_DESCR(scd) - buf); + + while (scd < endscd) { + if (scd->descr_num >= __arraycount(seen)) e(0); + if (seen[scd->descr_num]++) e(0); + + if (scd->descr_ver == 0) e(0); + if (scd->descr_str[scd->descr_len - 1] != '\0') e(0); + if (scd->descr_len != strlen(scd->descr_str) + 1) e(0); + + /* + * This is not supposed to be complete. We test different + * string lengths, private fields, and empty descriptions. + */ + switch (scd->descr_num) { + case TEST_INT: + if (strcmp(scd->descr_str, "Value test field")) e(0); + break; + case TEST_BOOL: + if (strcmp(scd->descr_str, "Boolean test field")) e(0); + break; + case TEST_QUAD: + if (strcmp(scd->descr_str, "Quad test field")) e(0); + break; + case TEST_STRING: + if (strcmp(scd->descr_str, "String test field")) e(0); + break; + case TEST_PRIVATE: + if (strcmp(scd->descr_str, "Private test field")) e(0); + break; + case TEST_SECRET: + if (strcmp(scd->descr_str, "Private subtree")) e(0); + break; + case TEST_PERM: + if (strcmp(scd->descr_str, "")) e(0); + break; + } + + /* + * If there are padding bytes, they must be zero, whether it is + * because we set them or the MIB service copied out zeroes. + */ + p = scd->descr_str + scd->descr_len; + while (p != (char *)NEXT_DESCR(scd)) + if (*p++ != '\0') e(0); + + scd = NEXT_DESCR(scd); + } + if (scd != endscd) e(0); + + if (!seen[TEST_INT]) e(0); + if (!seen[TEST_BOOL]) e(0); + if (!seen[TEST_QUAD]) e(0); + if (!seen[TEST_STRING]) e(0); + if (!seen[TEST_STRUCT]) e(0); + if (!seen[TEST_PRIVATE]) e(0); + if (!seen[TEST_ANYWRITE]) e(0); + if (!seen[TEST_SECRET]) e(0); + if (!seen[TEST_PERM]) e(0); + + memset(buf, 0, sizeof(buf)); + oldlen = sublen; + if (sysctl(mib, 3, buf, &oldlen, NULL, 0) != -1) e(0); + if (errno != ENOMEM) e(0); + + scd = (struct sysctldesc *)buf; + if (scd->descr_num != TEST_INT) e(0); + if (scd->descr_ver == 0) e(0); + if (scd->descr_str[scd->descr_len - 1] != '\0') e(0); + if (scd->descr_len != strlen(scd->descr_str) + 1) e(0); + if (strcmp(scd->descr_str, "Value test field")) e(0); + + /* Next up, tests for getting a particular node's description. */ + memset(buf, 0, sizeof(buf)); + oldlen = sizeof(buf); + if (sysctl(mib, 3, bad_ptr, &oldlen, NULL, 0) != -1) e(0); + if (errno != EFAULT) e(0); + + if (sysctl(mib, 3, NULL, NULL, bad_ptr, sizeof(scn) - 1) != -1) e(0); + if (errno != EINVAL) e(0); + if (sysctl(mib, 3, NULL, NULL, bad_ptr, sizeof(scn) + 1) != -1) e(0); + if (errno != EINVAL) e(0); + if (sysctl(mib, 3, NULL, NULL, bad_ptr, sizeof(scn)) != -1) e(0); + if (errno != EFAULT) e(0); + + memset(&scn, 0, sizeof(scn)); + scn.sysctl_flags = SYSCTL_VERS_0; + scn.sysctl_num = INT_MAX; + if (sysctl(mib, 3, NULL, NULL, &scn, sizeof(scn)) != -1) e(0); + if (errno != EINVAL) e(0); + + scn.sysctl_flags = SYSCTL_VERSION; + if (sysctl(mib, 3, NULL, NULL, &scn, sizeof(scn)) != -1) e(0); + if (errno != ENOENT) e(0); + + scn.sysctl_num = TEST_BOOL; + if (sysctl(mib, 3, NULL, NULL, &scn, sizeof(scn)) != 0) e(0); + + oldlen = sizeof(buf); + scn.sysctl_num = TEST_INT; + if (sysctl(mib, 3, bad_ptr, &oldlen, &scn, sizeof(scn)) != -1) e(0); + if (errno != EFAULT) e(0); + + oldlen = sublen - 1; + scn.sysctl_num = TEST_INT; + if (sysctl(mib, 3, buf, &oldlen, &scn, sizeof(scn)) != -1) e(0); + if (errno != ENOMEM) e(0); + if (oldlen != sublen) e(0); + + if (describe_node(mib, 2, TEST_INT, "Value test field", 0) != 0) e(0); + if (describe_node(mib, 2, TEST_QUAD, "Quad test field", 0) != 0) e(0); + if (describe_node(mib, 2, TEST_PRIVATE, "Private test field", + 0) != 0) e(0); + if (describe_node(mib, 2, TEST_SECRET, "Private subtree", + 0) != 0) e(0); + if (describe_node(mib, 2, TEST_PERM, "", 0) != 0) e(0); + + /* + * Make sure that unprivileged users cannot access privileged nodes' + * descriptions. It doesn't sound too bad to me if they could, but + * these are apparently the rules.. + */ + (void)test_nonroot(sub87e); + + /* Do some more path-related error code tests unrelated to the rest. */ + mib[1] = INT_MAX; + if (describe_node(mib, 2, TEST_DYNAMIC, "", 0) != -1) e(0); + if (errno != ENOENT) e(0); + + mib[1] = MINIX_TEST; + mib[2] = TEST_INT; + if (describe_node(mib, 3, TEST_DYNAMIC, "", 0) != -1) e(0); + if (errno != ENOTDIR) e(0); + + mib[2] = TEST_BOOL; + if (describe_node(mib, 3, TEST_DYNAMIC, "", 0) != -1) e(0); + if (errno != ENOTDIR) e(0); + + mib[2] = CTL_DESCRIBE; + if (describe_node(mib, 3, TEST_DYNAMIC, "", 0) != -1) e(0); + if (errno != EINVAL) e(0); +} + +/* + * Test setting descriptions from an unprivileged process. + */ +static void +sub87f(void) +{ + struct sysctlnode scn; + int mib[3]; + + mib[0] = CTL_MINIX; + mib[1] = MINIX_TEST; + mib[2] = CTL_DESCRIBE; + + memset(&scn, 0, sizeof(scn)); + scn.sysctl_flags = SYSCTL_VERSION; + scn.sysctl_num = TEST_DYNAMIC; + scn.sysctl_desc = "Description."; + + if (sysctl(mib, 3, NULL, NULL, &scn, sizeof(scn)) != -1) e(0); + if (errno != EPERM) e(0); +} + +/* + * Test sysctl(2) node descriptions, part 2: setting descriptions. + */ +static void +test87f(void) +{ + static char buf[2048]; + char seen, *p; + struct sysctlnode scn, tmpscn, scnset[3]; + struct sysctldesc *scd, *endscd, *scdset[2]; + size_t oldlen, len; + int i, r, mib[4], id[2]; + + subtest = 5; + + /* + * All tests that experiment with dynamic nodes must start with trying + * to destroy the TEST_DYNAMIC node first, as tests may be run + * individually, and this node exists as a static node after booting. + */ + mib[0] = CTL_MINIX; + mib[1] = MINIX_TEST; + (void)destroy_node(mib, 2, TEST_DYNAMIC); + + /* + * First try setting and retrieving the description of a dynamic node + * in a directory full of static nodes. + */ + mib[2] = CTL_CREATE; + memset(&scn, 0, sizeof(scn)); + scn.sysctl_flags = SYSCTL_VERSION | CTLFLAG_IMMEDIATE | + CTLFLAG_READONLY | CTLTYPE_INT; + scn.sysctl_size = sizeof(int); + scn.sysctl_num = TEST_DYNAMIC; + scn.sysctl_idata = 27182818; + strlcpy(scn.sysctl_name, "dynamic", sizeof(scn.sysctl_name)); + if (sysctl(mib, 3, NULL, NULL, &scn, sizeof(scn)) != 0) e(0); + + memcpy(&tmpscn, &scn, sizeof(tmpscn)); + + /* We should get an empty description for the node in a listing. */ + mib[2] = CTL_DESCRIBE; + memset(buf, 0, sizeof(buf)); + oldlen = sizeof(buf); + if (sysctl(mib, 3, buf, &oldlen, NULL, 0) != 0) e(0); + + scd = (struct sysctldesc *)buf; + endscd = (struct sysctldesc *)&buf[oldlen]; + seen = 0; + + while (scd < endscd) { + if (scd->descr_num == TEST_DYNAMIC) { + if (seen++) e(0); + + if (scd->descr_len != 1) e(0); + if (scd->descr_str[0] != '\0') e(0); + } + + if (scd->descr_ver == 0) e(0); + if (scd->descr_str[scd->descr_len - 1] != '\0') e(0); + if (scd->descr_len != strlen(scd->descr_str) + 1) e(0); + + p = scd->descr_str + scd->descr_len; + while (p != (char *)NEXT_DESCR(scd)) + if (*p++ != '\0') e(0); + + scd = NEXT_DESCR(scd); + } + if (scd != endscd) e(0); + + if (!seen) e(0); + + /* We should get an empty description quering the node directly. */ + if (describe_node(mib, 2, TEST_DYNAMIC, "", 0) != 0) e(0); + + /* Attempt to set a description with a bad description pointer. */ + if (describe_node(mib, 2, TEST_DYNAMIC, bad_ptr, 1) != -1) e(0); + if (errno != EFAULT) e(0); + + /* Attempt to set a description that is longer than allowed. */ + memset(buf, 'A', sizeof(buf) - 1); + buf[sizeof(buf) - 1] = '\0'; + if (describe_node(mib, 2, TEST_DYNAMIC, buf, 1) != -1) e(0); + if (errno != EINVAL) e(0); + + /* Now actually set a description. */ + if (describe_node(mib, 2, TEST_DYNAMIC, "Dynamic node", 1) != 0) e(0); + len = strlen("Dynamic node") + 1; + + /* We should get the new description for the node in a listing. */ + memset(buf, 0, sizeof(buf)); + oldlen = sizeof(buf); + if (sysctl(mib, 3, buf, &oldlen, NULL, 0) != 0) e(0); + + scd = (struct sysctldesc *)buf; + endscd = (struct sysctldesc *)&buf[oldlen]; + seen = 0; + + while (scd < endscd) { + if (scd->descr_num == TEST_DYNAMIC) { + if (seen++) e(0); + + if (scd->descr_len != len) e(0); + if (strcmp(scd->descr_str, "Dynamic node")) e(0); + } + + if (scd->descr_ver == 0) e(0); + if (scd->descr_str[scd->descr_len - 1] != '\0') e(0); + if (scd->descr_len != strlen(scd->descr_str) + 1) e(0); + + p = scd->descr_str + scd->descr_len; + while (p != (char *)NEXT_DESCR(scd)) + if (*p++ != '\0') e(0); + + scd = NEXT_DESCR(scd); + } + if (scd != endscd) e(0); + + if (!seen) e(0); + + /* We should get the new description quering the node directly. */ + if (describe_node(mib, 2, TEST_DYNAMIC, "Dynamic node", 0) != 0) e(0); + + mib[2] = CTL_DESCRIBE; + memset(&scn, 0, sizeof(scn)); + scn.sysctl_flags = SYSCTL_VERS_0; + scn.sysctl_num = TEST_INT; + scn.sysctl_desc = "Test description"; + if (sysctl(mib, 3, NULL, NULL, &scn, sizeof(scn)) != -1) e(0); + if (errno != EINVAL) e(0); + + /* It is not possible to replace an existing static description. */ + scn.sysctl_flags = SYSCTL_VERSION; + if (sysctl(mib, 3, NULL, NULL, &scn, sizeof(scn)) != -1) e(0); + if (errno != EPERM) e(0); + + /* Nonexistent nodes cannot be given a description. */ + scn.sysctl_num = INT_MAX; + if (sysctl(mib, 3, NULL, NULL, &scn, sizeof(scn)) != -1) e(0); + if (errno != ENOENT) e(0); + + /* It is not possible to replace an existing dynamic description. */ + scn.sysctl_num = TEST_DYNAMIC; + if (sysctl(mib, 3, NULL, NULL, &scn, sizeof(scn)) != -1) e(0); + if (errno != EPERM) e(0); + + /* It is not possible to set a description on a permanent node. */ + scn.sysctl_num = TEST_PERM; + if (sysctl(mib, 3, NULL, NULL, &scn, sizeof(scn)) != -1) e(0); + if (errno != EPERM) e(0); + + /* Verify that TEST_DYNAMIC now has CTLFLAG_OWNDESC set. */ + if (query_node(mib, 2, TEST_DYNAMIC, &scn) != 0) e(0); + if (!(scn.sysctl_flags & CTLFLAG_OWNDESC)) e(0); + + if (destroy_node(mib, 2, TEST_DYNAMIC) != 0) e(0); + + /* + * Set a description on a static node, ensure that CTLFLAG_OWNDESC is + * set, and then destroy the static node. This should still free the + * memory allocated for the description. We cannot test whether the + * memory is really freed, but at least we can trigger this case at + * all, and leave the rest up to memory checkers or whatever. Since we + * destroy the static node, we can not do this more than once, and thus + * we skip this test if the static node does not exist. + */ + r = describe_node(mib, 2, TEST_DESTROY1, "Destroy me", 1); + + if (r == -1 && errno != ENOENT) e(0); + else if (r == 0) { + if (query_node(mib, 2, TEST_DESTROY1, &scn) != 0) e(0); + if (!(scn.sysctl_flags & CTLFLAG_OWNDESC)) e(0); + + if (describe_node(mib, 2, TEST_DESTROY1, "Destroy me", 0) != 0) + e(0); + + if (destroy_node(mib, 2, TEST_DESTROY1) != 0) e(0); + } + + /* + * Test queries and description listings in subtrees. + */ + mib[2] = CTL_CREATE; + memset(&scn, 0, sizeof(scn)); + scn.sysctl_flags = SYSCTL_VERSION | CTLFLAG_READWRITE | CTLTYPE_NODE; + scn.sysctl_num = TEST_DYNAMIC; + strlcpy(scn.sysctl_name, "dynamic", sizeof(scn.sysctl_name)); + scn.sysctl_desc = "This will not be set."; + if (sysctl(mib, 3, NULL, NULL, &scn, sizeof(scn)) != 0) e(0); + + /* Setting sysctl_desc should have no effect during creation. */ + if (describe_node(mib, 2, TEST_DYNAMIC, "", 0) != 0) e(0); + + mib[2] = TEST_DYNAMIC; + id[0] = create_node(mib, 3, &tmpscn, CTL_CREATE, "NodeA", -1, NULL); + if (id[0] < 0) e(0); + id[1] = create_node(mib, 3, &tmpscn, CTL_CREATE, "NodeB", -1, NULL); + if (id[1] < 0) e(0); + if (id[0] == id[1]) e(0); + + mib[3] = CTL_QUERY; + oldlen = sizeof(scnset); + if (sysctl(mib, 4, scnset, &oldlen, NULL, 0) != 0) e(0); + if (oldlen != sizeof(scnset[0]) * 2) e(0); + i = (scnset[0].sysctl_num != id[0]); + if (scnset[i].sysctl_num != id[0]) e(0); + if (scnset[1 - i].sysctl_num != id[1]) e(0); + if (scnset[i].sysctl_flags & CTLFLAG_OWNDESC) e(0); + if (scnset[1 - i].sysctl_flags & CTLFLAG_OWNDESC) e(0); + + mib[3] = CTL_DESCRIBE; + memset(buf, 0, sizeof(buf)); + oldlen = sizeof(buf); + if (sysctl(mib, 4, buf, &oldlen, NULL, 0) != 0) e(0); + if (oldlen == 0) e(0); + + scdset[0] = (struct sysctldesc *)buf; + scdset[1] = NEXT_DESCR(scdset[0]); + if ((char *)NEXT_DESCR(scdset[1]) != &buf[oldlen]) e(0); + i = (scdset[0]->descr_num != id[0]); + if (scdset[i]->descr_num != id[0]) e(0); + if (scdset[i]->descr_ver == 0) e(0); + if (scdset[i]->descr_len != 1) e(0); + if (scdset[i]->descr_str[0] != '\0') e(0); + if (scdset[1 - i]->descr_num != id[1]) e(0); + if (scdset[1 - i]->descr_ver == 0) e(0); + if (scdset[1 - i]->descr_len != 1) e(0); + if (scdset[1 - i]->descr_str[0] != '\0') e(0); + + if (describe_node(mib, 3, id[0], "Description A", 1) != 0) e(0); + + mib[3] = CTL_QUERY; + oldlen = sizeof(scnset); + if (sysctl(mib, 4, scnset, &oldlen, NULL, 0) != 0) e(0); + if (oldlen != sizeof(scnset[0]) * 2) e(0); + i = (scnset[0].sysctl_num != id[0]); + if (scnset[i].sysctl_num != id[0]) e(0); + if (scnset[1 - i].sysctl_num != id[1]) e(0); + if (!(scnset[i].sysctl_flags & CTLFLAG_OWNDESC)) e(0); + if (scnset[1 - i].sysctl_flags & CTLFLAG_OWNDESC) e(0); + + mib[3] = CTL_DESCRIBE; + memset(buf, 0, sizeof(buf)); + oldlen = sizeof(buf); + if (sysctl(mib, 4, buf, &oldlen, NULL, 0) != 0) e(0); + if (oldlen == 0) e(0); + + scdset[0] = (struct sysctldesc *)buf; + scdset[1] = NEXT_DESCR(scdset[0]); + if ((char *)NEXT_DESCR(scdset[1]) != &buf[oldlen]) e(0); + i = (scdset[0]->descr_num != id[0]); + if (scdset[i]->descr_num != id[0]) e(0); + if (scdset[i]->descr_ver == 0) e(0); + if (strcmp(scdset[i]->descr_str, "Description A")) e(0); + if (scdset[i]->descr_len != strlen(scdset[i]->descr_str) + 1) e(0); + if (scdset[1 - i]->descr_num != id[1]) e(0); + if (scdset[1 - i]->descr_ver == 0) e(0); + if (scdset[1 - i]->descr_len != 1) e(0); + if (scdset[1 - i]->descr_str[0] != '\0') e(0); + + if (describe_node(mib, 3, id[1], "Description B", 1) != 0) e(0); + + mib[3] = CTL_QUERY; + oldlen = sizeof(scnset); + if (sysctl(mib, 4, scnset, &oldlen, NULL, 0) != 0) e(0); + if (oldlen != sizeof(scnset[0]) * 2) e(0); + i = (scnset[0].sysctl_num != id[0]); + if (scnset[i].sysctl_num != id[0]) e(0); + if (scnset[1 - i].sysctl_num != id[1]) e(0); + if (!(scnset[i].sysctl_flags & CTLFLAG_OWNDESC)) e(0); + if (!(scnset[1 - i].sysctl_flags & CTLFLAG_OWNDESC)) e(0); + + mib[3] = CTL_DESCRIBE; + memset(buf, 0, sizeof(buf)); + oldlen = sizeof(buf); + if (sysctl(mib, 4, buf, &oldlen, NULL, 0) != 0) e(0); + if (oldlen == 0) e(0); + + scdset[0] = (struct sysctldesc *)buf; + scdset[1] = NEXT_DESCR(scdset[0]); + if ((char *)NEXT_DESCR(scdset[1]) != &buf[oldlen]) e(0); + i = (scdset[0]->descr_num != id[0]); + if (scdset[i]->descr_num != id[0]) e(0); + if (scdset[i]->descr_ver == 0) e(0); + if (strcmp(scdset[i]->descr_str, "Description A")) e(0); + if (scdset[i]->descr_len != strlen(scdset[i]->descr_str) + 1) e(0); + if (scdset[1 - i]->descr_num != id[1]) e(0); + if (scdset[1 - i]->descr_ver == 0) e(0); + if (strcmp(scdset[1 - i]->descr_str, "Description B")) e(0); + if (scdset[1 - i]->descr_len != strlen(scdset[1 - i]->descr_str) + 1) + e(0); + + if (destroy_node(mib, 3, id[0]) != 0) e(0); + if (destroy_node(mib, 3, id[1]) != 0) e(0); + + if (destroy_node(mib, 2, TEST_DYNAMIC) != 0) e(0); + + /* + * Test that the resulting description is copied out after setting it, + * and that copy failures do not undo the description getting set. + */ + if (create_node(mib, 2, &tmpscn, TEST_DYNAMIC, "dynamic", -1, + NULL) == -1) e(0); + + mib[2] = CTL_DESCRIBE; + memset(&scn, 0, sizeof(scn)); + scn.sysctl_flags = SYSCTL_VERSION; + scn.sysctl_num = TEST_DYNAMIC; + scn.sysctl_desc = "Testing.."; + memset(buf, 0, sizeof(buf)); + oldlen = sizeof(buf); + if (sysctl(mib, 3, buf, &oldlen, &scn, sizeof(scn)) != 0) e(0); + if (oldlen == 0) e(0); + len = oldlen; + + scd = (struct sysctldesc *)buf; + if (scd->descr_str[scd->descr_len - 1] != '\0') e(0); + if (scd->descr_len != strlen(scn.sysctl_desc) + 1) e(0); + if (strcmp(scd->descr_str, scn.sysctl_desc)) e(0); + if (oldlen != (size_t)((char *)NEXT_DESCR(scd) - buf)) e(0); + p = scd->descr_str + scd->descr_len; + while (p != (char *)NEXT_DESCR(scd)) + if (*p++ != '\0') e(0); + + if (describe_node(mib, 2, TEST_DYNAMIC, "Testing..", 0) != 0) e(0); + + if (destroy_node(mib, 2, TEST_DYNAMIC) != 0) e(0); + + if (create_node(mib, 2, &tmpscn, TEST_DYNAMIC, "dynamic", -1, + NULL) == -1) e(0); + + memset(buf, 0, sizeof(buf)); + oldlen = len - 1; + if (sysctl(mib, 3, buf, &oldlen, &scn, sizeof(scn)) != -1) e(0); + if (errno != ENOMEM) e(0); + if (oldlen != len) e(0); + + if (describe_node(mib, 2, TEST_DYNAMIC, "Testing..", 0) != 0) e(0); + + if (destroy_node(mib, 2, TEST_DYNAMIC) != 0) e(0); + + if (create_node(mib, 2, &tmpscn, TEST_DYNAMIC, "dynamic", -1, + NULL) == -1) e(0); + + memset(buf, 0, sizeof(buf)); + oldlen = len; + if (sysctl(mib, 3, bad_ptr, &oldlen, &scn, sizeof(scn)) != -1) e(0); + if (errno != EFAULT) e(0); + + if (describe_node(mib, 2, TEST_DYNAMIC, "Testing..", 0) != 0) e(0); + + if (destroy_node(mib, 2, TEST_DYNAMIC) != 0) e(0); + + /* Finally, ensure that unprivileged users cannot set descriptions. */ + memcpy(&scn, &tmpscn, sizeof(scn)); + scn.sysctl_flags = SYSCTL_VERSION | CTLFLAG_IMMEDIATE | + CTLFLAG_READWRITE | CTLFLAG_ANYWRITE | CTLTYPE_INT; + if (create_node(mib, 2, &scn, TEST_DYNAMIC, "dynamic", -1, + NULL) == -1) e(0); + + (void)test_nonroot(sub87f); + + if (destroy_node(mib, 2, TEST_DYNAMIC) != 0) e(0); +} + +/* + * Set or test buffer contents. When setting, the buffer is filled with a + * sequence of bytes that is a) free of null characters and b) likely to cause + * detection of wrongly copied subsequences. When testing, for any size up to + * the size used to set the buffer contents, 0 is returned if the buffer + * contents match expectations, or -1 if they do not. + */ +static int +test_buf(unsigned char * buf, unsigned char c, size_t size, int set) +{ + int step; + + for (step = 1; size > 0; size--) { + if (set) + *buf++ = c; + else if (*buf++ != c) + return -1; + + c += step; + if (c == 0) { + if (++step == 256) + step = 1; + c += step; + } + } + + return 0; +} + +/* + * Test large data sizes from an unprivileged process. + */ +static void +sub87g(void) +{ + char *ptr; + size_t size, oldlen; + int id, mib[3]; + + size = getpagesize() * 3; + + if ((ptr = mmap(NULL, size, PROT_READ, MAP_ANON | MAP_PRIVATE, -1, + 0)) == MAP_FAILED) e(0); + memset(ptr, 0x2f, size); + + mib[0] = CTL_MINIX; + mib[1] = MINIX_TEST; + mib[2] = TEST_DYNAMIC; + oldlen = size - 2; + if (sysctl(mib, 3, ptr, &oldlen, NULL, 0) != 0) e(0); + if (oldlen != size - 2) e(0); + if (test_buf(ptr, 'D', size - 2, 0) != 0) e(0); + + /* + * Given the large data size, we currently expect this attempt to + * write to the structure to be blocked by the MIB service. + */ + if (sysctl(mib, 3, NULL, NULL, ptr, oldlen) != -1) e(0); + if (errno != EPERM) e(0); + + /* Get the ID of the second dynamic node. */ + mib[2] = TEST_ANYWRITE; + oldlen = sizeof(id); + if (sysctl(mib, 3, &id, &oldlen, NULL, 0) != 0) e(0); + if (oldlen != sizeof(id)) e(0); + if (id < 0) e(0); + + /* + * Test data size limits for strings as well, although here we can also + * ensure that we hit the right check by testing with a shorter string. + */ + mib[2] = id; + oldlen = size; + if (sysctl(mib, 3, ptr, &oldlen, NULL, 0) != 0) e(0); + if (oldlen != size) e(0); + if (test_buf(ptr, 'f', size - 1, 0) != 0) e(0); + if (ptr[size - 1] != '\0') e(0); + + test_buf(ptr, 'h', size - 1, 1); + if (sysctl(mib, 3, NULL, NULL, ptr, size) != -1) e(0); + if (errno != EPERM) e(0); + + if (sysctl(mib, 3, NULL, NULL, ptr, getpagesize() - 1) != 0) e(0); + + if (munmap(ptr, size) != 0) e(0); +} + +/* + * Test large data sizes and mid-data page faults. + */ +static void +test87g(void) +{ + struct sysctlnode scn, newscn; + char *ptr; + size_t pgsz, size, oldlen; + int id, mib[3]; + + subtest = 6; + + /* + * No need to go overboard with sizes here; it will just cause the MIB + * service's memory usage to grow - permanently. Three pages followed + * by an unmapped page is plenty for this test. + */ + pgsz = getpagesize(); + size = pgsz * 3; + + if ((ptr = mmap(NULL, size + pgsz, PROT_READ, MAP_ANON | MAP_PRIVATE, + -1, 0)) == MAP_FAILED) e(0); + if (munmap(ptr + size, pgsz) != 0) e(0); + + (void)destroy_node(mib, 2, TEST_DYNAMIC); + + /* Test string creation initializers with an accurate length. */ + mib[0] = CTL_MINIX; + mib[1] = MINIX_TEST; + mib[2] = CTL_CREATE; + memset(&scn, 0, sizeof(scn)); + scn.sysctl_flags = SYSCTL_VERSION | CTLFLAG_OWNDATA | + CTLFLAG_READWRITE | CTLTYPE_STRING; + scn.sysctl_num = TEST_DYNAMIC; + scn.sysctl_data = ptr; + scn.sysctl_size = size; + strlcpy(scn.sysctl_name, "dynamic", sizeof(scn.sysctl_name)); + test_buf(ptr, 'a', size, 1); + if (sysctl(mib, 3, NULL, NULL, &scn, sizeof(scn)) != -1) e(0); + if (errno != EINVAL) e(0); /* no null terminator */ + + scn.sysctl_size++; + if (sysctl(mib, 3, NULL, NULL, &scn, sizeof(scn)) != -1) e(0); + if (errno != EFAULT) e(0); + + scn.sysctl_size--; + ptr[size - 1] = '\0'; + if (sysctl(mib, 3, NULL, NULL, &scn, sizeof(scn)) != 0) e(0); + + mib[2] = TEST_DYNAMIC; + if (sysctl(mib, 3, NULL, &oldlen, NULL, 0) != 0) e(0); + if (oldlen != size) e(0); + + memset(ptr, 0, size); + if (sysctl(mib, 3, ptr, &oldlen, NULL, 0) != 0) e(0); + if (oldlen != size) e(0); + if (ptr[size - 1] != '\0') e(0); + if (test_buf(ptr, 'a', size - 1, 0) != 0) e(0); + + if (destroy_node(mib, 2, TEST_DYNAMIC) != 0) e(0); + + /* Test string creation initializers with no length. */ + mib[2] = CTL_CREATE; + scn.sysctl_size = 0; + test_buf(ptr, 'b', size, 1); + if (sysctl(mib, 3, NULL, NULL, &scn, sizeof(scn)) != -1) e(0); + if (errno != EFAULT) e(0); + + test_buf(ptr, 'b', size - 1, 1); + ptr[size - 1] = '\0'; + if (sysctl(mib, 3, NULL, NULL, &scn, sizeof(scn)) != 0) e(0); + + if (query_node(mib, 2, TEST_DYNAMIC, &newscn) != 0) e(0); + if (newscn.sysctl_size != size) e(0); + + mib[2] = TEST_DYNAMIC; + if (sysctl(mib, 3, NULL, &oldlen, NULL, 0) != 0) e(0); + if (oldlen != size) e(0); + + memset(ptr, 0x7e, size); + if (sysctl(mib, 3, ptr, &oldlen, NULL, 0) != 0) e(0); + if (oldlen != size) e(0); + if (ptr[size - 1] != '\0') e(0); + if (test_buf(ptr, 'b', size - 1, 0) != 0) e(0); + + if (destroy_node(mib, 2, TEST_DYNAMIC) != 0) e(0); + + /* + * Test string creation initializers with a length exceeding the string + * length. If the string is properly null terminated, this should not + * result in a fault. + */ + mib[2] = CTL_CREATE; + scn.sysctl_size = size; + scn.sysctl_data = &ptr[size - pgsz - 5]; + test_buf(&ptr[size - pgsz - 5], 'c', pgsz + 5, 1); + if (sysctl(mib, 3, NULL, NULL, &scn, sizeof(scn)) != -1) e(0); + if (errno != EFAULT) e(0); + + ptr[size - 1] = '\0'; + if (sysctl(mib, 3, NULL, NULL, &scn, sizeof(scn)) != 0) e(0); + + if (query_node(mib, 2, TEST_DYNAMIC, &newscn) != 0) e(0); + if (newscn.sysctl_size != size) e(0); + + mib[2] = TEST_DYNAMIC; + oldlen = size - pgsz - 6; + if (sysctl(mib, 3, ptr, &oldlen, NULL, 0) != 0) e(0); + if (oldlen != pgsz + 5) e(0); + /* We rely on only the actual string getting copied out here. */ + if (memcmp(ptr, &ptr[size - pgsz - 5], pgsz + 5)) e(0); + + if (destroy_node(mib, 2, TEST_DYNAMIC) != 0) e(0); + + /* Test structure creation initializers. */ + mib[2] = CTL_CREATE; + scn.sysctl_flags = SYSCTL_VERSION | CTLFLAG_OWNDATA | + CTLFLAG_ANYWRITE | CTLFLAG_READWRITE | CTLTYPE_STRUCT; + scn.sysctl_size = size - 2; + scn.sysctl_data = &ptr[3]; + if (sysctl(mib, 3, NULL, NULL, &scn, sizeof(scn)) != -1) e(0); + if (errno != EFAULT) e(0); + + scn.sysctl_data = &ptr[2]; + test_buf(&ptr[2], 'd', size - 2, 1); + if (sysctl(mib, 3, NULL, NULL, &scn, sizeof(scn)) != 0) e(0); + + mib[2] = TEST_DYNAMIC; + memset(ptr, 0x3b, size); + oldlen = size - 2; + if (sysctl(mib, 3, &ptr[3], &oldlen, NULL, 0) != -1) e(0); + if (errno != EFAULT) e(0); + oldlen = size - 2; + if (sysctl(mib, 3, &ptr[2], &oldlen, NULL, 0) != 0) e(0); + if (oldlen != size - 2) e(0); + if (test_buf(&ptr[2], 'd', size - 2, 0) != 0) e(0); + + /* + * Test setting new values. We already have a structure node, so let's + * start there. + */ + test_buf(&ptr[2], 'D', size - 2, 1); + if (sysctl(mib, 3, NULL, NULL, &ptr[3], size - 2) != -1) e(0); + if (errno != EFAULT) e(0); + + /* Did the mid-data fault cause a partial update? It better not. */ + memset(ptr, 0x4c, size); + oldlen = size - 2; + if (sysctl(mib, 3, ptr, &oldlen, NULL, 0) != 0) e(0); + if (oldlen != size - 2) e(0); + if (test_buf(ptr, 'd', size - 2, 0) != 0) e(0); + + test_buf(&ptr[2], 'D', size - 2, 1); + if (sysctl(mib, 3, NULL, NULL, &ptr[2], size - 2) != 0) e(0); + + memset(ptr, 0x5d, size); + oldlen = size - 2; + if (sysctl(mib, 3, ptr, &oldlen, NULL, 0) != 0) e(0); + if (oldlen != size - 2) e(0); + if (test_buf(ptr, 'D', size - 2, 0) != 0) e(0); + + /* + * We are going to reuse TEST_DYNAMIC for the non-root test later, so + * create a new node for string tests. + */ + mib[2] = CTL_CREATE; + memset(&scn, 0, sizeof(scn)); + scn.sysctl_flags = SYSCTL_VERSION | CTLFLAG_OWNDATA | + CTLFLAG_ANYWRITE | CTLFLAG_READWRITE | CTLTYPE_STRING; + scn.sysctl_num = CTL_CREATE; + scn.sysctl_size = size; + scn.sysctl_data = ptr; + test_buf(ptr, 'e', size - 1, 1); + ptr[size - 1] = '\0'; + strlcpy(scn.sysctl_name, "dynamic2", sizeof(scn.sysctl_name)); + oldlen = sizeof(newscn); + if (sysctl(mib, 3, &newscn, &oldlen, &scn, sizeof(scn)) != 0) e(0); + if (oldlen != sizeof(newscn)) e(0); + id = newscn.sysctl_num; + if (id < 0) e(0); + + /* + * Test setting a short but faulty string, ensuring that no partial + * update on the field contents takes place. + */ + mib[2] = id; + memcpy(&ptr[size - 3], "XYZ", 3); + if (sysctl(mib, 3, NULL, NULL, &ptr[size - 3], 4) != -1) e(0); + if (errno != EFAULT) e(0); + + oldlen = size; + if (sysctl(mib, 3, ptr, &oldlen, NULL, 0) != 0) e(0); + if (oldlen != size) e(0); + if (test_buf(ptr, 'e', size - 1, 0) != 0) e(0); + if (ptr[size - 1] != '\0') e(0); + + memcpy(&ptr[size - 3], "XYZ", 3); + if (sysctl(mib, 3, NULL, NULL, &ptr[size - 3], 3) != 0) e(0); + + oldlen = size; + if (sysctl(mib, 3, ptr, &oldlen, NULL, 0) != 0) e(0); + if (oldlen != 4) e(0); + if (strcmp(ptr, "XYZ")) e(0); + + test_buf(&ptr[1], 'f', size - 1, 1); + if (sysctl(mib, 3, NULL, NULL, &ptr[1], size - 1) != 0) e(0); + + test_buf(&ptr[1], 'G', size - 1, 1); + if (sysctl(mib, 3, NULL, NULL, &ptr[1], size) != -1) e(0); + if (errno != EFAULT) e(0); + + oldlen = size; + if (sysctl(mib, 3, ptr, &oldlen, NULL, 0) != 0) e(0); + if (oldlen != size) e(0); + if (test_buf(ptr, 'f', size - 1, 0) != 0) e(0); + if (ptr[size - 1] != '\0') e(0); + + /* + * Test descriptions as well. First, the MIB service does not allow + * for overly long descriptions, although the limit is not exposed. + * Three memory pages worth of text is way too long though. + */ + memset(ptr, 'A', size); + if (describe_node(mib, 2, id, ptr, 1) != -1) e(0); + if (errno != EINVAL) e(0); /* not EFAULT, should never get that far */ + + ptr[size - 1] = '\0'; + if (describe_node(mib, 2, id, ptr, 1) != -1) e(0); + if (errno != EINVAL) e(0); + + if (describe_node(mib, 2, id, "", 0) != 0) e(0); + + /* + * Second, the description routine must deal with faults occurring + * while it is trying to find the string end. + */ + ptr[size - 2] = 'B'; + ptr[size - 1] = 'C'; + if (describe_node(mib, 2, id, &ptr[size - 3], 1) != -1) e(0); + if (errno != EFAULT) e(0); + + if (describe_node(mib, 2, id, "", 0) != 0) e(0); + + ptr[size - 1] = '\0'; + if (describe_node(mib, 2, id, &ptr[size - 3], 1) != 0) e(0); + + if (describe_node(mib, 2, id, "AB", 0) != 0) e(0); + + /* Pass the second dynamic node ID to the unprivileged child. */ + mib[2] = TEST_ANYWRITE; + if (sysctl(mib, 3, NULL, NULL, &id, sizeof(id)) != 0) e(0); + + (void)test_nonroot(sub87g); + + mib[2] = id; + oldlen = size; + if (sysctl(mib, 3, ptr, &oldlen, NULL, 0) != 0) e(0); + if (oldlen != pgsz) e(0); + if (test_buf(ptr, 'h', pgsz - 1, 1) != 0) e(0); + if (ptr[pgsz - 1] != '\0') e(0); + + if (destroy_node(mib, 2, TEST_DYNAMIC) != 0) e(0); + if (destroy_node(mib, 2, id) != 0) e(0); + + munmap(ptr, size); +} + +/* + * Verify whether the given node on the given path has the given node version. + * Return 0 if the version matches, or -1 if it does not or a failure occurred. + */ +static int +check_version(const int * path, unsigned int pathlen, int id, uint32_t ver) +{ + struct sysctlnode scn; + struct sysctldesc scd; + size_t oldlen; + int r, mib[CTL_MAXNAME]; + + assert(pathlen < CTL_MAXNAME); + memcpy(mib, path, sizeof(mib[0]) * pathlen); + mib[pathlen] = CTL_DESCRIBE; + + /* + * For some reason, when retrieving a particular description (as + * opposed to setting one), the node version number is not checked. + * In order to test this, we deliberately pass in a node version number + * that, if checked, would eventually cause failures. + */ + memset(&scn, 0, sizeof(scn)); + scn.sysctl_flags = SYSCTL_VERSION; + scn.sysctl_num = id; + scn.sysctl_ver = 1; + oldlen = sizeof(scd); + r = sysctl(mib, pathlen + 1, &scd, &oldlen, &scn, sizeof(scn)); + if (r == -1 && errno != ENOMEM) e(0); + + return (scd.descr_ver == ver) ? 0 : -1; +} + +/* + * Test sysctl(2) node versioning. + */ +static void +test87h(void) +{ + struct sysctlnode scn, oldscn; + size_t oldlen; + uint32_t ver[4]; + int mib[4], id[4]; + + /* + * The other tests have already tested sufficiently that a zero version + * is always accepted in calls. Here, we test that node versions + * actually change when creating and destroying nodes, and that the + * right version test is implemented for all of the four node meta- + * operations (query, create, destroy, describe). Why did we not do + * this earlier, you ask? Well, versioning was implemented later on. + */ + subtest = 7; + + /* + * Test versioning with node creation. + */ + mib[0] = CTL_MINIX; + mib[1] = MINIX_TEST; + mib[2] = CTL_CREATE; + memset(&scn, 0, sizeof(scn)); + scn.sysctl_flags = SYSCTL_VERSION | CTLFLAG_READWRITE | CTLTYPE_NODE; + scn.sysctl_num = CTL_CREATE; + strlcpy(scn.sysctl_name, "NodeA", sizeof(scn.sysctl_name)); + oldlen = sizeof(oldscn); + if (sysctl(mib, 3, &oldscn, &oldlen, &scn, sizeof(scn)) != 0) e(0); + if (oldlen != sizeof(oldscn)) e(0); + id[0] = oldscn.sysctl_num; + ver[0] = oldscn.sysctl_ver; + if (ver[0] == 0) e(0); + + if (check_version(mib, 0, CTL_MINIX, ver[0]) != 0) e(0); + if (check_version(mib, 1, MINIX_TEST, ver[0]) != 0) e(0); + if (check_version(mib, 2, id[0], ver[0]) != 0) e(0); + + strlcpy(scn.sysctl_name, "NodeB", sizeof(scn.sysctl_name)); + oldlen = sizeof(oldscn); + if (sysctl(mib, 3, &oldscn, &oldlen, &scn, sizeof(scn)) != 0) e(0); + if (oldlen != sizeof(oldscn)) e(0); + id[1] = oldscn.sysctl_num; + ver[1] = oldscn.sysctl_ver; + if (ver[1] == 0) e(0); + if (ver[1] != NEXT_VER(ver[0])) e(0); + + if (check_version(mib, 0, CTL_MINIX, ver[1]) != 0) e(0); + if (check_version(mib, 1, MINIX_TEST, ver[1]) != 0) e(0); + if (check_version(mib, 2, id[0], ver[0]) != 0) e(0); + if (check_version(mib, 2, id[1], ver[1]) != 0) e(0); + + /* A version that is too high should be rejected. */ + mib[2] = id[0]; + mib[3] = CTL_CREATE; + scn.sysctl_flags = SYSCTL_VERSION | CTLFLAG_IMMEDIATE | + CTLFLAG_READWRITE | CTLTYPE_INT; + scn.sysctl_size = sizeof(int); + scn.sysctl_ver = NEXT_VER(ver[1]); + strlcpy(scn.sysctl_name, "ValueA", sizeof(scn.sysctl_name)); + if (sysctl(mib, 4, NULL, NULL, &scn, sizeof(scn)) != -1) e(0); + if (errno != EINVAL) e(0); + + /* The version of the parent node should be accepted. */ + scn.sysctl_ver = ver[0]; /* different from the root node version */ + oldlen = sizeof(oldscn); + if (sysctl(mib, 4, &oldscn, &oldlen, &scn, sizeof(scn)) != 0) e(0); + if (oldlen != sizeof(oldscn)) e(0); + id[2] = oldscn.sysctl_num; + ver[2] = oldscn.sysctl_ver; + if (ver[2] == 0) e(0); + if (ver[2] != NEXT_VER(ver[1])) e(0); + + if (check_version(mib, 0, CTL_MINIX, ver[2]) != 0) e(0); + if (check_version(mib, 1, MINIX_TEST, ver[2]) != 0) e(0); + if (check_version(mib, 2, id[0], ver[2]) != 0) e(0); + if (check_version(mib, 3, id[2], ver[2]) != 0) e(0); + if (check_version(mib, 2, id[1], ver[1]) != 0) e(0); + + /* A version that is too low (old) should be rejected. */ + mib[2] = id[1]; + + scn.sysctl_ver = ver[0]; + strlcpy(scn.sysctl_name, "ValueB", sizeof(scn.sysctl_name)); + if (sysctl(mib, 4, NULL, NULL, &scn, sizeof(scn)) != -1) e(0); + if (errno != EINVAL) e(0); + + /* The version of the root node should be accepted. */ + scn.sysctl_ver = ver[2]; /* different from the parent node version */ + oldlen = sizeof(oldscn); + if (sysctl(mib, 4, &oldscn, &oldlen, &scn, sizeof(scn)) != 0) e(0); + if (oldlen != sizeof(oldscn)) e(0); + id[3] = oldscn.sysctl_num; + ver[3] = oldscn.sysctl_ver; + if (ver[3] == 0) e(0); + if (ver[3] != NEXT_VER(ver[2])) e(0); + + if (check_version(mib, 0, CTL_MINIX, ver[3]) != 0) e(0); + if (check_version(mib, 1, MINIX_TEST, ver[3]) != 0) e(0); + if (check_version(mib, 2, id[0], ver[2]) != 0) e(0); + if (check_version(mib, 2, id[1], ver[3]) != 0) e(0); + if (check_version(mib, 3, id[3], ver[3]) != 0) e(0); + mib[2] = id[0]; + if (check_version(mib, 3, id[2], ver[2]) != 0) e(0); + + /* + * Test versioning with node queries. + */ + mib[3] = CTL_QUERY; + memset(&scn, 0, sizeof(scn)); + scn.sysctl_flags = SYSCTL_VERSION; + scn.sysctl_ver = ver[0]; /* previous parent version */ + if (sysctl(mib, 4, NULL, NULL, &scn, sizeof(scn)) != -1) e(0); + if (errno != EINVAL) e(0); + + scn.sysctl_ver = ver[2]; /* parent version */ + if (sysctl(mib, 4, NULL, NULL, &scn, sizeof(scn)) != 0) e(0); + + scn.sysctl_ver = ver[2]; /* root version */ + if (sysctl(mib, 4, NULL, NULL, &scn, sizeof(scn)) != 0) e(0); + + scn.sysctl_ver = NEXT_VER(ver[3]); /* nonexistent version */ + if (sysctl(mib, 4, NULL, NULL, &scn, sizeof(scn)) != -1) e(0); + if (errno != EINVAL) e(0); + + /* + * Test versioning with node description. + */ + mib[2] = CTL_DESCRIBE; + scn.sysctl_num = id[0]; + scn.sysctl_ver = ver[3]; /* root and parent, but not target version */ + scn.sysctl_desc = "Parent A"; + if (sysctl(mib, 3, NULL, NULL, &scn, sizeof(scn)) != -1) e(0); + if (errno != EINVAL) e(0); + + scn.sysctl_ver = ver[1]; /* another bad version */ + if (sysctl(mib, 3, NULL, NULL, &scn, sizeof(scn)) != -1) e(0); + if (errno != EINVAL) e(0); + + scn.sysctl_ver = ver[2]; /* target version */ + if (sysctl(mib, 3, NULL, NULL, &scn, sizeof(scn)) != 0) e(0); + + /* Neither querying nor description should have changed versions. */ + if (check_version(mib, 0, CTL_MINIX, ver[3]) != 0) e(0); + if (check_version(mib, 1, MINIX_TEST, ver[3]) != 0) e(0); + if (check_version(mib, 2, id[0], ver[2]) != 0) e(0); + if (check_version(mib, 2, id[1], ver[3]) != 0) e(0); + mib[2] = id[1]; + if (check_version(mib, 3, id[3], ver[3]) != 0) e(0); + mib[2] = id[0]; + if (check_version(mib, 3, id[2], ver[2]) != 0) e(0); + + /* + * Test versioning with node destruction. + */ + mib[3] = CTL_DESTROY; + memset(&scn, 0, sizeof(scn)); + scn.sysctl_flags = SYSCTL_VERSION; + scn.sysctl_num = id[2]; + scn.sysctl_ver = ver[3]; /* root but not target version */ + if (sysctl(mib, 4, NULL, NULL, &scn, sizeof(scn)) != -1) e(0); + if (errno != EINVAL) e(0); + + scn.sysctl_ver = ver[2]; /* target (and parent) version */ + if (sysctl(mib, 4, NULL, NULL, &scn, sizeof(scn)) != 0) e(0); + + /* Fortunately, versions are predictable. */ + ver[0] = NEXT_VER(ver[3]); + + if (check_version(mib, 0, CTL_MINIX, ver[0]) != 0) e(0); + if (check_version(mib, 1, MINIX_TEST, ver[0]) != 0) e(0); + if (check_version(mib, 2, id[0], ver[0]) != 0) e(0); + if (check_version(mib, 2, id[1], ver[3]) != 0) e(0); + + mib[2] = id[1]; + scn.sysctl_num = id[3]; + scn.sysctl_ver = ver[0]; /* root but not target version */ + if (sysctl(mib, 4, NULL, NULL, &scn, sizeof(scn)) != -1) e(0); + if (errno != EINVAL) e(0); + + scn.sysctl_ver = ver[3]; /* target (and parent) version */ + if (sysctl(mib, 4, NULL, NULL, &scn, sizeof(scn)) != 0) e(0); + + ver[1] = NEXT_VER(ver[0]); + + if (check_version(mib, 0, CTL_MINIX, ver[1]) != 0) e(0); + if (check_version(mib, 1, MINIX_TEST, ver[1]) != 0) e(0); + if (check_version(mib, 2, id[0], ver[0]) != 0) e(0); + if (check_version(mib, 2, id[1], ver[1]) != 0) e(0); + + mib[2] = CTL_DESTROY; + scn.sysctl_num = id[0]; + scn.sysctl_ver = ver[1]; /* root and parent, but not target version */ + if (sysctl(mib, 3, NULL, NULL, &scn, sizeof(scn)) != -1) e(0); + if (errno != EINVAL) e(0); + + scn.sysctl_ver = ver[0]; /* target version */ + oldlen = sizeof(oldscn); + if (sysctl(mib, 3, &oldscn, &oldlen, &scn, sizeof(scn)) != 0) e(0); + if (oldlen != sizeof(oldscn)) e(0); + if (oldscn.sysctl_num != id[0]) e(0); + if (oldscn.sysctl_ver != ver[0]) e(0); + + ver[2] = NEXT_VER(ver[1]); + + if (check_version(mib, 0, CTL_MINIX, ver[2]) != 0) e(0); + if (check_version(mib, 1, MINIX_TEST, ver[2]) != 0) e(0); + if (check_version(mib, 2, id[1], ver[1]) != 0) e(0); + + /* For the last destruction, just see if we get the old version. */ + scn.sysctl_num = id[1]; + scn.sysctl_ver = 0; + oldlen = sizeof(oldscn); + if (sysctl(mib, 3, &oldscn, &oldlen, &scn, sizeof(scn)) != 0) e(0); + if (oldlen != sizeof(oldscn)) e(0); + if (oldscn.sysctl_num != id[1]) e(0); + if (oldscn.sysctl_ver != ver[1]) e(0); + + ver[3] = NEXT_VER(ver[2]); + + if (check_version(mib, 0, CTL_MINIX, ver[3]) != 0) e(0); + if (check_version(mib, 1, MINIX_TEST, ver[3]) != 0) e(0); +} + +/* + * Perform pre-test initialization. + */ +static void +test87_init(void) +{ + size_t oldlen; + int mib[3]; + + subtest = 99; + + if ((bad_ptr = mmap(NULL, getpagesize(), PROT_READ, + MAP_ANON | MAP_PRIVATE, -1, 0)) == MAP_FAILED) e(0); + if (munmap(bad_ptr, getpagesize()) != 0) e(0); + + mib[0] = CTL_MINIX; + mib[1] = MINIX_MIB; + mib[2] = MIB_NODES; + oldlen = sizeof(nodes); + if (sysctl(mib, 3, &nodes, &oldlen, NULL, 0) != 0) e(0); + if (oldlen != sizeof(nodes)) e(0); + + mib[2] = MIB_OBJECTS; + oldlen = sizeof(objects); + if (sysctl(mib, 3, &objects, &oldlen, NULL, 0) != 0) e(0); + if (oldlen != sizeof(objects)) e(0); +} + +/* + * Perform post-test checks. + */ +static void +test87_check(void) +{ + unsigned int newnodes, newobjects; + size_t oldlen; + int mib[3]; + + subtest = 99; + + mib[0] = CTL_MINIX; + mib[1] = MINIX_MIB; + mib[2] = MIB_NODES; + oldlen = sizeof(newnodes); + if (sysctl(mib, 3, &newnodes, &oldlen, NULL, 0) != 0) e(0); + if (oldlen != sizeof(newnodes)) e(0); + + /* + * Upon the first run, the total number of nodes must actually go down, + * as we destroy number of static nodes. Upon subsequent runs, the + * number of nodes should remain stable. Thus, we can safely test that + * the number of nodes has not gone up as a result of the test. + */ + if (newnodes > nodes) e(0); + + mib[2] = MIB_OBJECTS; + oldlen = sizeof(newobjects); + if (sysctl(mib, 3, &newobjects, &oldlen, NULL, 0) != 0) e(0); + if (oldlen != sizeof(newobjects)) e(0); + + /* + * The number of dynamically allocated objects should remain the same + * across the test. + */ + if (newobjects != objects) e(0); +} + +/* + * Test program for sysctl(2). + */ +int +main(int argc, char ** argv) +{ + int i, m; + + start(87); + + if (argc == 2) + m = atoi(argv[1]); + else + m = 0xFF; + + test87_init(); + + for (i = 0; i < ITERATIONS; i++) { + if (m & 0x001) test87a(); + if (m & 0x002) test87b(); + if (m & 0x004) test87c(); + if (m & 0x008) test87d(); + if (m & 0x010) test87e(); + if (m & 0x020) test87f(); + if (m & 0x040) test87g(); + if (m & 0x080) test87h(); + } + + test87_check(); + + quit(); +} diff --git a/minix/usr.bin/trace/Makefile b/minix/usr.bin/trace/Makefile index af2d63b86..26580624e 100644 --- a/minix/usr.bin/trace/Makefile +++ b/minix/usr.bin/trace/Makefile @@ -4,7 +4,7 @@ PROG= trace SRCS= call.o error.o escape.o format.o ioctl.o kernel.o mem.o output.o \ proc.o signal.o trace.o .PATH: ${.CURDIR}/service -SRCS+= pm.o vfs.o rs.o vm.o ipc.o +SRCS+= pm.o vfs.o rs.o mib.o vm.o ipc.o .PATH: ${.CURDIR}/ioctl SRCS+= block.o char.o net.o svrctl.o diff --git a/minix/usr.bin/trace/call.c b/minix/usr.bin/trace/call.c index 441432992..b4de797c5 100644 --- a/minix/usr.bin/trace/call.c +++ b/minix/usr.bin/trace/call.c @@ -9,6 +9,7 @@ static const struct calls *call_table[] = { &pm_calls, &vfs_calls, &rs_calls, + &mib_calls, &vm_calls, &ipc_calls, }; @@ -66,6 +67,7 @@ put_endpoint(struct trace_proc * proc, const char * name, endpoint_t endpt) TEXT(SCHED_PROC_NR); TEXT(TTY_PROC_NR); TEXT(DS_PROC_NR); + TEXT(MIB_PROC_NR); TEXT(VM_PROC_NR); TEXT(PFS_PROC_NR); TEXT(ANY); @@ -684,3 +686,22 @@ call_name(struct trace_proc * proc) return proc->call_name; } + +/* + * Return whether the current call failed due to an error at the system call + * level, and if so, return the error code as well. May be called during the + * leave phase of a call only. + */ +int +call_errno(struct trace_proc * proc, int * err) +{ + + if (proc->call_flags & (CF_REG_ERR | CF_MSG_ERR | CF_IPC_ERR)) + return FALSE; + + if (proc->call_result >= 0) + return FALSE; + + *err = -proc->call_result; + return TRUE; +} diff --git a/minix/usr.bin/trace/proc.h b/minix/usr.bin/trace/proc.h index 509f523ee..f89a97991 100644 --- a/minix/usr.bin/trace/proc.h +++ b/minix/usr.bin/trace/proc.h @@ -53,6 +53,13 @@ struct trace_proc { /* ioctl state (ioctl.c) */ int ioctl_index; unsigned int ioctl_flags; + + /* sysctl state (service/mib.c) */ + uint32_t sctl_flags; + size_t sctl_size; + int (*sctl_proc)(struct trace_proc *, const char *, int, const void *, + vir_bytes, size_t); + int sctl_arg; }; /* Trace flags. */ @@ -97,3 +104,8 @@ struct trace_proc { #define IF_OUT 0x1 /* call to print outgoing (written) data */ #define IF_IN 0x2 /* call to print incoming (read) data */ #define IF_ALL 0x4 /* all fields printed (not really a bit) */ + +/* Sysctl processing types, determining what the callback function is to do. */ +#define ST_NAME 0 /* print the rest of the name */ +#define ST_OLDP 1 /* print the data pointed to by oldp */ +#define ST_NEWP 2 /* print the data pointed to by newp */ diff --git a/minix/usr.bin/trace/proto.h b/minix/usr.bin/trace/proto.h index 27a889421..ddd02baee 100644 --- a/minix/usr.bin/trace/proto.h +++ b/minix/usr.bin/trace/proto.h @@ -10,6 +10,7 @@ int call_enter(struct trace_proc *proc, int show_stack); void call_leave(struct trace_proc *proc, int skip); void call_replay(struct trace_proc *proc); const char *call_name(struct trace_proc *proc); +int call_errno(struct trace_proc *proc, int *err); /* error.c */ const char *get_error_name(int err); @@ -107,6 +108,7 @@ void put_dev(struct trace_proc *proc, const char *name, dev_t dev); const struct calls pm_calls; const struct calls vfs_calls; const struct calls rs_calls; +const struct calls mib_calls; const struct calls vm_calls; const struct calls ipc_calls; diff --git a/minix/usr.bin/trace/service/mib.c b/minix/usr.bin/trace/service/mib.c new file mode 100644 index 000000000..5f3fd6e5c --- /dev/null +++ b/minix/usr.bin/trace/service/mib.c @@ -0,0 +1,714 @@ + +#include "inc.h" + +#include + +struct sysctl_tab { + int id; + size_t size; + const struct sysctl_tab *tab; + int (*proc)(struct trace_proc *, const char *, int, const void *, + vir_bytes, size_t); +}; +#define NODE(i,t) { .id = i, .size = __arraycount(t), .tab = t } +#define PROC(i,s,p) { .id = i, .size = s, .proc = p } + +/* The CTL_KERN table. */ +static const struct sysctl_tab kern_tab[] = { +}; + +/* The top-level table, which is indexed by identifier. */ +static const struct sysctl_tab root_tab[] = { + [CTL_KERN] = NODE(0, kern_tab), +}; + +/* + * This buffer should be large enough to avoid having to perform dynamic + * allocation in all but highly exceptional cases. The CTL_KERN subtree is + * currently the largest, so we base the buffer size on its length. + * TODO: merge this buffer with ioctlbuf. + */ +static char sysctlbuf[sizeof(struct sysctlnode) * KERN_MAXID]; + +static const struct flags sysctl_flags[] = { + FLAG_MASK(SYSCTL_VERS_MASK, SYSCTL_VERS_0), + FLAG_MASK(SYSCTL_VERS_MASK, SYSCTL_VERSION), +#define SYSCTL_VER_ENTRIES 2 /* the first N entries are for SYSCTL_VERS_MASK */ + FLAG(CTLFLAG_UNSIGNED), + FLAG(CTLFLAG_OWNDESC), + FLAG(CTLFLAG_MMAP), + FLAG(CTLFLAG_ALIAS), + FLAG(CTLFLAG_ANYNUMBER), + FLAG(CTLFLAG_ROOT), + FLAG(CTLFLAG_HEX), + FLAG(CTLFLAG_IMMEDIATE), + FLAG(CTLFLAG_OWNDATA), + FLAG(CTLFLAG_HIDDEN), + FLAG(CTLFLAG_PERMANENT), + FLAG(CTLFLAG_PRIVATE), + FLAG(CTLFLAG_ANYWRITE), + FLAG_MASK(CTLFLAG_READWRITE, CTLFLAG_READONLY), + FLAG_MASK(CTLFLAG_READWRITE, CTLFLAG_READWRITE), + FLAG_MASK(SYSCTL_TYPEMASK, CTLTYPE_NODE), + FLAG_MASK(SYSCTL_TYPEMASK, CTLTYPE_INT), + FLAG_MASK(SYSCTL_TYPEMASK, CTLTYPE_STRING), + FLAG_MASK(SYSCTL_TYPEMASK, CTLTYPE_QUAD), + FLAG_MASK(SYSCTL_TYPEMASK, CTLTYPE_STRUCT), + FLAG_MASK(SYSCTL_TYPEMASK, CTLTYPE_BOOL), +}; + +/* + * Print the immediate value of a sysctl node. + */ +static void +put_sysctl_imm(struct trace_proc * proc, struct sysctlnode * scn, int use_name) +{ + char *name; + + name = NULL; + + switch (SYSCTL_TYPE(scn->sysctl_flags)) { + case CTLTYPE_INT: + if (use_name) + name = "sysctl_idata"; + if (scn->sysctl_flags & CTLFLAG_HEX) + put_value(proc, name, "0x%x", scn->sysctl_idata); + else if (scn->sysctl_flags & CTLFLAG_UNSIGNED) + put_value(proc, name, "%u", scn->sysctl_idata); + else + put_value(proc, name, "%d", scn->sysctl_idata); + break; + case CTLTYPE_BOOL: + if (use_name) + name = "sysctl_bdata"; + put_field(proc, name, (scn->sysctl_bdata) ? "true" : "false"); + break; + case CTLTYPE_QUAD: + if (use_name) + name = "sysctl_qdata"; + if (scn->sysctl_flags & CTLFLAG_HEX) + put_value(proc, name, "0x%"PRIx64, scn->sysctl_qdata); + else + put_value(proc, name, "%"PRIu64, scn->sysctl_qdata); + break; + } +} + +/* + * Printer for CTL_QUERY data. + */ +static int +put_sysctl_query(struct trace_proc * proc, const char * name, int type, + const void * data __unused, vir_bytes addr, size_t size) +{ + struct sysctlnode scn; + + if (type == ST_NEWP) { + if (!put_open_struct(proc, name, 0, addr, &scn, sizeof(scn))) + return TRUE; + + /* Print just the protocol version, that's all there is. */ + if (verbose > 1) + put_flags(proc, "sysctl_flags", sysctl_flags, + SYSCTL_VER_ENTRIES, "0x%x", scn.sysctl_flags); + + put_close_struct(proc, FALSE /*all*/); + } else { + /* TODO: optionally dump struct sysctlnode array */ + put_open(proc, name, 0, "[", ", "); + if (size > 0) + put_tail(proc, size / sizeof(scn), 0); + put_close(proc, "]"); + } + + return TRUE; +} + +/* + * Printer for CTL_CREATE data. + */ +static int +put_sysctl_create(struct trace_proc * proc, const char * name, int type, + const void * data __unused, vir_bytes addr, size_t size) +{ + struct sysctlnode scn; + + if (!put_open_struct(proc, name, 0, addr, &scn, sizeof(scn))) + return TRUE; + + if (type == ST_NEWP) + put_flags(proc, "sysctl_flags", sysctl_flags, + COUNT(sysctl_flags), "0x%x", scn.sysctl_flags); + + if (scn.sysctl_num == CTL_CREATE && type == ST_NEWP && !valuesonly) + put_field(proc, "sysctl_num", "CTL_CREATE"); + else + put_value(proc, "sysctl_num", "%d", scn.sysctl_num); + + if (type == ST_NEWP) { + put_buf(proc, "sysctl_name", PF_LOCADDR | PF_STRING, + (vir_bytes)scn.sysctl_name, sizeof(scn.sysctl_name)); + } + if (scn.sysctl_ver != 0 && verbose > 0) + put_value(proc, "sysctl_ver", "%u", scn.sysctl_ver); + + if (type == ST_NEWP) { + if (scn.sysctl_flags & CTLFLAG_IMMEDIATE) + put_sysctl_imm(proc, &scn, TRUE /*use_name*/); + + switch (SYSCTL_TYPE(scn.sysctl_flags)) { + case CTLTYPE_NODE: + break; + case CTLTYPE_STRING: + if (scn.sysctl_data != NULL) + put_buf(proc, "sysctl_data", PF_STRING, + (vir_bytes)scn.sysctl_data, + (scn.sysctl_size > 0) ? scn.sysctl_size : + SSIZE_MAX /* hopefully it stops early */); + if (scn.sysctl_data != NULL || verbose == 0) + break; + /* FALLTHROUGH */ + default: + if (!(scn.sysctl_flags & CTLFLAG_IMMEDIATE) && + verbose > 0) + put_ptr(proc, "sysctl_data", + (vir_bytes)scn.sysctl_data); + break; + } + + if (SYSCTL_TYPE(scn.sysctl_flags) == CTLTYPE_STRUCT || + verbose > 0) + put_value(proc, "sysctl_size", "%zu", scn.sysctl_size); + } + + put_close_struct(proc, FALSE /*all*/); + + return TRUE; +} + +/* + * Printer for CTL_DESTROY data. + */ +static int +put_sysctl_destroy(struct trace_proc * proc, const char * name, int type, + const void * data __unused, vir_bytes addr, size_t size) +{ + struct sysctlnode scn; + + if (!put_open_struct(proc, name, 0, addr, &scn, sizeof(scn))) + return TRUE; + + if (type == ST_NEWP) { + put_value(proc, "sysctl_num", "%d", scn.sysctl_num); + if (scn.sysctl_name[0] != '\0') + put_buf(proc, "sysctl_name", PF_LOCADDR | PF_STRING, + (vir_bytes)scn.sysctl_name, + sizeof(scn.sysctl_name)); + if (scn.sysctl_ver != 0 && verbose > 0) + put_value(proc, "sysctl_ver", "%u", scn.sysctl_ver); + } + + put_close_struct(proc, FALSE /*all*/); + + return TRUE; +} + +/* + * Printer for CTL_CREATE data. + */ +static int +put_sysctl_describe(struct trace_proc * proc, const char * name, int type, + const void * data __unused, vir_bytes addr, size_t size) +{ + struct sysctlnode scn; + + if (type == ST_NEWP) { + if (!put_open_struct(proc, name, 0, addr, &scn, sizeof(scn))) + return TRUE; + + /* Print just the protocol version, that's all there is. */ + if (verbose > 1) + put_flags(proc, "sysctl_flags", sysctl_flags, + SYSCTL_VER_ENTRIES, "0x%x", scn.sysctl_flags); + + put_value(proc, "sysctl_num", "%d", scn.sysctl_num); + + if (scn.sysctl_desc != NULL) + put_buf(proc, "sysctl_desc", PF_STRING, + (vir_bytes)scn.sysctl_desc, 1024 /*no constant!*/); + else if (verbose > 0) + put_ptr(proc, "sysctl_desc", + (vir_bytes)scn.sysctl_desc); + + put_close_struct(proc, FALSE /*all*/); + } else { + /* TODO: optionally dump struct sysctldesc array */ + put_field(proc, name, (size == 0) ? "[]" : "[..]"); + } + + return TRUE; +} + +/* + * Printer for generic data, using the node flags stored in proc->sysctl_flags. + */ +static int +put_sysctl_generic(struct trace_proc * proc, const char * name, int type, + const void * data __unused, vir_bytes addr, size_t size) +{ + struct sysctlnode scn; + void *ptr; + int i; + bool b; + u_quad_t q; + size_t len; + + switch (SYSCTL_TYPE(proc->sctl_flags)) { + case CTLTYPE_STRING: + put_buf(proc, name, PF_STRING, addr, size); + return TRUE; + case CTLTYPE_INT: + ptr = &scn.sysctl_idata; + len = sizeof(scn.sysctl_idata); + break; + case CTLTYPE_BOOL: + ptr = &scn.sysctl_bdata; + len = sizeof(scn.sysctl_bdata); + break; + case CTLTYPE_QUAD: + ptr = &scn.sysctl_qdata; + len = sizeof(scn.sysctl_qdata); + break; + case CTLTYPE_STRUCT: + default: + ptr = NULL; + len = 0; + break; + } + + if (ptr == NULL || len != size || + mem_get_data(proc->pid, addr, ptr, len) < 0) { + put_ptr(proc, name, addr); + return TRUE; + } + + put_open(proc, name, PF_NONAME, "{", ", "); + + scn.sysctl_flags = proc->sctl_flags; + + put_sysctl_imm(proc, &scn, FALSE); + + put_close(proc, "}"); + + return TRUE; +} + +/* + * Obtain information about a particular node 'id' in the node directory + * identified by the MIB path 'name' (length 'namelen'). Return TRUE if the + * node was found, in which case it is copied into 'scnp'. Return FALSE if the + * node was not found or another error occurred. + */ +static int +get_sysctl_node(const int * name, unsigned int namelen, int id, + struct sysctlnode * scnp) +{ + struct sysctlnode *scn, *escn, *fscn; + char *buf; + size_t len, elen; + int r, mib[CTL_MAXNAME]; + + assert(namelen < CTL_MAXNAME); + assert(id >= 0); + + /* Query the parent, first using our static buffer for the results. */ + memcpy(mib, name, sizeof(mib[0]) * namelen); + mib[namelen] = CTL_QUERY; + len = sizeof(sysctlbuf); + r = sysctl(mib, namelen + 1, sysctlbuf, &len, NULL, 0); + if (r == -1 && (errno != ENOMEM || len == 0)) + return FALSE; + + /* Even with partial results, check if we already found the node. */ + elen = MIN(len, sizeof(sysctlbuf)); + scn = (struct sysctlnode *)sysctlbuf; + escn = (struct sysctlnode *)&sysctlbuf[elen]; + fscn = NULL; /* pointer to the node once found, NULL until then */ + for (; scn < escn && fscn == NULL; scn++) + if (scn->sysctl_num == id) + fscn = scn; + + /* If our buffer was too small, use a temporary buffer. */ + if (fscn == NULL && r == -1) { + if ((buf = malloc(len)) == NULL) + return FALSE; + if (sysctl(mib, namelen, buf, &len, NULL, 0) == 0) { + scn = (struct sysctlnode *)sysctlbuf; + escn = (struct sysctlnode *)&sysctlbuf[len]; + for (; scn < escn && fscn != NULL; scn++) + if (scn->sysctl_num == id) + fscn = scn; + } + free(buf); + } + + if (fscn != NULL) { + memcpy(scnp, fscn, sizeof(*scnp)); + return TRUE; + } else + return FALSE; +} + +/* + * Print the name string of one level of a sysctl(2) name, while also gathering + * information about the target node. Return 1 if name interpretation should + * continue as before, meaning this function will also be called for the next + * name component (if any). Return 0 if the rest of the name should be printed + * as numbers, without interpretation. Return -1 if printing the name is now + * complete. + */ +static int +put_sysctl_namestr(struct trace_proc * proc, const int * name, + unsigned int namelen, unsigned int n, int all, + const struct sysctl_tab ** sctp) +{ + const struct sysctl_tab *sct; + struct sysctlnode scn; + const char *namestr; + int i, r, id, is_last; + + assert(n < namelen); + + id = name[n]; + is_last = (n == namelen - 1 && all); + namestr = NULL; + + /* Negative identifiers are meta-identifiers. */ + if (id < 0) { + switch (id) { + case CTL_EOL: namestr = ""; break; + case CTL_QUERY: namestr = ""; break; + case CTL_CREATE: namestr = ""; break; + case CTL_CREATESYM: namestr = ""; break; + case CTL_DESTROY: namestr = ""; break; + case CTL_MMAP: namestr = ""; break; + case CTL_DESCRIBE: namestr = ""; break; + } + + /* For some of them, we can print their parameters. */ + if (is_last) { + switch (id) { + case CTL_QUERY: + proc->sctl_proc = put_sysctl_query; + break; + case CTL_CREATE: + proc->sctl_proc = put_sysctl_create; + break; + case CTL_DESTROY: + proc->sctl_proc = put_sysctl_destroy; + break; + case CTL_DESCRIBE: + proc->sctl_proc = put_sysctl_describe; + break; + } + } + + /* + * Meta-identifiers are allowed only at the very end of a name, + * so if anything follows a meta-identifier, there is no good + * way to interpret it. We just print numbers. + */ + r = 0; + } else if (get_sysctl_node(name, n, id, &scn)) { + /* + * For regular identifiers, first see if we have a callback + * function that does the interpretation. The use of the + * callback function depends on whether the current node is of + * type CTLTYPE_NODE: if it is, the callback function is + * responsible for printing the rest of the name (and we return + * -1 here after we are done, #1); if it isn't, then we just + * use the callback function to interpret the node value (#2). + * If we do not have a callback function, but the current node + * is of type CTLTYPE_NODE *and* has a non-NULL callback + * function registered in the MIB service, the remote callback + * function would interpret the rest of the name, so we simply + * print the rest of the name as numbers (returning 0 once we + * are done, #3). Without a MIB-service callback function, + * such nodes are just taken as path components and thus we + * return 1 to continue resolution (#4). Finally, if we do not + * have a callback function, and the current node is a data + * node (i.e., *not* of type CTLTYPE_NODE), we try to interpret + * it generically if it is the last component (#5), or we give + * up and just print numbers otherwise (#6). + */ + + /* Okay, so start by looking up the node in our own tables. */ + sct = NULL; + if (n == 0) { + /* The top level is ID-indexed for performance. */ + if ((unsigned int)id < __arraycount(root_tab)) + *sctp = &root_tab[id]; + else + *sctp = NULL; + } else if (*sctp != NULL) { + /* Other levels are searched, because of sparseness. */ + sct = (*sctp)->tab; /* NULL if missing or leaf */ + for (i = (int)(*sctp)->size; sct != NULL && i > 0; + i--, sct++) + if (sct->id == id) + break; + if (i == 0) + sct = NULL; + *sctp = sct; + } + + /* Now determine what to do. */ + if (SYSCTL_TYPE(scn.sysctl_flags) == CTLTYPE_NODE) { + if (sct != NULL && sct->proc != NULL) { + proc->sctl_size = sct->size; + proc->sctl_proc = sct->proc; + r = -1; /* #1 */ + } else if (scn.sysctl_func != NULL) + r = 0; /* #3 */ + else + r = 1; /* #4 */ + } else { + if (!is_last) + r = 0; /* #6 */ + else if (sct != NULL && sct->proc != NULL) { + /* A nonzero size must match the node size. */ + if (sct->size == 0 || + sct->size == scn.sysctl_size) { + proc->sctl_size = sct->size; + proc->sctl_proc = sct->proc; + } + r = 0; /* #2 */ + } else { + proc->sctl_flags = scn.sysctl_flags; + proc->sctl_proc = put_sysctl_generic; + r = 0; /* #5 */ + } + } + + namestr = scn.sysctl_name; + } else { + /* + * The node was not found. This basically means that we will + * not be able to get any information about deeper nodes + * either. We do not even try: just print numbers. + */ + r = 0; + } + + if (!valuesonly && namestr != NULL) + put_field(proc, NULL, namestr); + else + put_value(proc, NULL, "%d", id); + + /* + * Did we determine that the rest of the name should be printed by the + * callback function? Then we might as well make that happen. The + * abuse of the parameter types is not great, oh well. + */ + if (r == -1) + (void)proc->sctl_proc(proc, NULL, ST_NAME, &name[n + 1], 0, + namelen - n - 1); + + return r; +} + +/* + * Print the sysctl(2) name parameter, and gather information needed to print + * the oldp and newp parameters later. + */ +static void +put_sysctl_name(struct trace_proc * proc, const char * name, int flags, + vir_bytes addr, unsigned int namelen) +{ + const struct sysctl_tab *sct = NULL; + const char *namestr; + int r, all, namebuf[CTL_MAXNAME]; + unsigned int n; + + if (namelen > CTL_MAXNAME) { + namelen = CTL_MAXNAME; + all = 0; + } else + all = 1; + + if ((flags & PF_FAILED) || valuesonly > 1 || namelen > CTL_MAXNAME || + (namelen > 0 && !(flags & PF_LOCADDR) && + mem_get_data(proc->pid, addr, namebuf, + namelen * sizeof(namebuf[0])) < 0)) { + if (flags & PF_LOCADDR) + put_field(proc, name, "&.."); + else + put_ptr(proc, name, addr); + return; + } else if (namelen > 0 && (flags & PF_LOCADDR)) + memcpy(namebuf, (void *)addr, sizeof(namebuf[0]) * namelen); + + /* + * Print the path name of the node as possible, and find information + * about the target node as we go along. See put_sysctl_namestr() for + * the meaning of 'r'. + */ + put_open(proc, name, PF_NONAME, "[", "."); + for (n = 0, r = 1; n < namelen; n++) { + if (r == 1) { + if ((r = put_sysctl_namestr(proc, namebuf, namelen, n, + all, &sct)) < 0) + break; + } else + put_value(proc, NULL, "%d", namebuf[n]); + } + if (!all) + put_field(proc, NULL, ".."); + put_close(proc, "]"); +} + +/* + * Print the sysctl(2) oldp or newp parameter. PF_ALT means that the given + * parameter is newp rather than oldp, in which case PF_FAILED will not be set. + */ +static void +put_sysctl_data(struct trace_proc * proc, const char * name, int flags, + vir_bytes addr, size_t len) +{ + char *ptr; + int type, all; + + if ((flags & PF_FAILED) || addr == 0 || valuesonly > 1 || + proc->sctl_proc == NULL || proc->sctl_size > sizeof(sysctlbuf) || + (proc->sctl_size > 0 && (proc->sctl_size != len || + mem_get_data(proc->pid, addr, sysctlbuf, proc->sctl_size) < 0))) { + put_ptr(proc, name, addr); + return; + } + + type = (flags & PF_ALT) ? ST_NEWP : ST_OLDP; + ptr = (proc->sctl_size > 0) ? sysctlbuf : NULL; + + /* + * The rough idea here: we have a "simple" mode and a "flexible" mode, + * depending on whether a size was specified in our table. For the + * simple mode, we only call the callback function when we have been + * able to copy in the data. A surrounding {} block will be printed + * automatically, the callback function only has to print the data + * fields. The simple mode is basically for structures. In contrast, + * the flexible mode leaves both the copying and the printing entirely + * to the callback function, which thus may print the pointer on copy + * failure (in which case the surrounding {}s would get in the way). + */ + if (ptr != NULL) + put_open(proc, name, 0, "{", ", "); + + all = proc->sctl_proc(proc, name, type, ptr, addr, len); + + if (ptr != NULL) { + if (all == FALSE) + put_field(proc, NULL, ".."); + put_close(proc, "}"); + } +} + +static int +mib_sysctl_out(struct trace_proc * proc, const message * m_out) +{ + unsigned int namelen; + + /* Reset the sysctl-related state. */ + proc->sctl_flags = 0; + proc->sctl_size = 0; + proc->sctl_proc = NULL; + proc->sctl_arg = 0; + + namelen = m_out->m_lc_mib_sysctl.namelen; + + /* As part of processing the name, we initialize the state. */ + if (namelen <= CTL_SHORTNAME) + put_sysctl_name(proc, "name", PF_LOCADDR, + (vir_bytes)&m_out->m_lc_mib_sysctl.name, namelen); + else + put_sysctl_name(proc, "name", 0, m_out->m_lc_mib_sysctl.namep, + namelen); + + put_value(proc, "namelen", "%u", namelen); + + if (m_out->m_lc_mib_sysctl.oldp == 0 || valuesonly > 1) { + put_sysctl_data(proc, "oldp", 0, + m_out->m_lc_mib_sysctl.oldp, + m_out->m_lc_mib_sysctl.oldlen); + /* If oldp is NULL, oldlen may contain garbage; don't print. */ + if (m_out->m_lc_mib_sysctl.oldp != 0) + put_value(proc, "oldlen", "%zu", /* {%zu} is more */ + m_out->m_lc_mib_sysctl.oldlen); /* correct.. */ + else + put_value(proc, "oldlen", "%d", 0); + put_sysctl_data(proc, "newp", PF_ALT, + m_out->m_lc_mib_sysctl.newp, + m_out->m_lc_mib_sysctl.newlen); + put_value(proc, "newlen", "%zu", + m_out->m_lc_mib_sysctl.newlen); + return CT_DONE; + } else + return CT_NOTDONE; +} + +static void +mib_sysctl_in(struct trace_proc * proc, const message * m_out, + const message * m_in, int failed) +{ + int err; + + if (m_out->m_lc_mib_sysctl.oldp != 0 && valuesonly <= 1) { + put_sysctl_data(proc, "oldp", failed, + m_out->m_lc_mib_sysctl.oldp, + m_in->m_mib_lc_sysctl.oldlen /* the returned length */); + put_value(proc, "oldlen", "%zu", /* {%zu} is more correct.. */ + m_out->m_lc_mib_sysctl.oldlen); + put_sysctl_data(proc, "newp", PF_ALT, + m_out->m_lc_mib_sysctl.newp, + m_out->m_lc_mib_sysctl.newlen); + put_value(proc, "newlen", "%zu", + m_out->m_lc_mib_sysctl.newlen); + put_equals(proc); + } + + put_result(proc); + + /* + * We want to print the returned old length in the following cases: + * 1. the call succeeded, the old pointer was NULL, and no new data was + * supplied; + * 2. the call succeeded, the old pointer was not NULL, and the + * returned old length is different from the supplied old length. + * 3. the call failed with ENOMEM or EEXIST, and the old pointer was + * not NULL (an undocumented NetBSD feature, used by sysctl(8)). + */ + if (/*#1*/ (!failed && m_out->m_lc_mib_sysctl.oldp == 0 && + (m_out->m_lc_mib_sysctl.newp == 0 || + m_out->m_lc_mib_sysctl.newlen == 0)) || + /*#2*/ (!failed && m_out->m_lc_mib_sysctl.oldp != 0 && + m_out->m_lc_mib_sysctl.oldlen != m_in->m_mib_lc_sysctl.oldlen) || + /*#3*/ (failed && call_errno(proc, &err) && + (err == ENOMEM || err == EEXIST) && + m_out->m_lc_mib_sysctl.oldp != 0)) { + put_open(proc, NULL, 0, "(", ", "); + put_value(proc, "oldlen", "%zu", m_in->m_mib_lc_sysctl.oldlen); + put_close(proc, ")"); + } +} + +#define MIB_CALL(c) [((MIB_ ## c) - MIB_BASE)] + +static const struct call_handler mib_map[] = { + MIB_CALL(SYSCTL) = HANDLER("sysctl", mib_sysctl_out, mib_sysctl_in), +}; + +const struct calls mib_calls = { + .endpt = MIB_PROC_NR, + .base = MIB_BASE, + .map = mib_map, + .count = COUNT(mib_map) +}; diff --git a/releasetools/Makefile b/releasetools/Makefile index f2ae72f70..7b4bed4fb 100644 --- a/releasetools/Makefile +++ b/releasetools/Makefile @@ -20,9 +20,10 @@ PROGRAMS+= ${PROGROOT}/minix/servers/sched/sched PROGRAMS+= ${PROGROOT}/minix/servers/vfs/vfs PROGRAMS+= ${PROGROOT}/minix/drivers/storage/memory/memory PROGRAMS+= ${PROGROOT}/minix/drivers/tty/tty/tty -PROGRAMS+= ${PROGROOT}/minix/fs/mfs/mfs +PROGRAMS+= ${PROGROOT}/minix/servers/mib/mib PROGRAMS+= ${PROGROOT}/minix/servers/vm/vm PROGRAMS+= ${PROGROOT}/minix/fs/pfs/pfs +PROGRAMS+= ${PROGROOT}/minix/fs/mfs/mfs PROGRAMS+= ${PROGROOT}/sbin/init/init all usage help: diff --git a/releasetools/arm_sdimage.sh b/releasetools/arm_sdimage.sh index 124d3c656..51578628e 100755 --- a/releasetools/arm_sdimage.sh +++ b/releasetools/arm_sdimage.sh @@ -170,7 +170,7 @@ ${CROSS_PREFIX}objcopy ${OBJ}/minix/kernel/kernel -O binary ${OBJ}/kernel.bin mcopy -bsp -i ${WORK_DIR}/fat.img ${OBJ}/kernel.bin ::kernel.bin for f in servers/vm/vm servers/rs/rs servers/pm/pm servers/sched/sched \ - servers/vfs/vfs servers/ds/ds fs/mfs/mfs fs/pfs/pfs \ + servers/vfs/vfs servers/ds/ds servers/mib/mib fs/pfs/pfs fs/mfs/mfs \ ../sbin/init/init do fn=`basename $f`.elf diff --git a/releasetools/gen_uEnv.txt.sh b/releasetools/gen_uEnv.txt.sh index 4884c8279..d83db0758 100755 --- a/releasetools/gen_uEnv.txt.sh +++ b/releasetools/gen_uEnv.txt.sh @@ -9,10 +9,11 @@ list="0x80200000 kernel.bin 0x84000000 vfs.elf 0x84800000 memory.elf 0x85000000 tty.elf -0x85800000 mfs.elf +0x85800000 mib.elf 0x86000000 vm.elf 0x86800000 pfs.elf -0x87000000 init.elf" +0x87000000 mfs.elf +0x87800000 init.elf" # # PREFIX for loading file over tftp to allow hosting multiple diff --git a/releasetools/release.functions b/releasetools/release.functions index 8c0e4a4b3..2e9866cd7 100644 --- a/releasetools/release.functions +++ b/releasetools/release.functions @@ -77,10 +77,11 @@ load=/mod04_sched load=/mod05_vfs load=/mod06_memory load=/mod07_tty -load=/mod08_mfs +load=/mod08_mib load=/mod09_vm load=/mod10_pfs -load=/mod11_init +load=/mod11_mfs +load=/mod12_init # This space intentionally left blank - leave to appease bootloader! # This space intentionally left blank - leave to appease bootloader! # This space intentionally left blank - leave to appease bootloader! diff --git a/releasetools/x86_cdimage.sh b/releasetools/x86_cdimage.sh index 1d14ea0a4..bc8c913c5 100755 --- a/releasetools/x86_cdimage.sh +++ b/releasetools/x86_cdimage.sh @@ -62,10 +62,11 @@ load=/boot/minix_default/mod04_sched load=/boot/minix_default/mod05_vfs load=/boot/minix_default/mod06_memory load=/boot/minix_default/mod07_tty -load=/boot/minix_default/mod08_mfs +load=/boot/minix_default/mod08_mib load=/boot/minix_default/mod09_vm load=/boot/minix_default/mod10_pfs -load=/boot/minix_default/mod11_init +load=/boot/minix_default/mod11_mfs +load=/boot/minix_default/mod12_init END_BOOT_CFG add_file_spec "boot.cfg" extra.cdfiles diff --git a/sys/sys/sysctl.h b/sys/sys/sysctl.h index 9d63f60ef..1eea8f207 100644 --- a/sys/sys/sysctl.h +++ b/sys/sys/sysctl.h @@ -1341,16 +1341,16 @@ typedef void *sysctlfn; __BEGIN_DECLS int sysctl(const int *, u_int, void *, size_t *, const void *, size_t); -#if !defined(__minix) int sysctlbyname(const char *, void *, size_t *, const void *, size_t); int sysctlgetmibinfo(const char *, int *, u_int *, char *, size_t *, struct sysctlnode **, int); int sysctlnametomib(const char *, int *, size_t *); +#if !defined(__minix) int proc_compare(const struct kinfo_proc2 *, const struct kinfo_lwp *, const struct kinfo_proc2 *, const struct kinfo_lwp *); +#endif /* !defined(__minix) */ void *asysctl(const int *, size_t, size_t *); void *asysctlbyname(const char *, size_t *); -#endif /* !defined(__minix) */ __END_DECLS #endif /* !_KERNEL */