[Toybox] Issue with names_to_pid(), pidof, killall

Rob Landley rob at landley.net
Tue Dec 17 06:20:36 PST 2013


On 11/22/2013 12:26:14 AM, Ashwini Sharma wrote:
> Hi All,
> 
> Due to some issue with my email account, changing my email to
> ak.ashwini1981 at gmail.com

Finally catching up on my email myself. :)

> I came across a bug in pidof command. The issue is
> with _-o_ option. callback function returns _1_ for omit
> list, to names_to_pid().

Sigh. names_to_pid() is in pending, a command not in pending is calling  
it...

(My bad.)

> names_to_pid(), breaks the internal _for_ loop, at the same
> time it also breaks out of the _while(readdir())_ loop.
> Which is causing the issue, as other pids are not searched for.

Indeed, it should return 0 not 1.

> for (curname = names; *curname; curname++)
>       if (**curname == '/' ? !strcmp(cmd, *curname)
>           : !strcmp(basename(cmd), basename(*curname)))
>         if (callback(u, *curname)) break;
> -    if (*curname) break;
> }
> 
> Why break from _while_ loop, as there can be other processes in the  
> list
> waiting for actioin.
> removing this fixes the issue.

Returning nonzero says to abort the loop. We shouldn't return nonzero  
if we want additional callbacks callbacks.

> _names_to_pid()_ is also used by _killall_. killall also has issues,
> 
> 1. the exit status is always > 0, thats because of toys.exitval++
> in _main() function. Exit status need to be corrected.

Actually it's supposed to exit 1 if no processes were found, and 0 if  
it could kill at least one process. I'll have it zero out the exit code  
if a signal could successfully be delivered.

> 2. No error/warning message is displayed for a non-exiting process.
> as is one by GNU implementation. instead due to the _toys.exitval++_
> its always printing __ No such process __ without any process name.

Actually it prints ("bad %u", pid)?

   ret = kill(pid, TT.signum);
   if (ret == -1 && !(toys.optflags & FLAG_q))
     error_msg("bad %u", (unsigned)pid);

That looks like it should be perror_msg() so it reports the errno  
reason.

The "no such process" message was always printed because the exit value  
was never cleared.

No, the problem is if you kill things that don't exist in the ubuntu  
version:

   $ killall whackamole froglegs
   whackamole: no process found
   froglegs: no process found

You get an error message for each one. And this infrastructure isn't  
tracking that. Although to be honest, the upstream infrastructure is  
lying:

   $ killall init whackamole
   init(1): Operation not permitted
   init: no process found
   whackamole: no process found

And the errno behavior is slightly non-obvious:

   $ sleep 9999 &
   [1] 8016
   $ killall init sleep whackamole
   init(1): Operation not permitted
   init: no process found
   whackamole: no process found
   [1]+  Terminated              sleep 9999
   $ echo $?
   1

Rummage, rummage... Ok, if there was a command it couldn't kill  
(including "not found"), errno is 1. Order doesn't matter.

Ideally I'd save the errno of each entry so the message was right, but  
I don't want to mark up optargs[] (and if I did it'd screw up killing a  
second command with the same name), and can't conveniently use toybuf  
as a bitfield for two reasons (already used for a message right now,  
not necessarily long enough).

Speaking of not necessarily long enough:

   if (toys.optflags & FLAG_i) {
     sprintf(toybuf, "Signal %s(%d) ?", name, pid);

Bad because toybuf is 4k and name is environment data that could be  
128k, so somebody can trivially manipulate a heap overflow out of that.

Right, if I switch that to xsmprintf() I can use toybuf. I can save the  
start of the name array into the globals and then do "name-start" to  
get the offset into the array. If I use it as a bitfield I can  
reproduce the original (broken) behavior, and can track 32768 commands,  
which is probably more than anyone will ever use. (With 128k  
environment space they'd all have to be 3 character command names to  
_fill_ that, you'd need 2 chars to surpass it...)

If I use it as an array to store the errno... does it need to be short  
or char?

   $ cat arch/*/include/uapi/asm/errno* | awk '{print $3}' | sort -n
   ...
   255
   256
   257
   516
   1133

Sigh. Those last few aren't relevant (I grepped, they won't be produced  
by kill(2)), but from a complexity level I don't want to have to  
explain it in a giant comment, nor am I quite comfortable with 4096  
entries as a limit where behavior magically changes.

Screw it: malloc an array.

The next question is, how do I get the position in the array? I was  
thinking I could do pointer subtraction but that gives me how many  
bytes of string data were used. Hack up names_to_pid() to pass it?  
(Only currently two users...) Do a for loop checking?

Eh, I'll just do a for loop. Pathological case is less than 60k command  
line, and I don't have to compare strings (just pointers).

> In order not to modify the lib function _names_to_pid_ too much, these
> killall issues could be fixed from callback function _kill_process()_.

Modifying the callback is the correct approach, yes. The names_to_pid()  
function just iterates over the array we give it to find pids, there  
shouldn't be any logic about what use we put the results to.

> For printing the warning message for non existent processes, can have  
> a map
> for the processes which are signalled thru _kill_process_ and finally  
> once
> _names_to_pid_ returns, print the message for non-signalled processes.
> 
> Like to have your opinion.

I implemented the array version mentioned above before I actually read  
this far in the message. :)

I need to debug a bit before checking in and it's bus-to-work time, but  
I'll see if I can get this checked in tonight.

Rob


More information about the Toybox mailing list