diff --git a/minix/usr.bin/trace/NOTES b/minix/usr.bin/trace/NOTES index 91d8cb761..fed4a8475 100644 --- a/minix/usr.bin/trace/NOTES +++ b/minix/usr.bin/trace/NOTES @@ -201,6 +201,112 @@ functions for call with no parameters, and for functions which need no more than put_result() to print their system call result, respectively. +ADDING AN IOCTL HANDLER + +There are many IOCTL requests, and many have their own associated data types. +Like with system calls, the idea is to provide an actual implementation for any +IOCTLs that can actually occur in the wild. This consists of printing the full +IOCTL name, as well as its argument. First something about how handling IOCTLs +is grouped into files in the ioctl subdirectory, then about the actual +procedure the IOCTLs are handled. + +Grouping of IOCTL handling in the ioctl subdirectory is currently based on the +IOCTLs' associated device type. This is not a performance optimization: for +any given IOCTL, there is no way for the main IOCTL code (in ioctl.c) to know +which group, if any, contains a handler for the IOCTL, so it simply queries all +groups. The grouping is there only to keep down the size of individual source +files, and as such not even strict: for example, networking IOCTLs are +technically a subset of character IOCTLs, and kept separate only because there +are so many of them. The point here is mainly that the separation is not at +all set in stone. However, the svrctl group is an exception: svrctl(2) +requests are very much like IOCTLs, and thus also treated as such, but they are +in a different namespace. Thus, their handlers are in a separate file. + +As per the ioctl_table structure, each group has a function to return the name +of an IOCTL it knows (typically _ioctl_name), and a function to handle +IOCTL arguments (typically _ioctl_arg). Whenever an IOCTL system call +is made, each group's name function is queried. This function has the +following prototype: + + const char *group_ioctl_name(unsigned long req); + +The "req" parameter contains the IOCTL request code. The function is to return +a static non-NULL string if it knows the name for the request code, or NULL +otherwise. If the function returns a non-NULL string, that name will be used +for the IOCTL. In addition, if the IOCTL has an argument at all, i.e. it is +not of the basic _IO() type, that group (and only that group!) will be queried +about the IOCTL argument, by calling the group's IOCTL argument function. The +IOCTL argument function has the following prototype: + + int group_ioctl_arg(struct trace_proc *proc, unsigned long req, void *ptr, + int dir); + +For a single IOCTL, this function may be called up to three times. The first +time, "ptr" will be NULL, and based on the same IOCTL request code "req", the +function must return any bitwise combination of two flags: IF_OUT and IF_IN. + +The returned flags determine whether and how the IOCTL's argument will be +printed: before and/or after performing the IOCTL system call. These two flags +effectively correspond to the "write" and "read" argument directions of IOCTLs: +IF_OUT indicates that the argument should be printed before the IOCTL request, +and this is to be used only for IOCTLs of type _IOW() and _IOWR(). IF_IN +indicates that the argument should be printed after the IOCTL request (but if +it was successful only), and is to be used only for IOCTLs of type _IOR() and +_IOWR(). + +The returned flag combination determines how the IOCTL is formatted. The +following possible return values result in the following output formats, again +with the "|" indicating the call split, "out" being the IOCTL argument contents +printed before the IOCTL call, and "in" being the IOCTL argument printed after +the IOCTL call: + + 0: ioctl(3, IOCFOO, &0xaddress) = |0 + IF_OUT: ioctl(3, IOCFOO, {out}) = |0 + IF_OUT|IF_IN: ioctl(3, IOCFOO, {out}) = |0 {in} + IF_IN: ioctl(3, IOCFOO, |{in}) = 0 + +Both IF_ flags are optional, mainly because it is not always needed to print +both sides for an _IOWR() request. However, using the wrong flag (e.g., IF_OUT +for an _IOR() request, which simply makes no sense) will trigger an assert. +Also, the function should basically never return 0 for an IOCTL it recognizes. +Again, for IOCTLs of type _IO(), which have no argument, the argument function +is not called at all. + +Now the important part. For each flag that is returned on the initial call to +the argument function, the argument function will be called again, this time to +perform actual printing of the argument. For these subsequent calls, "ptr" +will point to the argument data which has been copied to the local address +space, and "dir" will contain one of the returned flags (that is, either IF_OUT +or IF_IN) to indicate whether the function is called before or after the IOCTL +call. As should now be obvious, if the first call returned IF_OUT | IF_IN, the +function will be called again with "dir" set to IF_OUT, and if the IOCTL call +did not fail, once more (for the third time), now with "dir" set to IF_IN. + +For these calls with an actual "ptr" value and a direction, the function should +indeed print the argument as appropriate, using "proc" as process pointer for +use in calls to the printing functions. The general approach is to print non- +structure arguments as single values with no field name, and structure +arguments by printing its fields with their field names. The main code (in +ioctl.c) ensures that the output is enclosed in curly brackets, thus making the +output look like a structure anyway. + +For these subsequent calls, the argument function's return value should be +IF_ALL if all parts of the IOCTL argument have been printed, or 0 otherwise. +In the latter case, the main code will add a final ".." field to indicate to +the user that not all parts of the argument have been printed, very much like +the "all" parameter of put_close_struct. + +If no name can be found for the IOCTL request code, the argument will simply be +printed as a pointer. The same happens in error cases, for example if copying +in the IOCTL data resulted in an error. + +There is no support for dealing with multiple IOCTLs with the exact same +request code--something that should not, but sadly does, occur in practice. +For now, the preferred approach would be to implement only support for the +IOCTL that is most likely to be found in practice, and possibly to put a horse +head in the bed of whoever introduced the duplicate request code. + + INTERNALS: MULTIPROCESS OUTPUT AND PREEMPTION Things get interesting when multiple processes are traced at once. Due to the diff --git a/minix/usr.bin/trace/ioctl.c b/minix/usr.bin/trace/ioctl.c index ed8931baf..4c4b49834 100644 --- a/minix/usr.bin/trace/ioctl.c +++ b/minix/usr.bin/trace/ioctl.c @@ -44,16 +44,19 @@ put_ioctl_req(struct trace_proc * proc, const char * name, unsigned long req, * smart about looking up particular codes in each switch statement, * although in the worst case, it's a full O(n) lookup. */ - for (i = 0; !valuesonly && i < COUNT(ioctl_table); i++) { + for (i = 0; i < COUNT(ioctl_table); i++) { /* IOCTLs and SVRCTLs are considered different name spaces. */ if (ioctl_table[i].is_svrctl != is_svrctl) continue; if ((text = ioctl_table[i].name(req)) != NULL) { - put_field(proc, name, text); - proc->ioctl_index = i; + if (valuesonly) + break; + + put_field(proc, name, text); + return; } } @@ -106,9 +109,12 @@ put_ioctl_arg_out(struct trace_proc * proc, const char * name, dir = (_MINIX_IOCTL_IOW(req) ? IF_OUT : 0) | (_MINIX_IOCTL_IOR(req) ? IF_IN : 0); - if (dir == 0) + if (dir == 0) { proc->ioctl_index = -1; /* no argument to print at all */ + return CT_DONE; + } + /* No support for printing big-IOCTL contents just yet. */ if (valuesonly > 1 || _MINIX_IOCTL_BIG(req) || proc->ioctl_index == -1) {