From: David van Moolenbroek Date: Tue, 13 Oct 2015 22:43:53 +0000 (+0000) Subject: Add MIB service, sysctl(2) support X-Git-Url: http://zhaoyanbai.com/repos/Bv9ARM.ch13.html?a=commitdiff_plain;h=e4e21ee1b2710f3d411514741ce10d80156f4b5d;p=minix.git Add MIB service, sysctl(2) support The new MIB service implements the sysctl(2) system call which, as we adopt more NetBSD code, is an increasingly important part of the operating system API. The system call is implemented in the new service rather than as part of an existing service, because it will eventually call into many other services in order to gather data, similar to ProcFS. Since the sysctl(2) functionality is used even by init(8), the MIB service is added to the boot image. MIB stands for Management Information Base, and the MIB service should be seen as a knowledge base of management information. The MIB service implementation of the sysctl(2) interface is fairly complete; it incorporates support for both static and dynamic nodes and imitates many NetBSD-specific quirks expected by userland. The patch also adds trace(1) support for the new system call, and adds a new test, test87, which tests the fundamental operation of the MIB service rather thoroughly. Change-Id: I4766b410b25e94e9cd4affb72244112c2910ff67 --- 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 */