[launchd-dev] multiple commands in one launchd item

Scott Haneda talklists at newgeo.com
Mon Dec 14 23:26:13 PST 2009


Thanks Damien, comments, below.  I am working with an already  
installed app to add value to it, so there is a limited amount of  
stuff I can install before the user may feel I a installing too much.   
If I can get away with one plist I would love it.

The general goal is
1) Run installed
2) install launchd item in /Library/LaunchDaemons/
3) Run on schedule of load, and 300 seconds
	a) chown 0:0 a specific file in the logded in users dir
	b) set the executable bit to that file.

Yes, I can put that in a shell script, I am trying to keep it as  
simple as possible.  Having two commands in a plist does not seem like  
a lot.

My tests below were bad, but I did not want to give too much of my  
idea away, so I worked out some basic tests that ended up on my  
desktop.  Sorry about that.  I do always use full paths, I always  
assume ENV and PATH are going to be non known.

More below...

On Dec 13, 2009, at 3:07 PM, Damien Sorresso wrote:

> On Dec 12, 2009, at 6:46 PM, Scott Haneda wrote:
>> If I can run a command in the terminal, should it also be possible  
>> to put that entire command into a launchhd plist?
>>
>> For example, this will work in a shell, as a one liner:
>> cd /Users/me/Desktop; mkdir zzzzzzzz; cd zzzzzzzz; touch test;  
>> chown 0:0 test; rm -rf /Users/me/Desktop/zzzzzzzz;
>>
>> Putting that into a plist, I get errors to syslog:
>> Dec 12 18:37:23 macbook com.test.me.test.foo[1596]: /usr/bin/cd:  
>> line 4: cd: /Users/me/Desktop;: No such file or directory
>> Dec 12 18:37:23 macbook com.apple.launchd[1]  
>> (com.test.me.test.foo[1596]): Exited with exit code: 1
>>
>> Here is how I have tried it:
>> <?xml version="1.0" encoding="UTF-8"?>
>> <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd 
>> ">
>> <plist version="1.0">
>> <dict>
>> 	<key>Label</key>
>> 	<string>com.test.me.test.foo</string>
>> 	<key>ProgramArguments</key>
>> 	<array>
>> 		<string>cd</string>
>> 		<string>/Users/me/Desktop;</string>
>> 		<string>mkdir</string>
>> 		<string>zzzzzzzz;</string>
>> 		<string>cd</string>
>> 		<string>zzzzzzzz;</string>
>> 		<string>touch</string>
>> 		<string>test;</string>
>> 		<string>chown</string>
>> 		<string>0:0</string>
>> 		<string>test;</string>
>> 		<string>rm</string>
>> 		<string>-rf</string>
>> 		<string>/Users/me/Desktop/zzzzzzzz;</string>
>> 	</array>
>> 	<key>RunAtLoad</key>
>> 	<true/>
>> 	<key>StartInterval</key>
>> 	<integer>10</integer>
>> </dict>
>> </plist>
>>
>> I have also tried removing the ";" terminator, and reloading, that  
>> does not seem to work either.  I am resoting to shoving these  
>> commands into a shell script, which launchd will then call.  This  
>> works, but is one more file I need to manage, and wold love to be  
>> able to manage this in one launchd file.
>>
>> My goal, it to have a schedule, once every 5 minutes, it will chown  
>> 0:0 a file, and set the executable bit on it.  So something like  
>> cd /Users/me/.hidden; chown 0:0 .runner; chmod +x .runner;
>
> Several things. Firstly, launchd is not a shell interpreter, so  
> please don't assume that it acts like one or makes the same  
> guarantees. launchd jobs consist of a plist describing the job and  
> an executable file that will be run. This is our model, and your  
> deployment scenario clearly allows for you being able to install a  
> plist into /Library/LaunchDaemons, so there's no reason you couldn't  
> also deploy a shell script to, say, /usr/local/bin.

Trying to not deploy a file a file is I need not too, that is the goal  
at least.  Setting 0:0 on a file and +x on it is all I want to do.  I  
have to find out the users login name so I know where the file is I  
want to act on, but that should not be too hard.

Suggestions welcome.

> Secondly, you're doing yourself a disservice by putting the commands  
> in the launchd.plist directly, because you are forcing yourself to  
> reload the plist every time you make a change to the embedded  
> script. If you just point to an executable file on-disk, you can  
> change it to your heart's content without ever having to reload the  
> launchd job.

This plist acts as an emergency item, the idea is it should never be  
used, in the event it is used, it may save your life, otherwise, it  
should just run, every 5 minutes, and do nothing other than keep those  
permissions and +x set.

> Thirdly, you should be specifying the full paths to these commands  
> in your shell script or guaranteeing that your PATH is sanitized,  
> just as a matter of best practices.

Yeah, my bad, I always do, I was just being sloppy in my testing as I  
have been down the road so many times before, I sort of knew it did  
not matter in that case.

> This kind of sanitation is much easier to accomplish in a single  
> shell script file. (Remember, launchd doesn't make any guarantees  
> about what its PATH is or what PATH its jobs will inherit.)

Noted, thanks.

> Fourthly, you're clearly working with your own user account here, so  
> this job should be an agent, not a daemon, so that the files you  
> create are created with the proper ownership and permissions.

Just in testing, as I need the ability to set chown 0:0, I will need  
to be in /Library/LaunchDaemons/ when all is said and done.  I will  
make a nice OS X installer for it as well.

> Lastly, if you're absolutely dead-set on doing things this way,

For this one, yes, in every other case, no, I have 20's of shell  
sccipts in ~/bin/whatever that do things, and each is called by a  
launchd items, I like it that was, as I can test it and not play the  
unlod/load game all the time.

> you have to again remember that launchd is not a shell interpreter,  
> so those commands have no syntactical meaning to it. It just sees "/ 
> Users/me/Desktop;" and copies *that entire string* as the argument.  
> Since it's not a shell interpreter, the ending semi-colon doesn't  
> carry any meaning. It's just the second argument you want passed  
> into an execve(3) call with "cd" as its first argument.
>
> What you should be doing is specifying a Program pointing to the  
> shell of your choice (i.e. "/bin/bash"), followed by the appropriate  
> flag to ask the shell to interpret the argument following as a  
> script ("-c" for bash).

So for example, just to be sure I have this right:

	<key>ProgramArguments</key>
	<array>
		<string>/bin/bash -c cd /Users/useranme/.hidden/commander; \	
			/usr/sbin/chown0:0 /Users/useranme/.hidden/commander; \
			/bin/chmod +x /Users/useranme/.hidden/commander;
		</string>
	
	* Probably have to do that all on one line and nick the \ but is that  
the general idea?

Or is it:
	<key>ProgramArguments</key>
	<array>
		<string>/bin/bash -c /usr/bin/cd /Users/useranme/.hidden/commander; \	
			/bin/bash -c /usr/sbin/chown0:0 /Users/useranme/.hidden/commander; \
			/bin/bash -c /bin/chmod +x /Users/useranme/.hidden/commander;
		</string>

I will probably hand write the plist pre-install to hard code the  
username into it, so there is no chance at all dfor confusion.
	
Thanks for your pointers.

Just out of curiosity, why does this not work in launchd?
	<string>/bin/echo "test" >> "/tmp/this is a test"</string>

-- 
Scott * If you contact me off list replace talklists@ with scott@ *



More information about the launchd-dev mailing list