[Toybox] [CLEANUP] ifconfig commit 907

Rob Landley rob at landley.net
Mon May 20 22:26:14 PDT 2013


http://landley.net/hg/toybox/rev/907

--- show_ip_addr

The function show_ip_addr() is only ever called from display_ifconfig.  
It's called four times in a row, so it's better to have it be a  
function that repeated inline four times. However, it's even better to  
have it be the body of a loop that we iterate over four times, parsing  
a table of data. (Locality of reference makes code easier to read: a  
loop that does its work inline is all in front of you, a loop that  
calls a function has to marshal data into and out of the function and  
you have to go to another page of code to see what it's doing.)

The question is: does the overhead of the table outweigh the advantage  
of having the code moved inline at its only callsite? This is an  
apples/oranges comparison: making the code bigger vs making it easier  
to understand. However, each function call with multiple arguments has  
a certain amount of overhead, as does the extra function body itself,  
so if the table doesn't have more entries than the functino has  
arguments we're probably not actually losing space. (We could check  
with "make baseline" and "make bloatcheck" if we wanted to be sure, but  
with inling making the code easier to read, it seems worth it to me.)

The new table is:

+    struct {
+      char *name;
+      int flag, offset;
+    } addr[] = {
+      {"addr", 0, offsetof(struct if_list, addr)},
+      {"P-t-P", IFF_POINTOPOINT, offsetof(struct if_list, dstaddr)},
+      {"Bcast", IFF_BROADCAST, offsetof(struct if_list, broadaddr)},
+      {"Mask", 0, offsetof(struct if_list, netmask)}
+    };

And code showing the actual IPs becomes:

-    show_ip_addr("addr", &il->addr);
-    if(il->flags & IFF_POINTOPOINT) show_ip_addr("P-t-P",  
&il->dstaddr);
-    if(il->flags & IFF_BROADCAST) show_ip_addr("Bcast",  
&il->broadaddr);
-    show_ip_addr("Mask", &il->netmask);
+    for (i=0; i < sizeof(addr)/sizeof(*addr); i++) {
+      struct sockaddr_in *s = (struct sockaddr_in  
*)(addr[i].offset+(char *)il);
+
+      if (!addr[i].flag || (il->flags & addr[i].flag))
+        xprintf(" %s:%s ", addr[i].name,
+          (s->sin_family == 0xFFFF || !s->sin_family) ? "[NOT SET]" :
+            inet_ntoa(s->sin_addr));
+    }
+

This includes the inlined version of show_ip_addr(), so the function  
can go away.

While I'm there:

-    char *name = "unspec";

-    if (af == AF_INET) name = "inet";
-    else if (af == AF_INET6) name = "inet6";
-    xprintf("%10c%s", ' ', name);
+    xprintf("%10c%s", ' ',
+      (af == AF_INET) ? "inet" : (af == AF_INET6) ? "inet6" :  
"unspec");

It's really another way of saying the same thing, but in half as many  
lines. You can go overboard putting too much on one line (especially in  
languages like perl that pretty much start that way), but there's an  
advantage to being able to see more code on the screen at once. It's  
all tradeoffs...

--- hex_to_binary and set_hw_address

Scrolling over the function looking for the next bite-sized chunk, I  
see hex_to_binary(). Another function called from one place: in  
set_hw_address().

So it can be inlined, and also there are several library functions that  
can convert hex strings to integers, but let's work out what  
hex_to_binary() does:

Take an ascii representation of a big hex number in char *hw_addr, and  
write the data out into struct sockaddr *sock field sa_data. The data  
is written out big-endian, on all targets. (Is that right for  
little-endian x86? Testing... Yes, apparently so.) The length field is  
checked against ETH_ALEN and INFINIBAND_ALEN, but the only caller is  
doing:

   if(hex_to_binary(hw_addr, &sock, hw_class == 1 ? ETH_ALEN :  
INFINIBAND_ALEN))

So we're parsing/validating data we supplied. Sanity-checking yourself  
this way generally isn't useful, if the code's wrong fix it. If there's  
enough code you can't tell it's wrong or not, simplify.

The colons in the string we're parsing are optional, it accepts strings  
with an without them. Bit tricker to use standard functions then.  
Hmmm...

