[Toybox] Explaining the ifconfig cleanup: part I.

Rob Landley rob at landley.net
Tue Apr 9 00:07:35 PDT 2013


I blogged a bit (http://landley.net/notes-2013.html#31-03-2013) about  
how I'd like more cleanup submissions. Possibly if I explain the kind  
of cleanup I'm going for, it might help with that.

When ifconfig was submitted, it touched a half-dozen files. I glued it  
together into a single self-contained file, which needs a lot of  
cleanup.

The reason I collected the half-dozen files is that nothing else is  
using any of that code yet. I try to avoid what I call "infrastructure  
in search of a user". Putting code into common directories should wait  
until it has more than one command actually calling it.

Another thing I'm trying to do is collect code together so functions  
and their callers are in physical proximity, because that lets you  
inline, untangle, and simplify. For example, code that does:

   #define CONSTANT 42
   printf("%d", CONSTANT);

There's no reason for the #define, you can just substitute it straight  
in the only place it's ever used. But if define is far away, you can't  
always see that it's not needed.

Defines are actually a specific category of bloat, because people like  
to pull values out and collect them together in what they think of as  
configuration sections. But this isn't always an improvement. Asking  
whether this config knob will ever be changed, and even if it is would  
it be any harder to find it inline in one or two places that actually  
use it than figuring out you need to change the config block? (Which  
involves reading the code and understanding what it's doing anyway to  
know that changing the #define is enough to change the behavior, and  
there's not some other hardwired assumption that the change breaks?)

Let's look at an example:

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

Commit 843 was a recent cleanup to ifconfig. The first hunk I changed  
is:

--- a/toys/pending/ifconfig.c   Thu Apr 04 19:39:44 2013 -0500
+++ b/toys/pending/ifconfig.c   Thu Apr 04 20:27:08 2013 -0500
@@ -37,14 +37,6 @@
  #include <net/ethernet.h>
  #include <alloca.h>

-#define TRUE ((int) 1)
-#define HTTP_DEFAULT_PORT 80
-#define FTP_DEFAULT_PORT 21
-
-#ifndef MAX_PORT_VALUE
-#define MAX_PORT_VALUE 65535
-#endif
-

Let's look at that. TRUE is 1. This is built into C. (c99 actually says  
that any nonzero value is true in a comparison function like if (), but  
that logical operators && and || and == will return 0 or 1. So you can  
pull tricks like rewriting:

   if (y == 4) x = 20;
   else x = 7;
   mangle(thingy, x);

as
   mangle (thingy, 7+(y==4)*13);

And it's actually portable on a c99 compiler. Whether or not it's  
better code is complicated (digression warning: this parenthetical is  
irrelevant. Modern processors rely heavily on speculative execution to  
keep multiple cores busy and work around prefetch stalls and other  
pipeline bubbles, and avoiding branches makes speculative execution  
much more efficient because if it guesses the wrong branch it wastes  
work it has to discard. But in this case a modern processor probably  
has conditional assignment instructions, although more power efficient  
architectures do less speculative execution because wasted work still  
comsumes power so avoiding it slows absolute speed but increases  
battery efficiency. But if you're plugged into wall current you can  
statistically turn some of those wasted cycles into performance by  
being lucky guessing what comes next.)

The reason _I_ like the one line version instead of the three line  
version is it's a lot less code to read. If you fit more on the screen  
it's easier to keep what it's doing in your head, because it's right  
there in front of you instead of something you have to flip back and  
forth to follow.

Going back to the #define TRUE: in theory there's probaby some header  
we could #include to get this, but there turned out to be exactly one  
user, resulting in this hunk to remove it:

@@ -1483,7 +1419,7 @@
                 perror_msg("error: no inet socket available");
                 return -1;
         }
-       while(TRUE) {
+       for (;;) {
                 ifcon.ifc_len = sizeof(struct ifreq) * num_of_req;  
//Size of buffer.
                 ifcon.ifc_buf = xrealloc(ifcon.ifc_buf, ifcon.ifc_len);

Saying while (1) wouldn't be any less clear than while (TRUE) if you're  
at all familiar with C, and saying for (;;) lets you not have a test at  
all. (The compiler edits out constant true/false tests, but still. I  
find it cleaner to not have them if they're not doing anything.)

Going back to the rest of that #define block: HTTP_DEFAULT_PORT and  
FTP_DEFAULT_PORT are 20 year old standardized values. This isn't a  
config knob anybody can ever change. same for MAX_PORT_VALUE, it's  
hardwired for both IPv4 and IPv6, and changing this code to work with  
other packet procols is pretty much a complete rewrite anyway.

The two DEFAULT_PORT macros are each only used once, in a function that  
is never called from anywhere, nor is the one after it. Easy cleanup  
that: remove the lot of it. (It's easy enough to put back if something  
does start using it. And if so, just use the constants. If they really  
need a comment to explain that 80 is HTTP, add a comment, but if you're  
doing network stuff that may be common knowledge by now and what the  
function was doing was strcmp(blah, "http") and strcmp(blah, "ftp") so  
it's there in the code without needing a comment anyway. So if we  
hadn't removed the function, the defines still would have gone away.)

MAX_PORT_VALUE was used twice, both times to sanity check a command  
line input. There's range checking built into the lib/args.c option  
parsing stuff, you can go "#<1>65535". I dunno if it applies here, but  
a todo item would be to check that. Also, why is ifconfig dealing with  
port numbers? For now just swap in the constants and make the #define  
go away.

And that's one cleanup pass.

Rob



More information about the Toybox mailing list