Date: Thu, 8 Dec 2011 22:13:04 +1100 (EST) From: Bruce Evans <brde@optusnet.com.au> To: Pawel Jakub Dawidek <pjd@FreeBSD.org> Cc: svn-src-head@FreeBSD.org, svn-src-all@FreeBSD.org, src-committers@FreeBSD.org, Eitan Adler <eadler@FreeBSD.org> Subject: Re: svn commit: r228342 - head/contrib/tzcode/zic Message-ID: <20111208200957.F1091@besplex.bde.org> In-Reply-To: <20111208073210.GC1678@garage.freebsd.pl> References: <201112080240.pB82ek4Q066638@svn.freebsd.org> <20111208073210.GC1678@garage.freebsd.pl>
next in thread | previous in thread | raw e-mail | index | archive | help
On Thu, 8 Dec 2011, Pawel Jakub Dawidek wrote: > On Thu, Dec 08, 2011 at 02:40:46AM +0000, Eitan Adler wrote: >> Log: >> - set progname for use in usage() >> >> Modified: head/contrib/tzcode/zic/zdump.c >> ============================================================================== >> --- head/contrib/tzcode/zic/zdump.c Thu Dec 8 00:56:23 2011 (r228341) >> +++ head/contrib/tzcode/zic/zdump.c Thu Dec 8 02:40:46 2011 (r228342) >> @@ -260,6 +260,7 @@ char * argv[]; >> register struct tm * tmp; >> register struct tm * newtmp; >> >> + progname=argv[0]; >> INITIALIZE(cutlotime); >> INITIALIZE(cuthitime); >> #if HAVE_GETTEXT > > It will good to to try to upstream it as this is contributed code. > This change doesn't seem to be consistent with the style of this file. > They use spaces around =. Also, if this were not contrib'ed code, then using progname in it would involve 2 layers of style bugs: - user-visible layer: in FreeBSD, the program name printed by usage() is literal and doesn't depend on the pathname with which the program was invoked. In old versions of zdump in FreeBSD, one of the main changes was to convert it to FreeBSD style. Old versions were also missing the uninitialization of progrname, even in the vendor version: % Index: zdump.c % =================================================================== % RCS file: /home/ncvs/src/usr.sbin/zic/zdump.c,v % retrieving revision 1.1.1.4 % retrieving revision 1.9 % diff -r1.1.1.4 -r1.9 % ... % 130d132 % < static char * progname; % 131a134 % > static void usage(void); % 160d162 % < progname = argv[0]; % 163,164c165 % < (void) printf("%s\n", elsieid); % < (void) exit(EXIT_SUCCESS); % --- % > errx(EXIT_SUCCESS, "%s", elsieid); % 174,177c175 % < (void) fprintf(stderr, % < _("%s: usage is %s [ --version ] [ -v ] [ -c cutoff ] zonename ...\n"), % < argv[0], argv[0]); % < (void) exit(EXIT_FAILURE); % --- % > usage(); % 204,207c202,205 % < (fakeenv[0] = (char *) malloc(longest + 4)) == NULL) { % < (void) perror(progname); % < (void) exit(EXIT_FAILURE); % < } % --- % > (fakeenv[0] = (char *) malloc((size_t) (longest + % > 4))) == NULL) % > errx(EXIT_FAILURE, % > _("malloc() failed")); % 266,270c264,265 % < if (fflush(stdout) || ferror(stdout)) { % < (void) fprintf(stderr, "%s: ", argv[0]); % < (void) perror(_("Error writing standard output")); % < (void) exit(EXIT_FAILURE); % < } % --- % > if (fflush(stdout) || ferror(stdout)) % > errx(EXIT_FAILURE, _("error writing standard output")); % 277a273,280 % > static void % > usage(void) % > { % > fprintf(stderr, % > _("usage: zdump [--version] [-v] [-c cutoff] zonename ...\n")); % > exit(EXIT_FAILURE); % > } This change also breaks the vendor style, since it doesn't use an excessive (void) cast on the fprintf() in usage(). The other changes mostly involve changing perror() and [f]printf() to errx(). This is a user-visible change if the [f]printf() was to stdout instead of stderr, and perhaps if errx() gives better formatting than perror(). The current version uses the unportable errx() instead of perror() plus exit(), but still uses [f]printf() instead of warnx() when not exiting. I can't see any vendor version of it using cvs. - FreeBSD and NetBSD programs never use progname = argv[0] or use progname explicitly. They use getprogname(3). This is implemented by setting a variable __progname to argv[0] in crt. The setting may be changed by setprogname(3). This gives minor inconsistencies which may be considered features: usage() always prints the literal program name, while the errx() family uses getprogname() and this prints argv[0] or whatever setprogname() changed __progname too. Grepping for progname in /usr/src/*bin shows only a few violations of the above rules: % bin/chio/chio.c: "<from ET> <from EU> <to ET> <to EU> [inv]\n", getprogname(), cname); % bin/chio/chio.c: getprogname(), cname); % bin/chio/chio.c: getprogname(), cname); % bin/chio/chio.c: (void) fprintf(stderr, "usage: %s %s\n", getprogname(), cname); % bin/chio/chio.c: (void) fprintf(stderr, "usage: %s %s\n", getprogname(), cname); % bin/chio/chio.c: (void) fprintf(stderr, "usage: %s %s <picker>\n", getprogname(), cname); % bin/chio/chio.c: getprogname(), cname); % bin/chio/chio.c: getprogname(), cname); % bin/chio/chio.c: getprogname(), cname); % bin/chio/chio.c: "<from ET> <from EU>\n", getprogname(), cname); % bin/chio/chio.c: "arg1 arg2 [arg3 [...]]\n", getprogname()); chio doesn't look like FreeBSD sources. It came from NetBSD. % bin/ps/ps.c: getprogname(), *argv); Someone broke ps. It is internally inconsistent: it prints getprogname() here, and then immediately calls usage() which prints a literal "ps". % bin/pkill/pkill.c: if (strcmp(getprogname(), "pgrep") == 0) { % bin/pkill/pkill.c: " [-t tty] [-u euid] pattern ...\n", getprogname(), pkill can be a link to pgrep. This is the only excuse for using getprograme() in usage(). __progname is actually initialized to an edited version of argv[0], with the path prefix stripped. Thus if pkill is copied to ./cat, and run from any path leading to ./cat, the above bogusly prints that its name is `cat'. When pkill is invoked under any other name that pgrep, it defaults to acting as pkill, but it doesn't change its name to pkill so what it is acting as is hard to tell from its error messages if its name is not pkill. Before getprogname() existed, usage() would probably have printed a name that matched its action using a flag set when its action was determined (acting_as_pgrep ? "pgrep" : "pkill"). Using getprogname(), the logic should probably be that if the action is defaulted then the name should be changed to match the defaulted action. But it would be clearer to default to an error, with a special usage message. This the existence of getprogname() doesn't simplify printing of the active program name in usage() for programs whose action depends on their name. % bin/echo/echo.c: char *progname = argv[0]; % bin/echo/echo.c: errexit(progname, "malloc"); % bin/echo/echo.c: errexit(progname, "write"); echo has contortions to avoid using err() since that might break sh when echo is sh's builtin. It should use getprogname() here. IIRC, this was implemented before gteprogname() existed. % sbin/newfs/newfs.c: getprogname(), SOmeone broken newfs. Fixed in my version. % sbin/fsck_ffs/main.c: getprogname()); % sbin/fsck/fsutil.c: dev, getprogname()); % sbin/fsck/fsck.c: getprogname(), common); This is probably from NetBSD> % sbin/dhclient/dhclient.c: extern char *__progname; % sbin/dhclient/dhclient.c: openlog(__progname, LOG_PID | LOG_NDELAY, DHCPD_LOG_FACILITY); % sbin/dhclient/dhclient.c: extern char *__progname; % sbin/dhclient/dhclient.c: fprintf(stderr, "usage: %s [-bdqu] ", __progname); Chumminess with the implementation. % sbin/mdmfs/mdmfs.c: if (strcmp(getprogname(), "mount_mfs") == 0 || % sbin/mdmfs/mdmfs.c: strcmp(getprogname(), "mfs") == 0) { % sbin/mdmfs/mdmfs.c:"\tmd-device mount-point\n", getprogname()); Because it is sometimes named mount_mfs, and this is supported (like pkill/pgrep). % sbin/hastctl/hastctl.c: getprogname()); % sbin/hastctl/hastctl.c: getprogname()); % sbin/hastctl/hastctl.c: getprogname()); % sbin/hastctl/hastctl.c: getprogname()); Apparently just style bugs. % sbin/swapon/swapon.c: getprogname(), % sbin/swapon/swapon.c: getprogname(), which_prog == SWAPOFF ? "remov" : "add", % sbin/swapon/swapon.c: fprintf(stderr, "usage: %s ", getprogname()); Because it is sometimes named swapoff or swapctl. % sbin/devfs/rule.c: setprogname("devfs rule"); % sbin/devfs/rule.c: setprogname("devfs ruleset"); Change to a strange program name with spaces in it. This name would be hard to parse if it is actually used. % sbin/md5/md5.c: const char *progname; % sbin/md5/md5.c: const char* progname; % sbin/md5/md5.c: if ((progname = strrchr(argv[0], '/')) == NULL) % sbin/md5/md5.c: progname = argv[0]; % sbin/md5/md5.c: progname++; Home made version of getprogname(). Probably written before the latter existed. % sbin/md5/md5.c: if (strcasecmp(Algorithm[digest].progname, progname) == 0) % sbin/md5/md5.c: fprintf(stderr, "usage: %s [-pqrtx] [-s string] [files ...]\n", alg->progname); Because it can have various names. [... 249 more lines] Got bored here. The above examples are enough to show that the FreeBSD style is to use getprogname() in usage() if and only if the program can have various _supported_ names (not unsupported names like a copy of /bin/rm to ./cat would have). Oops. I chose rm for maximal danger when it is renamed to a harmless command. But rm is linked to unlink. Its logic for determining its action and name are slightly broken, as for pkill/pgrep but more: - to determine whether it should act like unlink, it uses a home made getprogname(). - when its name is neither rm nor unlink, it acts like rm - when its name is unlink, it normally doesn't call usage(), so if there is an error then its name is reported as being unlink. However, it calls usage() if it has any option or no args. usage() doesn't use getprogname(), so it then prints the usage message for rm, including rm's literal name. But usage() prints the message for unlink too. usage() always prints both messages. This gives noise more for the case where the program is just rm. Related bug: the program name returned by getprogname() is initialized as if by setprogname(argv[0]). pkill.c depends on this, but this is not documented in getprogname(3). It is documented that setprogname() sets the program name to the last component of its arg, but not that initialization uses either argv[0] or setprogname() on it. The man page also says that getprogname() manipulates the program name. These bugs are fixed or moot in NetBSD's man page, but the semantics of setprogname() are very different in NetBSD (at least in 2005). In NetBSD, setprogname() is always called from crt (as in FreeBSD), but subsequent calls have no effect. It is recommended that all programs start with a call to setprogname() in main(), just for portability to systems that don't call it automatically. For systems that don't have it at all, these calls can be implemented in a special library or annulled by a macro. It's amazing how complicated keeping track of your own name is when you use a library/bureauocracy that might not exist to do it :-). Bruce
Want to link to this message? Use this URL: <https://mail-archive.FreeBSD.org/cgi/mid.cgi?20111208200957.F1091>