new NOFORKs: pwdx,kill[all5],ttysize,realpath,readlink NOEXECs: date,resize

function                                             old     new   delta
run_nofork_applet                                    258     280     +22
readlink_main                                        112     123     +11
------------------------------------------------------------------------------
(add/remove: 0/0 grow/shrink: 2/0 up/down: 33/0)               Total: 33 bytes

Signed-off-by: Denys Vlasenko <vda.linux@googlemail.com>
This commit is contained in:
Denys Vlasenko 2017-08-03 19:00:01 +02:00
parent 663ae52676
commit 39194f0309
11 changed files with 106 additions and 93 deletions

View file

@ -1,11 +1,10 @@
Why an applet can't be NOFORK or NOEXEC? Why an applet can't be NOFORK or NOEXEC?
Why can't be NOFORK: Why can't be NOFORK:
daemon: runs indefinitely
interactive: may wait for user input, ^C has to work interactive: may wait for user input, ^C has to work
spawner: "tool PROG ARGS" which changes program's environment - must fork spawner: "tool PROG ARGS" which changes program's environment - must fork
changes state: e.g. environment, signal handlers changes state: e.g. environment, signal handlers
runner: sometimes may run for long time, and/or works with network: runner: sometimes may run for long(ish) time, and/or works with network:
^C has to work (cat BIGFILE, chmod -R, ftpget, nc) ^C has to work (cat BIGFILE, chmod -R, ftpget, nc)
"runners" can become eligible after hush is taught ^C to interrupt NOFORKs! "runners" can become eligible after hush is taught ^C to interrupt NOFORKs!
@ -15,9 +14,12 @@ suid: runs under different uid - must fork+exec
Why shouldn't be NOFORK/NOEXEC: Why shouldn't be NOFORK/NOEXEC:
complex: no immediately obvious reason why NOFORK wouldn't work, complex: no immediately obvious reason why NOFORK wouldn't work,
but does some non-obvoius operations (example: fuser, lsof, losetup). but does some non-obvoius operations (example: fuser, lsof, losetup);
for NOFORK, nested xmallocs (typical in complex code) is a problem. nested xmallocs (typical in complex code) is a problem for NOFORK
rare: not used often enough to bother optimizing (example: poweroff) rare: not used often enough to bother optimizing (example: poweroff)
longterm: often runs for a long time (many seconds), execing would make
memory footprint smaller
daemon: runs indefinitely
[ - NOFORK [ - NOFORK
[[ - NOFORK [[ - NOFORK
@ -31,7 +33,7 @@ arch - NOFORK
arp arp
arping - runner arping - runner
ash - interactive ash - interactive
awk - noexec, runner awk - noexec. runner
base64 - runner base64 - runner
basename - NOFORK basename - NOFORK
beep beep
@ -44,63 +46,63 @@ bunzip2 - runner
busybox busybox
bzcat - runner bzcat - runner
bzip2 - runner bzip2 - runner
cal cal - runner: cal -n9999
cat - runner cat - runner
chat chat
chattr - runner chattr - runner
chgrp - noexec, runner chgrp - noexec. runner
chmod - noexec, runner chmod - noexec. runner
chown - noexec, runner chown - noexec. runner
chpasswd - runner (list of "user:password"s from stdin) chpasswd - runner (list of "user:password"s from stdin)
chpst - spawner chpst - spawner
chroot - spawner chroot - spawner
chrt - spawner chrt - spawner
chvt chvt
cksum - noexec, runner cksum - noexec. runner
clear - NOFORK clear - NOFORK
cmp - runner cmp - runner
comm - runner comm - runner
conspy - interactive conspy - interactive
cp - noexec, runner cp - noexec. runner
cpio - runner cpio - runner
crond - daemon crond - daemon
crontab crontab
cryptpw cryptpw
cttyhack - spawner cttyhack - spawner
cut - noexec, runner cut - noexec. runner
date date - noexec. nofork candidate(needs to stop messing up env, free xasprintf result, not use xfuncs after xasprintf)
dc - runner (eats stdin if no params) dc - runner (eats stdin if no params)
dd - noexec, runner dd - noexec. runner
deallocvt deallocvt
delgroup delgroup
deluser deluser
depmod depmod
devmem devmem - runner, complex (access to device memory may hang)
df df - complex (nested allocs)
dhcprelay - daemon dhcprelay - daemon
diff - runner diff - runner
dirname - NOFORK dirname - NOFORK
dmesg dmesg - runner
dnsd - daemon dnsd - daemon
dnsdomainname dnsdomainname - DNS resolution may trigger, need ^C
dos2unix - noexec, runner dos2unix - noexec. runner
dpkg - runner dpkg - runner
du du - runner
dumpkmap dumpkmap
dumpleases dumpleases
echo - NOFORK echo - NOFORK
ed - interactive ed - interactive
egrep - runner egrep - runner
eject eject
env - noexec, changes state (env) env - noexec. changes state (env)
envdir - spawner envdir - spawner
envuidgid - spawner envuidgid - spawner
expand - runner expand - runner
expr expr - complex (nested allocs)
factor - runner (eats stdin if no params) factor - runner (eats stdin if no params)
fakeidentd - daemon fakeidentd - daemon
false - NOFORK false - NOFORK
fatattr fatattr - complex (xopen+xioctl can leak fd)
fbset fbset
fbsplash - runner, interactive fbsplash - runner, interactive
fdflush fdflush
@ -108,15 +110,15 @@ fdformat - runner
fdisk - interactive fdisk - interactive
fgconsole fgconsole
fgrep - runner fgrep - runner
find - noexec, runner find - noexec. runner
findfs - suid findfs - suid
flash_eraseall flash_eraseall
flash_lock flash_lock
flash_unlock flash_unlock
flashcp flashcp
flock flock
fold - noexec, runner fold - noexec. runner
free free - nofork candidate(struct globals, needs to close /proc/meminfo fd)
freeramdisk freeramdisk
fsck - interactive fsck - interactive
fsck.minix fsck.minix
@ -134,12 +136,12 @@ groups - noexec
gunzip - runner gunzip - runner
gzip - runner gzip - runner
halt - rare halt - rare
hd - noexec, runner hd - noexec. runner
hdparm - complex, rare hdparm - complex, rare
head - noexec, runner head - noexec. runner
hexdump - noexec, runner hexdump - noexec. runner
hostid - NOFORK hostid - NOFORK
hostname hostname - DNS resolution may trigger, need ^C
httpd - daemon httpd - daemon
hush - interactive hush - interactive
hwclock hwclock
@ -169,11 +171,11 @@ iproute
iprule iprule
iptunnel iptunnel
kbd_mode kbd_mode
kill kill - NOFORK
killall killall - NOFORK
killall5 killall5 - NOFORK
klogd - daemon klogd - daemon
last last - runner (I've got 1300 lines of output when tried it)
less - interactive less - interactive
link - NOFORK link - NOFORK
linux32 - spawner linux32 - spawner
@ -189,7 +191,7 @@ losetup - complex
lpd - daemon lpd - daemon
lpq - runner lpq - runner
lpr - runner lpr - runner
ls - noexec, runner ls - noexec. runner
lsattr lsattr
lsmod lsmod
lsof - complex lsof - complex
@ -203,7 +205,7 @@ lzopcat - runner
makedevs makedevs
makemime - runner makemime - runner
man - spawner, interactive man - spawner, interactive
md5sum - noexec, runner md5sum - noexec. runner
mdev - daemon mdev - daemon
mesg mesg
microcom - interactive, complex microcom - interactive, complex
@ -225,11 +227,11 @@ mount - suid
mountpoint mountpoint
mpstat mpstat
mt mt
mv mv - runner (can be noexec?)
nameif nameif
nbd-client nbd-client
nc - runner nc - runner
netstat netstat - runner with -c
nice - spawner nice - spawner
nl - runner nl - runner
nmeter - runner nmeter - runner
@ -240,40 +242,40 @@ od - runner
openvt - spawner openvt - spawner
partprobe partprobe
passwd - suid passwd - suid
paste - noexec, runner paste - noexec. runner
patch patch
pgrep pgrep - nofork candidate(xregcomp, procps_scan - are they ok?)
pidof pidof - nofork candidate(uses find_pid_by_name, is that ok?)
ping - suid, runner ping - suid, runner
ping6 - suid, runner ping6 - suid, runner
pipe_progress pipe_progress
pivot_root pivot_root
pkill pkill - nofork candidate(xregcomp, procps_scan - are they ok?)
pmap pmap
popmaildir - runner popmaildir - runner
poweroff - rare poweroff - rare
powertop - interactive powertop - interactive, longterm
printenv - NOFORK printenv - NOFORK
printf - NOFORK printf - NOFORK
ps ps
pscan pscan
pstree pstree
pwd - NOFORK pwd - NOFORK
pwdx pwdx - NOFORK
raidautorun raidautorun
rdate rdate
rdev rdev
readlink readlink - NOFORK
readprofile readprofile
realpath realpath - NOFORK
reboot - rare reboot - rare
reformime - runner reformime - runner
remove-shell remove-shell
renice renice - nofork candidate(uses getpwnam, is that ok?)
reset - spawner (execs "stty") reset - spawner (execs "stty")
resize resize - noexec. changes state (signal handlers)
rev - runner rev - runner
rm - noexec, rm -i interactive rm - noexec. rm -i interactive
rmdir - NOFORK rmdir - NOFORK
rmmod rmmod
route route
@ -289,7 +291,7 @@ script
scriptreplay scriptreplay
sed - runner sed - runner
sendmail - runner sendmail - runner
seq - noexec, runner seq - noexec. runner
setarch - spawner setarch - spawner
setconsole setconsole
setfont setfont
@ -300,22 +302,22 @@ setserial
setsid - spawner setsid - spawner
setuidgid setuidgid
sh - interactive sh - interactive
sha1sum - noexec, runner sha1sum - noexec. runner
sha256sum - noexec, runner sha256sum - noexec. runner
sha3sum - noexec, runner sha3sum - noexec. runner
sha512sum - noexec, runner sha512sum - noexec. runner
showkey - interactive showkey - interactive
shred - runner shred - runner
shuf - noexec, runner shuf - noexec. runner
slattach slattach
sleep - runner sleep - runner
smemcap - runner smemcap - runner
softlimit - spawner softlimit - spawner
sort - noexec, runner sort - noexec. runner
split - runner split - runner
ssl_client - network ssl_client - network
start-stop-daemon start-stop-daemon
stat stat - nofork candidate(needs fewer allocs)
strings - runner strings - runner
stty stty
su - suid, spawner su - suid, spawner
@ -326,11 +328,11 @@ svc
svlogd - daemon svlogd - daemon
swapoff - rare swapoff - rare
swapon - rare swapon - rare
switch_root - spawner, rare, change state switch_root - spawner, rare, changes state
sync - NOFORK sync - NOFORK
sysctl sysctl
syslogd - daemon syslogd - daemon
tac - noexec, runner tac - noexec. runner
tail - runner tail - runner
tar - runner tar - runner
taskset - spawner taskset - spawner
@ -341,9 +343,9 @@ telnetd - daemon
test - NOFORK test - NOFORK
tftp - runner tftp - runner
tftpd - daemon tftpd - daemon
time - spawner, change state (signals) time - spawner, changes state (signals)
timeout - spawner, change state (signals) timeout - spawner, changes state (signals)
top - interactive top - interactive, longterm
touch - NOFORK touch - NOFORK
tr - runner tr - runner
traceroute - suid, runner traceroute - suid, runner
@ -351,7 +353,7 @@ traceroute6 - suid, runner
true - NOFORK true - NOFORK
truncate - NOFORK truncate - NOFORK
tty - NOFORK tty - NOFORK
ttysize ttysize - NOFORK
tunctl tunctl
tune2fs tune2fs
ubiattach ubiattach
@ -370,14 +372,14 @@ uname - NOFORK
uncompress - runner uncompress - runner
unexpand - runner unexpand - runner
uniq - runner uniq - runner
unix2dos - noexec, runner unix2dos - noexec. runner
unlink - NOFORK unlink - NOFORK
unlzma - runner unlzma - runner
unlzop - runner unlzop - runner
unxz - runner unxz - runner
unzip - runner unzip - runner
uptime uptime - nofork candidate(is getutxent ok?)
users users - nofork candidate(is getutxent ok?)
usleep - NOFORK usleep - NOFORK
uudecode - runner uudecode - runner
uuencode - runner uuencode - runner
@ -395,10 +397,10 @@ which - NOFORK
who who
whoami - NOFORK whoami - NOFORK
whois whois
xargs - noexec, spawner xargs - noexec. spawner
xxd - noexec, runner xxd - noexec. runner
xz - runner xz - runner
xzcat - runner xzcat - runner
yes - noexec, runner yes - noexec. runner
zcat - runner zcat - runner
zcip - daemon zcip - daemon

View file

@ -23,7 +23,7 @@
//config: E.g.: //config: E.g.:
//config: COLUMNS=80;LINES=44;export COLUMNS LINES; //config: COLUMNS=80;LINES=44;export COLUMNS LINES;
//applet:IF_RESIZE(APPLET(resize, BB_DIR_USR_BIN, BB_SUID_DROP)) //applet:IF_RESIZE(APPLET_NOEXEC(resize, resize, BB_DIR_USR_BIN, BB_SUID_DROP, resize))
//kbuild:lib-$(CONFIG_RESIZE) += resize.o //kbuild:lib-$(CONFIG_RESIZE) += resize.o

View file

@ -58,7 +58,7 @@
//config: the same format. With it on, 'date DATE' additionally supports //config: the same format. With it on, 'date DATE' additionally supports
//config: MMDDhhmm[[YY]YY][.ss] format. //config: MMDDhhmm[[YY]YY][.ss] format.
//applet:IF_DATE(APPLET(date, BB_DIR_BIN, BB_SUID_DROP)) //applet:IF_DATE(APPLET_NOEXEC(date, date, BB_DIR_BIN, BB_SUID_DROP, date))
//kbuild:lib-$(CONFIG_DATE) += date.o //kbuild:lib-$(CONFIG_DATE) += date.o
@ -152,12 +152,6 @@ enum {
OPT_HINT = (1 << 6) * ENABLE_FEATURE_DATE_ISOFMT, /* D */ OPT_HINT = (1 << 6) * ENABLE_FEATURE_DATE_ISOFMT, /* D */
}; };
static void maybe_set_utc(int opt)
{
if (opt & OPT_UTC)
putenv((char*)"TZ=UTC0");
}
#if ENABLE_LONG_OPTS #if ENABLE_LONG_OPTS
static const char date_longopts[] ALIGN1 = static const char date_longopts[] ALIGN1 =
"rfc-822\0" No_argument "R" "rfc-822\0" No_argument "R"
@ -170,6 +164,19 @@ static const char date_longopts[] ALIGN1 =
; ;
#endif #endif
/* We are a NOEXEC applet.
* Obstacles to NOFORK:
* - we change env
* - xasprintf result not freed
* - after xasprintf we use other xfuncs
*/
static void maybe_set_utc(int opt)
{
if (opt & OPT_UTC)
putenv((char*)"TZ=UTC0");
}
int date_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE; int date_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
int date_main(int argc UNUSED_PARAM, char **argv) int date_main(int argc UNUSED_PARAM, char **argv)
{ {

View file

@ -20,7 +20,7 @@
//config: help //config: help
//config: Enable the readlink option (-f). //config: Enable the readlink option (-f).
//applet:IF_READLINK(APPLET(readlink, BB_DIR_USR_BIN, BB_SUID_DROP)) //applet:IF_READLINK(APPLET_NOFORK(readlink, readlink, BB_DIR_USR_BIN, BB_SUID_DROP, readlink))
//kbuild:lib-$(CONFIG_READLINK) += readlink.o //kbuild:lib-$(CONFIG_READLINK) += readlink.o
@ -85,6 +85,7 @@ int readlink_main(int argc UNUSED_PARAM, char **argv)
if (!(opt & 4)) /* not -v */ if (!(opt & 4)) /* not -v */
logmode = LOGMODE_NONE; logmode = LOGMODE_NONE;
/* NOFORK: only one alloc is allowed; must free */
if (opt & 1) { /* -f */ if (opt & 1) { /* -f */
buf = xmalloc_realpath(fname); buf = xmalloc_realpath(fname);
} else { } else {
@ -94,9 +95,7 @@ int readlink_main(int argc UNUSED_PARAM, char **argv)
if (!buf) if (!buf)
return EXIT_FAILURE; return EXIT_FAILURE;
printf((opt & 2) ? "%s" : "%s\n", buf); printf((opt & 2) ? "%s" : "%s\n", buf);
free(buf);
if (ENABLE_FEATURE_CLEAN_UP)
free(buf);
fflush_stdout_and_exit(EXIT_SUCCESS); fflush_stdout_and_exit(EXIT_SUCCESS);
} }

View file

@ -13,7 +13,7 @@
//config: Return the canonicalized absolute pathname. //config: Return the canonicalized absolute pathname.
//config: This isn't provided by GNU shellutils, but where else does it belong. //config: This isn't provided by GNU shellutils, but where else does it belong.
//applet:IF_REALPATH(APPLET(realpath, BB_DIR_USR_BIN, BB_SUID_DROP)) //applet:IF_REALPATH(APPLET_NOFORK(realpath, realpath, BB_DIR_USR_BIN, BB_SUID_DROP, realpath))
//kbuild:lib-$(CONFIG_REALPATH) += realpath.o //kbuild:lib-$(CONFIG_REALPATH) += realpath.o
@ -36,6 +36,7 @@ int realpath_main(int argc UNUSED_PARAM, char **argv)
} }
do { do {
/* NOFORK: only one alloc is allowed; must free */
char *resolved_path = xmalloc_realpath(*argv); char *resolved_path = xmalloc_realpath(*argv);
if (resolved_path != NULL) { if (resolved_path != NULL) {
puts(resolved_path); puts(resolved_path);

View file

@ -98,7 +98,7 @@ It itself calls run_nofork_applet(), if argv[0] turned out to be a name
of a NOFORK applet. of a NOFORK applet.
run_nofork_applet() saves/inits/restores option parsing, xfunc_error_retval, run_nofork_applet() saves/inits/restores option parsing, xfunc_error_retval,
applet_name. Thus, for example, caller does not need to worry about logmode, applet_name. Thus, for example, caller does not need to worry about
option_mask32 getting trashed. option_mask32 getting trashed.

View file

@ -31,9 +31,9 @@ struct group* FAST_FUNC xgetgrnam(const char *name)
return gr; return gr;
} }
struct passwd* FAST_FUNC xgetpwuid(uid_t uid) struct passwd* FAST_FUNC xgetpwuid(uid_t uid)
{ {
/* Note: used in nofork applets (whoami), be careful not to leak anything */
struct passwd *pw = getpwuid(uid); struct passwd *pw = getpwuid(uid);
if (!pw) if (!pw)
bb_error_msg_and_die("unknown uid %u", (unsigned)uid); bb_error_msg_and_die("unknown uid %u", (unsigned)uid);
@ -50,6 +50,7 @@ struct group* FAST_FUNC xgetgrgid(gid_t gid)
char* FAST_FUNC xuid2uname(uid_t uid) char* FAST_FUNC xuid2uname(uid_t uid)
{ {
/* Note: used in nofork applets (whoami), be careful not to leak anything */
struct passwd *pw = xgetpwuid(uid); struct passwd *pw = xgetpwuid(uid);
return pw->pw_name; return pw->pw_name;
} }

View file

@ -92,6 +92,7 @@ struct nofork_save_area {
void (*die_func)(void); void (*die_func)(void);
const char *applet_name; const char *applet_name;
uint32_t option_mask32; uint32_t option_mask32;
smallint logmode;
uint8_t xfunc_error_retval; uint8_t xfunc_error_retval;
}; };
static void save_nofork_data(struct nofork_save_area *save) static void save_nofork_data(struct nofork_save_area *save)
@ -100,6 +101,7 @@ static void save_nofork_data(struct nofork_save_area *save)
save->die_func = die_func; save->die_func = die_func;
save->applet_name = applet_name; save->applet_name = applet_name;
save->option_mask32 = option_mask32; save->option_mask32 = option_mask32;
save->logmode = logmode;
save->xfunc_error_retval = xfunc_error_retval; save->xfunc_error_retval = xfunc_error_retval;
} }
static void restore_nofork_data(struct nofork_save_area *save) static void restore_nofork_data(struct nofork_save_area *save)
@ -108,6 +110,7 @@ static void restore_nofork_data(struct nofork_save_area *save)
die_func = save->die_func; die_func = save->die_func;
applet_name = save->applet_name; applet_name = save->applet_name;
option_mask32 = save->option_mask32; option_mask32 = save->option_mask32;
logmode = save->logmode;
xfunc_error_retval = save->xfunc_error_retval; xfunc_error_retval = save->xfunc_error_retval;
} }
@ -118,8 +121,8 @@ int FAST_FUNC run_nofork_applet(int applet_no, char **argv)
save_nofork_data(&old); save_nofork_data(&old);
logmode = LOGMODE_STDIO;
xfunc_error_retval = EXIT_FAILURE; xfunc_error_retval = EXIT_FAILURE;
/* In case getopt() or getopt32() was already called: /* In case getopt() or getopt32() was already called:
* reset the libc getopt() function, which keeps internal state. * reset the libc getopt() function, which keeps internal state.
*/ */
@ -146,7 +149,6 @@ int FAST_FUNC run_nofork_applet(int applet_no, char **argv)
/* Restoring some globals */ /* Restoring some globals */
restore_nofork_data(&old); restore_nofork_data(&old);
/* Other globals can be simply reset to defaults */ /* Other globals can be simply reset to defaults */
GETOPT_RESET(); GETOPT_RESET();

View file

@ -18,7 +18,7 @@
//config: error, but returns default 80x24. //config: error, but returns default 80x24.
//config: Usage in shell scripts: width=`ttysize w`. //config: Usage in shell scripts: width=`ttysize w`.
//applet:IF_TTYSIZE(APPLET(ttysize, BB_DIR_USR_BIN, BB_SUID_DROP)) //applet:IF_TTYSIZE(APPLET_NOFORK(ttysize, ttysize, BB_DIR_USR_BIN, BB_SUID_DROP, ttysize))
//kbuild:lib-$(CONFIG_TTYSIZE) += ttysize.o //kbuild:lib-$(CONFIG_TTYSIZE) += ttysize.o

View file

@ -32,10 +32,10 @@
//config: in its own session, so it won't kill the shell that is running //config: in its own session, so it won't kill the shell that is running
//config: the script it was called from. //config: the script it was called from.
//applet:IF_KILL(APPLET(kill, BB_DIR_BIN, BB_SUID_DROP)) //applet:IF_KILL( APPLET_NOFORK(kill, kill, BB_DIR_BIN, BB_SUID_DROP, kill))
// APPLET_ODDNAME:name main location suid_type help // APPLET_NOFORK:name main location suid_type help
//applet:IF_KILLALL( APPLET_ODDNAME(killall, kill, BB_DIR_USR_BIN, BB_SUID_DROP, killall)) //applet:IF_KILLALL( APPLET_NOFORK(killall, kill, BB_DIR_USR_BIN, BB_SUID_DROP, killall))
//applet:IF_KILLALL5(APPLET_ODDNAME(killall5, kill, BB_DIR_USR_SBIN, BB_SUID_DROP, killall5)) //applet:IF_KILLALL5(APPLET_NOFORK(killall5, kill, BB_DIR_USR_SBIN, BB_SUID_DROP, killall5))
//kbuild:lib-$(CONFIG_KILL) += kill.o //kbuild:lib-$(CONFIG_KILL) += kill.o
//kbuild:lib-$(CONFIG_KILLALL) += kill.o //kbuild:lib-$(CONFIG_KILLALL) += kill.o
@ -87,7 +87,7 @@
* + we can't use xfunc here * + we can't use xfunc here
* + we can't use applet_name * + we can't use applet_name
* + we can't use bb_show_usage * + we can't use bb_show_usage
* (Above doesn't apply for killall[5] cases) * (doesn't apply for killall[5], still should be careful b/c NOFORK)
* *
* kill %n gets translated into kill ' -<process group>' by shell (note space!) * kill %n gets translated into kill ' -<process group>' by shell (note space!)
* This is needed to avoid collision with kill -9 ... syntax * This is needed to avoid collision with kill -9 ... syntax

View file

@ -14,7 +14,7 @@
//config: help //config: help
//config: Report current working directory of a process //config: Report current working directory of a process
//applet:IF_PWDX(APPLET(pwdx, BB_DIR_USR_BIN, BB_SUID_DROP)) //applet:IF_PWDX(APPLET_NOFORK(pwdx, pwdx, BB_DIR_USR_BIN, BB_SUID_DROP, pwdx))
//kbuild:lib-$(CONFIG_PWDX) += pwdx.o //kbuild:lib-$(CONFIG_PWDX) += pwdx.o
@ -50,6 +50,7 @@ int pwdx_main(int argc UNUSED_PARAM, char **argv)
sprintf(buf, "/proc/%u/cwd", pid); sprintf(buf, "/proc/%u/cwd", pid);
/* NOFORK: only one alloc is allowed; must free */
s = xmalloc_readlink(buf); s = xmalloc_readlink(buf);
// "pwdx /proc/1" says "/proc/1: DIR", not "1: DIR" // "pwdx /proc/1" says "/proc/1: DIR", not "1: DIR"
printf("%s: %s\n", *argv, s ? s : strerror(errno == ENOENT ? ESRCH : errno)); printf("%s: %s\n", *argv, s ? s : strerror(errno == ENOENT ? ESRCH : errno));