[Toybox] [CLEANUP] Next ifconfig pass (commit 905)

Rob Landley rob at landley.net
Sun May 19 09:44:40 PDT 2013


Cleaning up ifconfig, commit 905:

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

get_strtou() isn't actually needed: it's a wrapper for strtoul() that's  
only ever called once. It sets most of its effort setting errno, but  
all you need to know is that the endptr from strtou isn't the end of  
the string. So the one and only caller can just become:

+    unsigned long p = strtoul(s, &ss, 0);
+    if (*ss || p > 65535) error_exit("bad port '%s'", s);

Using the libc function, and then get_strtou() can go away.

Two more functions, is_host_unix() and get_host_and_port(), are only  
ever called from one place, both in get_sockaddr(). so inline both of  
them.

The is_host_unix() logic only triggers if host starts with "local:", so  
wrap the function's guts in a big if() for that. The rest of the  
function is mostly the same except that swl is just a pointer instead  
of a pointer to a pointer, so dereference one less level.

Next we have the get_host_and_port() plumbing, which is a bit tricky  
for me because I don't have a strong understand of ipv6, haven't used  
it much. I'd really like some test cases, a near-term todo item is  
probably to do a
scripts/test/ifconfig.test and work out an aboriginal linux test  
environment to run it in.

So the function was:

static void get_host_and_port(char **host, int *port)
{
   char *ch_ptr;
   char *org_host = *host;
   if (*host[0] == '[') {
     (*host)++;
     ch_ptr = strchr(*host, ']');
     if (!ch_ptr || (ch_ptr[1] != ':' && ch_ptr[1] != '\0'))
       error_exit("bad address '%s'", org_host);
   } else {
     ch_ptr = strrchr(*host, ':');
     //There is more than one ':' like "::1"
     if(ch_ptr && strchr(*host, ':') != ch_ptr) ch_ptr = NULL;
   }
   if (ch_ptr) { //pointer to ":" or "]:"
     int size = ch_ptr - (*host) + 1;
     xstrncpy(*host, *host, size);
     if (*ch_ptr != ':') {
       ch_ptr++; //skip ']'
       //[nn] without port
       if (!*ch_ptr) return;
     }
     ch_ptr++; //skip ':' to get the port number.
     *port = get_strtou(ch_ptr, NULL, 10);
     if (errno || (unsigned)*port > 65535) error_exit("bad port '%s'",  
org_host);
   }
}

The logic boils down to: if the first character of host is '[' then we  
have a "[blah]" address format that may be "[blah]:port". The strange  
part is that although we adjust host to point to the first character  
within the brackets, we don't turn the ] into a null terminator? So I  
dunno who's going to be using this later? (Something to watch out for.)

If host didn't start with a [ then look for one colon. If we have more  
than one colon then it's ipv6 again, but if we have exactly one it's  
host:port.

Then at the end we parse the port number and barf if there was trailing  
garbage of if it's over 65k. (Port number of 0 is apparently fine  
though.)

So inlined with a "char *s;" up at the top of the function, that's:

+  if (*host == '[') {
+    host++;
+    s = strchr(host, ']');
+    if (s && !s[1]) s = 0;
+    else {
+      if (!s || s[1] != ':') error_exit("bad address '%s'", host-1);
+      s++;
+    }
+  } else {
+    s = strrchr(host, ':');
+    if (s && strchr(host, ':') != s) s = 0;
+  }
+
+  if (s++) {
+    char *ss;
+    unsigned long p = strtoul(s, &ss, 0);
+    if (*ss || p > 65535) error_exit("bad port '%s'", s);
+    port = p;
+  }

Minor tweaks to reset_flags() along the way (it's going away once all  
the instances are inlined), but wordwrap the arguments and make the &=  
and |= logic look like the inlined version.

Inline set_mtu(), which has exactly one caller and is just two lines  
anyway.

set_address() doesn't use its req_name argument: xioctl() took care of  
that; the error message isn't quite as good but it's simpler code and  
end-users won't know what SIOWTFOMGBBQ means without looking it up  
anyway. The important error message is "it didn't work".

While I was there, replace the IPV6_ADDR_* checks with a loop over an  
array of possible error message names (using an unfortunately clever  
(!!j)<<(j+3) to produce all the macro values in order), and then  
collate it all into a single xprintf(). This is taking advantage of  
Linux's binary backwards compatability: if you know what the macros  
resolve to, and it doesn't vary by architecture, you can rely on that  
because the kernel changing it would break existing binaries. Since  
that was the only user of the IPV6_ADDR_* macros, remove the #defines  
from the top of the file.

Down in ifconfig_main(), suck some more of the if/else staircase into  
the big table and loop. Add another field to the table indicating the  
set_address() call to make (only in the positive case, not in the  
starts-with-minus case), and use that to incorporate pointtopoint,  
broadcast, netmask, and dstaddr.

Add a guard so the SIOCGIFFLAGS stuff only gets called if there's a  
change to make, since netmask and dstaddr won't.

A further modification I'd _like_ to do is collapsing that new field  
into the second field of flags[], since it's always 0 when that's set.  
I note that all the macros are 0x89XX and "man ioctl_list" actually  
gives a reason for this: 0x89 is the "type" for socket calls, using the  
old _IO(type,nr) macros. Given that in terms of IFF_FLAGS 0x8900 would  
be IFF_DYNAMIC|IFF_SLAVE|IFF_PROMISC (I.E. an impossible flags  
combination), I could just test that flags[1]|0xFF=0x89FF and not have  
it be a separate field.

I haven't done this yet because it's one of those "being clever" vs  
"bigger code" tradeoffs that I'm not quite comfortable with, but I  
might do it in a future pass.

Back down in the if/else staircase, I also reordered "metric" and  
"txqueuelen" so all the commands that boil down to "set an ifre field,  
call xioctl()" are grouped together, and all the ones that call some  
function are together. I put some whitespace gaps around 'em to make  
the groups easier to spot.

Down at the end, the interface name parsing: this is_colon stuff is  
basically a manual inlined strchr(). I replaced it with a strchr().

This commit drops us down to 795 lines, and there's still more to do.

Rob


More information about the Toybox mailing list