[Toybox] [CLEANUP] More ifconfig cleanup.

Rob Landley rob at landley.net
Sat Apr 20 20:40:12 PDT 2013

Helping my friend Adrienne pack out her apartment today, but it's still  
more free time than a weekend with the nieces and nephews.

Commit 869 (http://landley.net/hg/toybox/rev/869):

Just moving code around, no actual code changes. Reorder functions to  
eliminate the need for function prototypes, and put the command_main()  
function at the end of the file. (That way it's easy to spot.)

In general, C code ordered to avoid function prototypes reads from the  
bottom up. The main function is at the end, and each other function is  
after the code it uses.

Commit 870 (http://landley.net/hg/toybox/rev/870):

Looking at the code for a rough edge, I immediately spot this:

-struct iface_list {
-  unsigned long long   receive_bytes; //total bytes received
-  unsigned long long   receive_packets; //total packets received
-  unsigned long     receive_errors; //bad packets received
-  unsigned long     receive_drop; //no space in linux buffers
-  unsigned long     receive_fifo; //receiver fifo overrun
-  unsigned long     receive_frame; //received frame alignment error
-  unsigned long     receive_compressed;
-  unsigned long     receive_multicast; //multicast packets received
-  unsigned long long   transmit_bytes; //total bytes transmitted
-  unsigned long long   transmit_packets; //total packets transmitted
-  unsigned long     transmit_errors; //packet transmit problems
-  unsigned long     transmit_drop; //no space available in linux
-  unsigned long     transmit_fifo;
-  unsigned long     transmit_colls;
-  unsigned long     transmit_carrier;
-  unsigned long     transmit_compressed; //num_tr_compressed;

The first thing to do with that is collate the declarations. We don't  
care about allocation order here because it's not an API, so:

+  unsigned long long r_bytes, r_packets, t_bytes, t_packets;
+  unsigned long r_errors, r_drop, r_fifo, r_frame, r_compressed,  
+  unsigned long t_errors, t_drop, t_fifo, t_colls, t_carrier,  

Yes I changed the names, because r_ and t_ provide all the information  
the longer prefixes did. That means we need to adjust the users, so the  
first one we find is in get_proc_info():

-  sscanf(buff, "%llu%llu%lu%lu%lu%lu%lu%lu%llu%llu%lu%lu%lu%lu%lu%lu",
-    &il->receive_bytes, &il->receive_packets, &il->receive_errors,
-    &il->receive_drop, &il->receive_fifo, &il->receive_frame,
-    &il->receive_compressed, &il->receive_multicast,  
-    &il->transmit_packets, &il->transmit_errors, &il->transmit_drop,
-    &il->transmit_fifo, &il->transmit_colls, &il->transmit_carrier,
-    &il->transmit_compressed);

And that... isn't right.

get_proc_info() has a giant sscanf reading in 16 values: can that just  
be an array of unsigned long long values? Yes, promoting 32 bit  
integers to 64 bit integers unnecessarily wastes some space, but  
consistency means the guts of get_proc_info() are something like:

     for (j=0; j<16 && !errno; j++) il->val[j] = strtoll(buf, &buf, 0);

Which can be trivially inlined at the only callsite in  
get_ifconfig_info, eliminating one function already. The potential  
_downside_ of this is we now have 16 magic values that aren't getting  
marshalled into named variables, and making an enum of the indexes to  
the array would be as ugly as what we're removing.

But glancing through the code to find consumers for the data, the only  
user seems to be display_ifconfig(). Can we clean that up in a way that  
avoids an enum?

Ok, first things first: let's go back adjust struct iface_list again to  
have an array of unsigned long long. While we're there, let's do a few  
more cleanups:

   - rename iface_list to if_list (global search and replace the name).

   - take the unnecessary ifr prefixes off of structure members (the  
names are
     always used relative to the structure, no need for prefix _within_  
the struct).

   - this reveals an ifaddr/ifraddr name conflict, they can't both be  
"addr". However,
     looking at users of the first one it should really be called  
hasaddr because it's
     0 or 1 depending on whether the interface has an address. (Can we  
do in-band
     signaling in sockaddr to determine that? No, because is a  
valid address.
     Hmmm, come back to that...)

While we're up there, yank the IFF_PORTSEL stuff because we took that  
out last commit (it was obsolete).

Inline get_proc_info() into get_ifconfig_info(), and smooth that over,  
and let's cut and paste out the old sscanf stuff into a cheatsheat for  
fixing up display_ifconfig():

//      &il->r_bytes, &il->r_packets, &il->r_errors, &il->r_drop,  
//      &il->r_frame, &il->r_compressed, &il->r_multicast, &il->t_bytes,
//      &il->t_packets, &il->t_errors, &il->t_drop, &il->t_fifo,  
//      &il->t_carrier, &il->t_compressed);

There are our 16 values, in order.

While we're at it, get_ifconfig_info() is only ever called from  
show_iface(), so inline _that_ and make another function go away. And  
we have xfopen() that handles the perror_exit() so never returns  
failure, and the caller never does anything after this returns except  
free memory and set the exit code, so make the function return void...  
the "return 1" can become another perror_exit(), and then the return 0  
goes away. Oh, and one more from readconf() but that can use xioctl()  
and perror_exit() for the socket failure (I should do an xsocket()),  
and then it returns void too and handles its own errors. (A nice thing  
about changing the return type to "void" is the compiler tells you when  
you miss a return of non-void type.)

And safe_strncpy() can be replaced by xstrcpy() out of lib/lib.c. Note  
that this is a behavior change: instead of silently truncating strings  
and operating on the wrong date, we error_exit() when asked to perform  
a strcpy we can't do. (Instead of silently malfunctioning, fail  
immediately and loudly.)


(This function is not the same as show_iface()? Um, gotta read through  
more of that at some point, but I'm stil on the "local transforms that  
should do equivalent thing" stage of simplification, not yet to the  
"holistic redesign" stage...)

Sigh. Next commit is going to have to be an inline-fest. Each of the  
first few functions in this (get_hw_info, print_hw_addr, print_ip_addr,  
print_ip6_addr...) they're all called exactly once, from here. So the  
body of those functions might as well go inline here, and then we can  
see what opportunities for simplification that affords us. But that's  
next time, I'm trying to keep these cleanups reasonably sized...

Alright, it looks like the body of if (non_virtual_interface) contains  
all the transmitted/received print logic. It's a bunch of explicit  
xprintf() statements with lots of arguments, which I'd really like to  
replace with table-driven logic.

It prints out entries 1, 2, 3, 4, and 5 from the array, each with  
appropriate "label:" strings. The next line is entries 9, 10, 11, 12,  
and... 14. Ok, not quite consistent. Then it goes back and prints 13 on  
the next line, Then it _might_ print out entry 6 ("compressed:") except  
I have no idea what that is. The comment says "Dummy types for non ARP  
hardware" which means what, exactly? (The host's ifconfig isn't  
printing this out for any interface type I have...) And then it prints  
txqueuelen, which isn't in this array but elsewhere in the if_list  
struct (and there's no corresponding rxqueuelen anywhere). And then the  
last line is entries 0 and 8.

Type 7, multicast, is never printed out. There's no code in ifconfig.c  
to print it out. I'm fine with this: multicast was a bad idea that went  
away around 2000. If netflix, youtube, itunes, and all the random  
podcasts out there aren't using it, it will never matter. Even  
bittorrent isn't using it. The internet is neither television nor  
radio, catching a broadcast as it goes by is not the model here. People  
thought "push" made sense back in 1997, but if you push to a phone  
that's asleep and you wake it up and make it waste battery, people get  
annoyed. (Instead you send a request, you get a response. That's the  
model, and it has no place for multicast. If you want to distribute and  
fan out, you use gnutella or bittorrent or akamai or...)

Next question: does "compressed" matter more than "multicast"? Dunno.  
It's only printed out for CSLIP types, but I can dig into the kernel  
source to find out what this CSLIP thing is...


It's a serial header compression technology from 1990 that's never been  
updated once in the past 23 years. Uh-huh. Well, the kernel hasn't  
removed support for it, but I'm not sure specifically reporting extra  
data for it is all that interesting. In fact, I think I'm going to drop  
that bit because I really don't care.

I think I'll make an array indicating what order to print each entry  
in, because it's too crazy to try to control it with if() statements.  
We can make the order array "signed char" because the 60 or so wasted  
bytes from making it int[] is probably more than the mask/shift code  
arm will add to read it, especially if we assign it into a local int  
variable and only read it once at the start of the loop. Making it  
signed means we can include -1 entries in the order to show where to  
stick a newline+indent, with one special case value to print txqueuelen  
instead of an entry out of the table.

(I tried ending with -1, but the last line after the table isn't always  
printed, and I don't like ending with a lot of trailing whitespace. So  
put an xputc('\n') after the table-driven bits.)

Altogether, we wind up with:

   if(il->non_virtual_iface) {
     char *label[] = {"RX bytes", "RX packets", "errors", "dropped",  
       "frame", 0, 0, "TX bytes", "TX packets", "errors", "dropped",  
       "collisions", "carrier", 0, "txqueuelen"};
     signed char order[] = {-1, 1, 2, 3, 4, 5, -1, 9, 10, 11, 12, 14,  
       13, 16, -1, 0, 8};
     int i;

     for (i = 0; i < sizeof(order); i++) {
       int j = order[i];

       if (j < 0) xprintf("\n%10c", ' ');
       else xprintf("%s:%llu ", label[j],
         j==16 ? (unsigned long long)il->txqueuelen : il->val[j]);

I can probably simplify that further (bytes, packets, errors, dropped,  
overruns: that's five duplicated strings, and it's the first five  
strings of j&7, then RX/TX is inherent j&8 and is printed out at the  
start of each line. The 4 non-repeated strings are positions 5, 13, 14,  
16, the first three of which are 0-3 with j/7 and that last one doesn't  
_have_ to be 16 in "order", it could be 21, and then I could have two  
string tables with the 5 repeated strings and the 4 non-repeated  

But it's a good enough stopping place for right now, and I have to stop  
and think if I want to be _that_ clever. (That's spending complexity to  
reduce size. I think it's ok in this instance, but it's the kind of  
thing I prefer to sleep on first.)

And notice: we're now under 1000 lines! (That's 956 lines total. We  
started somewhere over 1500. And we're nowhere near done cleaning up  
this file yet...)


More information about the Toybox mailing list