First, we delete the blank line before "char *ptr" and put it  
afterwards, to collate the local variable declarations into a space  
separated block at the top. (That's just basic code hygiene.) And a  
space after the address type checking block.

The address checking block can get moved into the caller, and all that  
setup code at the start of set_hw_address() simplified. The old code:

   char *hw_addr;
   struct sockaddr sock;
   char *ptr;
   char *hw_class_strings[] = {
       "ether",
       "infiniband",
       NULL
   };

   if(strcmp(hw_class_strings[0], **argv) == 0)
     hw_class = 1;
   else if(strcmp(hw_class_strings[1], **argv) == 0)
     hw_class = 2;
   if(!hw_class || !(*argv += 1)) {
     errno = EINVAL;
     toys.exithelp++;
     error_exit("bad hardware class");
   }

   memset(&sock, 0, sizeof(struct sockaddr));
   hw_addr = **argv;
   if(hex_to_binary(hw_addr, &sock, hw_class == 1 ? ETH_ALEN :  
INFINIBAND_ALEN))

becomes:

   char *hw_addr;
   struct sockaddr sock;

   memset(&sock, 0, sizeof(struct sockaddr));
   if (!strcmp("ether", **argv)) sock.sa_family = ARPHRD_ETHER;
   else if (!strcmp("infiniband", **argv)) sock.sa_family =  
ARPHRD_INFINIBAND;
   else {
     toys.exithelp++;
     error_exit("bad hw '%s'", **argv);
   }
   hw_addr = *(++*argv);

   if(hw_addr && hex_to_binary(hw_addr, &sock)

For once the table was overkill: there's just two entries and we didn't  
even loop over them. (By the way, you don't need a NULL terminator for  
an array, you can do sizeof(table)/sizeof(*table). Divide the size of  
the table (in bytes) by the size of a member of the table (in bytes) to  
get the number of entries.)

We also don't need the intermediate variable hw_class, since  
sock->sa_family holds the same info, and the "else" case naturally  
tells us we didn't match either of the first two comparisons. The  
hw_addr null check falls through to error_exit that does a printf("%s",  
hw_addr) but libc handles that case with a [NULL] indicator already...  
ah, but posix doesn't _require_ it to.

Grumble... Ok, add a ? : in the error_exit() for portability. (To what,  
I couldn't tell you...) Make *ptr go away since it's only used once on  
the line immediately following its assignment.

And while we're at it, this is wrong:

   memcpy( ((char *) ifre) + offsetof(struct ifreq, ifr_hwaddr),
     &sock, sizeof(struct sockaddr));

Why have a local struct sockaddr at all when there's already one in the  
ifre that's passed into us and we memcpy() this one over it? And why  
are we doing offsetof and adding when we can just take the address of  
ifre->ifr_hwaddr? Right, simplify _that_, sock is now a pointer to  
&ifre->ifr_hwaddr and no memcpy() necessary.

Anyway, back to hex_to_binary, it now looks like:

static int hex_to_binary(char *hw_addr, struct sockaddr *sock)
{
   int count = (sock->sa_family==ARPHRD_ETHER) ? 6 : 20;
   char *ptr = (char *) sock->sa_data, *p = ptr;

   while (*hw_addr && (p-ptr) < count) {
     int val, len = 0;

     if (*hw_addr == ':') hw_addr++;
     sscanf(hw_addr, "%2x%n", &val, &len);
     if (len != 2) break;
     hw_addr += len;
     *p++ = val;
   }

   return (p-ptr) != count || *hw_addr;
}

Toybox is built with -funsigned-char so we don't need to say "unsigned  
char".

Use sscanf() to parse pairs of hex digits, and consider not having  
parsed exactly two digits a failure. No need for an integer index to  
check loop length again, save the starting pointer and do pointer  
subtraction to get the length. (Note: output pointer, not input  
pointer. The colons don't cout.) And check at the end that we _did_  
grab exactly the right number of digits, and just use the actual  
numerical constants that can't change. (So we don't have to #define one  
of them up top to use it exactly once here... Is an infiniband address  
_really_ 40 hexadecimal digits?) The return value is just zero or  
nonzero, doesn't have to specifically be -1.

Now: inline hex_to_binary() at the one callsite. The variable  
declarations move up to the top but the initializations of them move  
down (default count to 6 and then assign to 20 in the if(infiniband)  
body, which now needs curly brackets; the rest of the initialization  
has to take place after "sock" is assigned).

Sanity check: sa_data is a member of a zeroed structure, which we're  
grabbing and using as a pointer. Confirm that it's an array, not a  
pointer, and thus doesn't need to be initialized... ok.

And of course the return value at the end becomes the if () that  
potentially calls error_exit().

Now, inline set_hw_address() in ifconfig_main(). Re-indent everything  
(this whole section is indented two off, I need to fix that later).  
Adjust ***argv to be just **argv (one less dereference level on each  
use), and ifre is a struct instead of a pointer now, and once again  
swap in the xioctl() value (_WHY_ were those arguments passed down from  
the caller?).

And another stopping point; ifconfig.c is now 724 lines


More information about the Toybox mailing list