7

Can somebody please clarify how launchd's Program and ProgramArguments configuration parameters should be used? I tried to register a service which on the command line I'd start like this:

$ /foo/bar/baz/python /foo/bar/baz/service start

I have tried divvying that up in various ways for launchd:

<key>Program</key>
<string>/foo/bar/baz/python</string>
<key>ProgramArguments</key>
<array>
    <string>/foo/bar/baz/service</string>
    <string>start</string>
</array>

or

<key>Program</key>
<string>/foo/bar/baz/python</string>
<key>ProgramArguments</key>
<array>
    <string>/foo/bar/baz/service start</string>
</array>

or

<key>ProgramArguments</key>
<array>
    <string>/foo/bar/baz/python</string>
    <string>/foo/bar/baz/service</string>
    <string>start</string>
</array>

or

<key>Program</key>
<string>/bin/bash</string>
<key>ProgramArguments</key>
<array>
    <string>-c</string>
    <string>/foo/bar/baz/python /foo/bar/baz/service start</string>
</array>

and just about any other variation that would seem to make sense. The service always failed with various different errors though. The only thing that worked was to create a .sh script with the exact line and run that via launchd.

So, to understand launchd services once and for all: How does launchd use these two configuration parameters, how would I replicate my bash command with them and what is the difference between both?
Or am I perhaps just stumbling across a problem of running this particular service with and without some environment variables which exist when executing it via bash? The service itself did not provide any useful output.

I have consulted the execvp(3) manual entry as advised in launchd.plist(5), but it did not really further my understanding.

deceze
  • 495

2 Answers2

6

If Program is specified, it will always be the program executed, even if ProgramArguments has been specified as well.

<key>Program</key>
<string>/foo/bar/baz/python</string>
<key>ProgramArguments</key>
<array>
    <string>/foo/bar/baz/python</string>
    <string>/foo/bar/baz/service</string>
    <string>start</string>
</array>

In this case the first element in ProgramArguments is not actually evaluated, but it is passed as argv[0] to the program being executed. Usually this is not needed, but it has its uses. For instance the program may inspect argv[0] and run in a different mode depending on this value.

For everything else it is sufficient to only use ProgramArguments. This job definition works exactly as the one above:

<key>ProgramArguments</key>
<array>
    <string>/foo/bar/baz/python</string>
    <string>/foo/bar/baz/service</string>
    <string>start</string>
</array>
LCC
  • 201
2

POSIX makes a distinction between the actual program being called and the program name. For example, if you have a binary /bin/abc and you do a symlink ln -s /bin/abc xyz and now you call the program ./xyz, then the program being called is /bin/abc but the name of the program is ./xyz.

The main() function of a binary gets an argument array, typically called argv, and argv[0] contains the name of the binary. So in the example above it would be ./xyz. The binary has no way of knowing that it is actually in /bin and is called abc.

Some binaries actually behave differently depending on what name they are called by. For example, gmake (GNU Make) supports many GNU-specific extensions, but when called as make, it behaves like a standard make without these extensions (on many systems, make is just a symlink to gmake).

So you have the binary to run, the name of the binary, and optional additional arguments.

If you specify only ProgramArguments, then the first entry of the array is used for both, the binary to run and the program name. If you specify both Program and ProgramArguments, then Program is the binary to run and the first entry of ProgramArguments is the name of the binary.

<key>Program</key>
<string>/usr/bin/gmake</string>
<key>ProgramArguments</key>
<array>
    <string>make</string>
    <string>...</string>
    <string>...</string>
</array>

This will ensure that gmake is being called but make is passed as program name.

The syntax of execvp is:

execvp( const char *file, char *const argv[] );

With file being the binary to execute and argv being the array passed to the main function of that binary, yet argv[0] is not the first argument but the name of program.

So if you just specify ProgramArgument, launchd will call the function as

execvp(ProgramArguments[0], ProgramArguments);

but if you specify Program and ProgramArgument, launchd will call the function as

execvp(Program, ProgramArguments);

Allowing you to name the program to be whatever you want it to be. However, if you need to arguments at all, you can just specify Program and launchd will call the function as

execvp(Program, [Program]);

turning Program into an array with only one element being the program name which just matches the binary path.

Mecki
  • 994