Merge lp:~jamesodhunt/upstart/file-bridge-MP into lp:upstart
- file-bridge-MP
- Merge into trunk
Status: | Merged |
---|---|
Merged at revision: | 1451 |
Proposed branch: | lp:~jamesodhunt/upstart/file-bridge-MP |
Merge into: | lp:upstart |
Diff against target: |
2336 lines (+2280/-4) 6 files modified
ChangeLog (+22/-0) extra/Makefile.am (+17/-4) extra/conf/upstart-file-bridge.conf (+19/-0) extra/man/file-event.7 (+99/-0) extra/man/upstart-file-bridge.8 (+221/-0) extra/upstart-file-bridge.c (+1902/-0) |
To merge this branch: | bzr merge lp:~jamesodhunt/upstart/file-bridge-MP |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Dimitri John Ledkov | Approve | ||
Review via email: mp+152767@code.launchpad.net |
Commit message
Description of the change
New bridge that allows jobs to react to file creation, modification and deletion events on files and directories. Basic globbing is also supported. See the manual pages for details.
* extra/Makefile.am: Add file bridge and conf file.
* extra/upstart-
* extra/conf/
file bridge.
* extra/man/
* extra/man/
James Hunt (jamesodhunt) wrote : | # |
James Hunt (jamesodhunt) wrote : | # |
That should be bug 776532, not 1103406.
Dimitri John Ledkov (xnox) wrote : | # |
On 11 March 2013 20:08, James Hunt <email address hidden> wrote:
> James Hunt has proposed merging lp:~jamesodhunt/upstart/file-bridge-MP into lp:upstart.
>
> === added file 'extra/
> --- extra/man/
> +++ extra/man/
> +.IP \(bu
> +If you wish to match on
> +.BR FMATCH ", "
> +ensure that
> +.B FPATH
> +does not contain multiple contiguous runs of slashes since otherwise
> +your job will find it difficult to perform such a match.
> +.\"
I didn't know the word "contiguous". I would have used "consecutive"
or "continuous".
Same for the comments in the code, where "contiguous" is also used.
> +/**
> + * expand_path:
> + *
> + * @parent: parent,
> + * @path: path.
> + *
> + * Expand @path by replacing a leading '~/', './' or no path prefix by
> + * the users home directory.
> + *
> + * Limitations: Does not expand '~user'.
> + *
> + * Returns: Newly-allocated fully-expanded path, or NULL on error.
> + **/
> +/**
> + * path_valid:
> + *
> + * @path: path.
> + *
> + * Perform basic tests to determine if @path is valid for
> + * the purposes of this bridge.
> + *
> + * Returns: TRUE if @path is acceptable, else FALSE.
> + **/
So in both of the above, no variable substitutions? Or the upstart
side of things would have replaced variables already for us (didn't
check thoughtfully)?
I am thinking about stuff like watching for file or directory that has
$(INSTANCE_NAME) in it.
I guess there should always be a "one up" location to watch for.
Overall the approach taken is sound and easy to follow and the code is
beautiful as usually. Building and testing here locally for now.
Have you considered using dbusmock for testing this bridge? (i guess
same applies to other bridges as well)
Regards,
Dmitrijs.
Dimitri John Ledkov (xnox) wrote : | # |
# stop upstart-file-bridge
# cat /etc/init/
start on file FPATH=/foo/a/test FEVENT=create
exec echo FPATH=$FPATH FEVENT=$FEVENT FMATCH=$FMATCH
# start upstart-file-bridge
# mkdir /foo
# mkdir /foo/a
# touch /foo/a/test
In the upstart-
Job got added /com/ubuntu/
upstart-
Is this a limitation or a bug?
Steve Langasek (vorlon) wrote : | # |
On Thu, Mar 14, 2013 at 06:53:22PM -0000, Dmitrijs Ledkovs wrote:
> Review: Needs Information
> # stop upstart-file-bridge
> # cat /etc/init/
> start on file FPATH=/foo/a/test FEVENT=create
> exec echo FPATH=$FPATH FEVENT=$FEVENT FMATCH=$FMATCH
> # start upstart-file-bridge
> # mkdir /foo
> # mkdir /foo/a
> # touch /foo/a/test
> In the upstart-
> Job got added /com/ubuntu/
> upstart-
> Is this a limitation or a bug?
Limitation. You're expected to only be able to watch for a) modification of
a single path in an existing directory, or b) creation/deletion of files in
an existing directory. Monitoring for recursive directory creation is
unwieldly via inotify, and out of scope for what we understand the desktop
requirements to be.
Dimitri John Ledkov (xnox) wrote : | # |
Moving start of upstart-
Moving start of upstart-
If /foo/a/ is created, file bridge started, and /a/ subfolder is deleted & recreated, touching /foo/a/test does not emit an event.
I'm not entirely sure about emitting create events, upon start up either.
For example, in my ssh config I have ControlPath set to ~/.cache/
I think simple things like this can be easily scripted into a shell based integration test.
I have been using inotifywait -m -r /foo, to check that inotify events are actually emitted.
Dimitri John Ledkov (xnox) wrote : | # |
On 14 March 2013 19:13, Steve Langasek <email address hidden> wrote:
> On Thu, Mar 14, 2013 at 06:53:22PM -0000, Dmitrijs Ledkovs wrote:
>> Review: Needs Information
>
>> # stop upstart-file-bridge
>
>> # cat /etc/init/
>> start on file FPATH=/foo/a/test FEVENT=create
>> exec echo FPATH=$FPATH FEVENT=$FEVENT FMATCH=$FMATCH
>
>> # start upstart-file-bridge
>> # mkdir /foo
>> # mkdir /foo/a
>> # touch /foo/a/test
>
>> In the upstart-
>> Job got added /com/ubuntu/
>> upstart-
>
>> Is this a limitation or a bug?
>
> Limitation. You're expected to only be able to watch for a) modification of
> a single path in an existing directory, or b) creation/deletion of files in
> an existing directory. Monitoring for recursive directory creation is
> unwieldly via inotify, and out of scope for what we understand the desktop
> requirements to be.
>
Me and steve chatted about this a little on irc on #ubuntu-devel.
In short, we believe we should be supporting the fact that all
/dir/ect/ori/es may not exist at first, but if they got finally
created we stop caring about changes to them (e.g. no support for
tracking removals, re-additions, etc of the dir being watched).
Prototyping here with inotifywait the solution is: if the dir exists
establish the watch & horay emit events as the jobs want.
If that dir does not exist, transverse to the first one that does and
establish a watch which is waiting for "CREATE,ISDIR $subdir", if that
fires establish a new watch on the $subdir, if that's the target we
suppose to watch huray, otherwise wait for "CREATE,ISDIR $nextsubdir",
the old watch can be simply destroyed. It looks like that's what the
code intended to do, but somehow failing.
I did a test for "file FPATH=/foobar.txt FEVENT=create" and that
failed, although that should also be working correctly.
Regards,
Dmitrijs.
- 1450. By James Hunt
-
* extra/man/
file-event. 7: Simplify language.
* extra/upstart-file-bridge. c:
- skip_slashes(): New macro to make path matching more reliable.
- file_filter(): Call skip_slashes().
- create_handler(): Call skip_slashes().
- modify_handler(): Call skip_slashes().
- delete_handler(): Call skip_slashes().
- watched_dir_new(): Special case watching the root directory. - 1451. By James Hunt
-
* extra/conf/
upstart- file-bridge. conf: Change start on condition
to ensure all filesystems are mounted before it starts.
James Hunt (jamesodhunt) wrote : | # |
No, the bridge doesn't expand variables since it doesn't have access to the jobs environment.
> upstart-
This was actually a bug which I have now fixed. Your other examples should now work too :)
Regarding your comments on how the bridge should work - that is exactly how it does work (as documented at the top of the file ;-)
As Steve has mentioned, the bridge as it currently stands is very simple: it should provide "just enough" functionality to be useful but there is certainly scope for future enhancement.
Dimitri John Ledkov (xnox) wrote : | # |
> No, the bridge doesn't expand variables since it doesn't have access to the
> jobs environment.
>
Ok. Good point.
> > upstart-
> directory
> This was actually a bug which I have now fixed. Your other examples should now
> work too :)
>
yeah \o/
> Regarding your comments on how the bridge should work - that is exactly how it
> does work (as documented at the top of the file ;-)
>
awesome, that was my understand, but I got lost a bit in watch removal.
> As Steve has mentioned, the bridge as it currently stands is very simple: it
> should provide "just enough" functionality to be useful but there is certainly
> scope for future enhancement.
Ok. Let me retest.
Dimitri John Ledkov (xnox) : | # |
Preview Diff
1 | === modified file 'ChangeLog' |
2 | --- ChangeLog 2013-03-04 11:53:08 +0000 |
3 | +++ ChangeLog 2013-03-15 12:53:28 +0000 |
4 | @@ -1,3 +1,25 @@ |
5 | +2013-03-15 James Hunt <james.hunt@ubuntu.com> |
6 | + |
7 | + * extra/man/file-event.7: Simplify language. |
8 | + * extra/upstart-file-bridge.c: |
9 | + - skip_slashes(): New macro to make path matching more reliable. |
10 | + - file_filter(): Call skip_slashes(). |
11 | + - create_handler(): Call skip_slashes(). |
12 | + - modify_handler(): Call skip_slashes(). |
13 | + - delete_handler(): Call skip_slashes(). |
14 | + - watched_dir_new(): Special case watching the root directory. |
15 | + * extra/conf/upstart-file-bridge.conf: Change start on condition |
16 | + to ensure all filesystems are mounted before it starts. |
17 | + |
18 | +2013-03-11 James Hunt <james.hunt@ubuntu.com> |
19 | + |
20 | + * extra/Makefile.am: Add file bridge and conf file. |
21 | + * extra/upstart-file-bridge.c: Inotify file bridge. |
22 | + * extra/conf/upstart-file-bridge.conf: Conf file for |
23 | + file bridge. |
24 | + * extra/man/file-event.7: New man page. |
25 | + * extra/man/upstart-file-bridge.8: New man page. |
26 | + |
27 | 2013-03-04 James Hunt <james.hunt@ubuntu.com> |
28 | |
29 | * init/session.c: session_from_dbus(): Fixed off-by-one |
30 | |
31 | === modified file 'extra/Makefile.am' |
32 | --- extra/Makefile.am 2012-12-19 14:46:53 +0000 |
33 | +++ extra/Makefile.am 2013-03-15 12:53:28 +0000 |
34 | @@ -18,16 +18,20 @@ |
35 | |
36 | sbin_PROGRAMS = \ |
37 | upstart-socket-bridge \ |
38 | - upstart-event-bridge |
39 | + upstart-event-bridge \ |
40 | + upstart-file-bridge |
41 | |
42 | dist_init_DATA = \ |
43 | conf/upstart-socket-bridge.conf \ |
44 | - conf/upstart-event-bridge.conf |
45 | + conf/upstart-event-bridge.conf \ |
46 | + conf/upstart-file-bridge.conf |
47 | |
48 | dist_man_MANS = \ |
49 | man/upstart-socket-bridge.8 \ |
50 | man/upstart-event-bridge.8 \ |
51 | - man/socket-event.7 |
52 | + man/upstart-file-bridge.8 \ |
53 | + man/socket-event.7 \ |
54 | + man/file-event.7 |
55 | |
56 | upstart_socket_bridge_SOURCES = \ |
57 | upstart-socket-bridge.c |
58 | @@ -51,7 +55,16 @@ |
59 | $(NIH_DBUS_LIBS) \ |
60 | $(DBUS_LIBS) |
61 | |
62 | - |
63 | +upstart_file_bridge_SOURCES = \ |
64 | + upstart-file-bridge.c |
65 | +nodist_upstart_file_bridge_SOURCES = \ |
66 | + $(com_ubuntu_Upstart_OUTPUTS) \ |
67 | + $(com_ubuntu_Upstart_Job_OUTPUTS) |
68 | +upstart_file_bridge_LDADD = \ |
69 | + $(LTLIBINTL) \ |
70 | + $(NIH_LIBS) \ |
71 | + $(NIH_DBUS_LIBS) \ |
72 | + $(DBUS_LIBS) |
73 | |
74 | if HAVE_UDEV |
75 | dist_init_DATA += \ |
76 | |
77 | === added file 'extra/conf/upstart-file-bridge.conf' |
78 | --- extra/conf/upstart-file-bridge.conf 1970-01-01 00:00:00 +0000 |
79 | +++ extra/conf/upstart-file-bridge.conf 2013-03-15 12:53:28 +0000 |
80 | @@ -0,0 +1,19 @@ |
81 | +# upstart-file-bridge - Bridge file events into upstart |
82 | +# |
83 | +# This helper daemon receives inotify(7) events and |
84 | +# emits equivalent Upstart events. |
85 | + |
86 | +description "Bridge file events into upstart" |
87 | + |
88 | +emits file |
89 | + |
90 | +# the bridge does not currently handle dealing with mounts that overlay |
91 | +# already-watched directories. |
92 | +start on mounted |
93 | + |
94 | +stop on runlevel [!2345] |
95 | + |
96 | +expect daemon |
97 | +respawn |
98 | + |
99 | +exec upstart-file-bridge --daemon |
100 | |
101 | === added file 'extra/man/file-event.7' |
102 | --- extra/man/file-event.7 1970-01-01 00:00:00 +0000 |
103 | +++ extra/man/file-event.7 2013-03-15 12:53:28 +0000 |
104 | @@ -0,0 +1,99 @@ |
105 | +.TH file\-event 7 2013-03-11 upstart |
106 | +.\" |
107 | +.SH NAME |
108 | +file \- event signalling that a file has changed |
109 | +.\" |
110 | +.SH SYNOPSIS |
111 | +.B file |
112 | +.BI FPATH\fR= PATH |
113 | +.BI FEVENT\fR= TYPE |
114 | +.IB \fR[ FMATCH\fR= PATH \fR] |
115 | +.\" |
116 | +.SH DESCRIPTION |
117 | + |
118 | +The |
119 | +.B file |
120 | +event is generated by the |
121 | +.BR upstart\-file\-bridge (8) |
122 | +daemon when a file whose details match the |
123 | +file event condition and environment specified in a jobs |
124 | +.B start on |
125 | +or |
126 | +.B stop on |
127 | +stanza is modified. |
128 | + |
129 | +The |
130 | +.BR FPATH " and " FEVENT |
131 | +environment variables will be set to the same values as specified by the |
132 | +job. Note that if the job did not specify |
133 | +.B FEVENT |
134 | +this will still be set to one of |
135 | +.BR create ", " |
136 | +.BR modify " or " |
137 | +.B delete |
138 | +depending on what type of file event caused the event to be emitted. |
139 | + |
140 | +The |
141 | +.B FEVENT |
142 | +environment variable will be set to the value of the corresponding |
143 | + |
144 | +If the job specified a glob pattern in the file part of the |
145 | +.B FPATH |
146 | +environment variable, the event will contain the |
147 | +.B FMATCH |
148 | +environment variable which will be set to the full path of the file that |
149 | +matched the pattern in |
150 | +.BR FPATH "." |
151 | +.\" |
152 | +.SH NOTES |
153 | + |
154 | +.IP \(bu 4 |
155 | +When specifying a path that contains spaces, ensure that the path is |
156 | +quoted. |
157 | +.\" |
158 | +.IP \(bu |
159 | +.B FPATH |
160 | +values specified by jobs are not canonicalised; they cannot be reliably as |
161 | +they may not exist so cannot be fully resolved. |
162 | +.\" |
163 | +.IP \(bu |
164 | +If you wish to match on |
165 | +.BR FMATCH ", " |
166 | +ensure that |
167 | +.B FPATH |
168 | +does not contain multiple consecutive runs of slashes since otherwise |
169 | +your job will find it difficult to perform such a match. |
170 | +.\" |
171 | +.SH EXAMPLES |
172 | +.\" |
173 | +.IP "start on file FPATH=/run/app.pid FEVENT=created" 0.4i |
174 | +Event emitted when file is created. |
175 | +.IP "start on file FPATH=/run/app.pid" |
176 | +Event emitted when file is created, modified or deleted. |
177 | +.IP "start on file FPATH=/var/log/" |
178 | +Event emitted when files within a directory are created, modified or |
179 | +deleted. |
180 | +.IP "start on file FPATH=/var/crash/*.crash FEVENT=created" |
181 | +Event emitted when files in a directory matching a glob pattern are |
182 | +created. |
183 | +.IP "start on file FPATH=""/this/path/contains whitespace.txt""" |
184 | +Specify a file that contains a space character. |
185 | +.\" |
186 | +.SH AUTHOR |
187 | +Written by James Hunt |
188 | +.RB < james.hunt@canonical.com > |
189 | +.\" |
190 | +.SH BUGS |
191 | +Report bugs at |
192 | +.RB < https://launchpad.net/upstart/+bugs > |
193 | +.\" |
194 | +.SH COPYRIGHT |
195 | +Copyright \(co 2013 Canonical Ltd. |
196 | +.PP |
197 | +This is free software; see the source for copying conditions. There is NO |
198 | +warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. |
199 | +.\" |
200 | +.SH SEE ALSO |
201 | +.BR init (5) |
202 | +.BR init (8) |
203 | +.BR upstart\-file\-bridge (8) |
204 | |
205 | === added file 'extra/man/upstart-file-bridge.8' |
206 | --- extra/man/upstart-file-bridge.8 1970-01-01 00:00:00 +0000 |
207 | +++ extra/man/upstart-file-bridge.8 2013-03-15 12:53:28 +0000 |
208 | @@ -0,0 +1,221 @@ |
209 | +.TH upstart\-file\-bridge 8 2013-03-11 upstart |
210 | +.\" |
211 | +.SH NAME |
212 | +upstart\-file\-bridge \- Bridge between Upstart and inotify |
213 | +.\" |
214 | +.SH SYNOPSIS |
215 | +.B upstart\-file\-bridge |
216 | +.RI [ OPTIONS ]... |
217 | +.\" |
218 | +.SH DESCRIPTION |
219 | +.B upstart\-file\-bridge |
220 | +receives information about kernel file events that |
221 | +.BR inotify (7) |
222 | +has received and creates |
223 | +.BR init (8) |
224 | +events for them. |
225 | + |
226 | +Supported events exposed to Upstart allow jobs to detect creation, |
227 | +modification and deletion. See |
228 | +.BR file\-event (7) |
229 | +for further details. |
230 | + |
231 | +The bridge works by querying the |
232 | +.BR init (8) |
233 | +daemon at bridge startup time to determine a list of all jobs whose |
234 | +.B start on |
235 | +or |
236 | +.B stop on |
237 | +conditions reference the |
238 | +.B file |
239 | +event. Further, the bridge arranges to be notified automatically when |
240 | +new jobs are created such that any subsequent jobs which reference the |
241 | +.B file |
242 | +event are also handled (as are job deletions). |
243 | + |
244 | +See \fBinotify\fP(7) and for further details. |
245 | + |
246 | +.\" |
247 | +.SH OPTIONS |
248 | +.\" |
249 | +.TP |
250 | +.B \-\-daemon |
251 | +Detach and run in the background. |
252 | +.\" |
253 | +.TP |
254 | +.B \-\-debug |
255 | +Enable debugging output. |
256 | +.\" |
257 | +.TP |
258 | +.B \-\-help |
259 | +Show brief usage summary. |
260 | +.\" |
261 | +.TP |
262 | +.B \-\-user |
263 | +User-session mode: connect to Upstart via the user session rather than |
264 | +over the D\-Bus system bus. |
265 | +.\" |
266 | +.TP |
267 | +.B \-\-verbose |
268 | +Enable verbose output. |
269 | +.\" |
270 | +.SH JOB ENVIRONMENT VARIABLES |
271 | +.TP |
272 | +.B FPATH |
273 | +Path to file to watch. When run without |
274 | +.BR \-\-user "," |
275 | +this must be an absolute path. If |
276 | +.BR \-\-user |
277 | +is specified, certain relative path types are supported: |
278 | +.RS |
279 | +.IP \[bu] 2 |
280 | +If the path begins with \(aq~/\(aq, the value will be expanded as would |
281 | +be performed by a shell for matching purposes (although when the event |
282 | +is emitted, the original value will be used). |
283 | +.\" |
284 | +.IP \[bu] |
285 | +If the path begins with \(aq./\(aq, \(aq.\(aq will be assumed to be the |
286 | +users home directory. |
287 | +.\" |
288 | +.IP \[bu] |
289 | +In all other scenarios, if the path does not begin with \(aq/\(aq, it |
290 | +will be assumed to represent a file below the users home directory. |
291 | +.P |
292 | +If the path ends with a slash, it is considered to be a directory which |
293 | +changes the match behaviour (see below). |
294 | +.P |
295 | +If the path is not a directory, the file (but not the directory) portion |
296 | +of the path may contain wildcard matches. See |
297 | +.BR fnmatch (3) |
298 | +and |
299 | +.BR glob (7) |
300 | +for further details. |
301 | +.RE |
302 | +.\" |
303 | +.TP |
304 | +.B FEVENT |
305 | +Event relating to |
306 | +.B FPATH |
307 | +that job is interested in. If this variable is specified the value must |
308 | +be set to |
309 | +.BR create ", " |
310 | +.BR modify " or " |
311 | +.B delete |
312 | +depending on what type of file event the job is interested in. If |
313 | +.B FPATH |
314 | +is not specified, the bridge will watch for all types and set this |
315 | +variable to the appropriate value when emitting the event. |
316 | +.\" |
317 | +.SH WATCH TYPE BEHAVIOUR |
318 | + |
319 | +The bridge emits events depending not only on the value of |
320 | +.BR FEVENT ", " |
321 | +but also on the entity specified by |
322 | +.BR FPATH ":" |
323 | +.\" |
324 | +.SS File |
325 | + |
326 | +An event will be emitted when the named file is created, modified or |
327 | +deleted depending on the value of \fBFEVENT\fR. If |
328 | +.B FEVENT |
329 | +is not specified, react to creation, modification and deletion. |
330 | + |
331 | +If the file already exists when the job is registered, and |
332 | +.B FEVENT |
333 | +either specifies |
334 | +.I create |
335 | +or the variable is not specified, the event will be emitted. |
336 | +.\" |
337 | +.SS Directory |
338 | + |
339 | +An event will be emitted when the named directory is created, modified |
340 | +(files within it are created, modified or deleted) or deleted depending |
341 | +on the value of |
342 | +\fBFEVENT\fR. If |
343 | +.B FEVENT |
344 | +is not specified, react to creation, modification and deletion. |
345 | + |
346 | +If the directory already exists when the job is registered, and |
347 | +.B FEVENT |
348 | +either specifies |
349 | +.I create |
350 | +or the variable is not specified, the event will be emitted. |
351 | +.\" |
352 | +.SS Glob |
353 | + |
354 | +One event will be emitted per match when the glob wildcard matches any |
355 | +files in the directory part is created, modified or deleted, depending |
356 | +on the value of |
357 | +\fBFEVENT\fR. If |
358 | +.B FEVENT |
359 | +is not specified, react to creation, modification and deletion. |
360 | + |
361 | +If any matches already exist when the job is registered, and |
362 | +.B FEVENT |
363 | +either specifies |
364 | +.I create |
365 | +or the variable is not specified, events will be emitted. |
366 | +.\" |
367 | +.SH NOTES |
368 | + |
369 | +.IP \(bu 4 |
370 | +A single instance of the bridge may be run at the system level, but |
371 | +multiple further instances may be run per user session instance by using |
372 | +the |
373 | +.BR \-\-user "." |
374 | +.IP \(bu |
375 | +All job conditions specifying the |
376 | +.B file |
377 | +event are multi-shot: if the same file event occurs multiple times, the |
378 | +bridge will emit an Upstart event each time. |
379 | +.\" |
380 | +.SH LIMITATIONS |
381 | + |
382 | +.IP \(bu 4 |
383 | +Since the bridge currently uses |
384 | +.BR inotify (7) "" "," |
385 | +it is subject to the same limitations; namely that recursive watches |
386 | +cannot be created reliably in all circumstances. As such, pathological |
387 | +scenarios such as deep directory trees being created and then quickly |
388 | +removed |
389 | +.B cannot |
390 | +be handled reliably. The following provides advice to minimise |
391 | +unexpected behaviour: |
392 | +.RS |
393 | +.IP \(bu 4 |
394 | +Attempt to only watch for files to be created, modified or deleted |
395 | +in directories that are guaranteed to already exist at the time |
396 | +the job is registered by the bridge. |
397 | +.\" |
398 | +.IP \(bu |
399 | +If the system cannot guarantee that the directory will exist at job |
400 | +registration time, arrange for the directory to be created by an Upstart |
401 | +job before the bridge itself starts. |
402 | +.\" |
403 | +.IP \(bu |
404 | +In user session mode, if a job specifies a file to watch for and that |
405 | +file is created but inaccessible to the user running the bridge, no |
406 | +event will be emitted. |
407 | +.RE |
408 | +.IP \(bu |
409 | +Tilde expansion is only supported for the current user; that is |
410 | +\(aq~otheruser\(aq will not work. |
411 | +.\" |
412 | +.SH AUTHOR |
413 | +Written by James Hunt |
414 | +.RB < james.hunt@canonical.com > |
415 | +.\" |
416 | +.SH BUGS |
417 | +Report bugs at |
418 | +.RB < https://launchpad.net/ubuntu/+source/upstart/+bugs > |
419 | +.\" |
420 | +.SH COPYRIGHT |
421 | +Copyright \(co 2013 Canonical Ltd. |
422 | +.PP |
423 | +This is free software; see the source for copying conditions. There is NO |
424 | +warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. |
425 | +.SH SEE ALSO |
426 | +.BR init (5) |
427 | +.BR init (8) |
428 | +.BR inotify (7) |
429 | +.BR file-event (7) |
430 | |
431 | === added file 'extra/upstart-file-bridge.c' |
432 | --- extra/upstart-file-bridge.c 1970-01-01 00:00:00 +0000 |
433 | +++ extra/upstart-file-bridge.c 2013-03-15 12:53:28 +0000 |
434 | @@ -0,0 +1,1902 @@ |
435 | +/* upstart |
436 | + * |
437 | + * Copyright © 2013 Canonical Ltd. |
438 | + * Author: James Hunt <james.hunt@canonical.com>. |
439 | + * |
440 | + * This program is free software; you can redistribute it and/or modify |
441 | + * it under the terms of the GNU General Public License version 2, as |
442 | + * published by the Free Software Foundation. |
443 | + * |
444 | + * This program is distributed in the hope that it will be useful, |
445 | + * but WITHOUT ANY WARRANTY; without even the implied warranty of |
446 | + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
447 | + * GNU General Public License for more details. |
448 | + * |
449 | + * You should have received a copy of the GNU General Public License along |
450 | + * with this program; if not, write to the Free Software Foundation, Inc., |
451 | + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. |
452 | + */ |
453 | + |
454 | +/** |
455 | + * This is the Upstart File Bridge which allows jobs to react to files |
456 | + * being created, modified and deleted. |
457 | + * |
458 | + * = Design = |
459 | + * |
460 | + * This bridge creates inotify watches on the _first existing parent |
461 | + * directory_ for the file (normal or directory) being watched for. As |
462 | + * directories are created, the watch is moved to become more specific |
463 | + * (closer to the actually requested file path) and as directories are |
464 | + * deleted, the watch is correspondingly changed to a less specific, but |
465 | + * existing, directory. |
466 | + * |
467 | + * This is necessary since: |
468 | + * |
469 | + * - It conserves system resources. |
470 | + * |
471 | + * There is little point creating 'n' watches on existing files when a |
472 | + * single watch on the parent directory will suffice. |
473 | + * |
474 | + * - It is not possible to create an inotify watch for a non-existent |
475 | + * entity (*). |
476 | + * |
477 | + * - In a sense, it simplifies the design. |
478 | + * |
479 | + * Otherwise the bridge would have to put a watch on each existing |
480 | + * file for modify and delete requests, but watch the parent for |
481 | + * create requests. And for a combination of requests who share |
482 | + * a parent directory, it's easier to just watch the parent alone. |
483 | + * |
484 | + * = Limitations = |
485 | + * |
486 | + * Since inotify is used, this bridge has a number of significant |
487 | + * limitations: |
488 | + * |
489 | + * 1) It cannot be anything but inherently racy. |
490 | + * |
491 | + * inotify(7) does not support recursive watches, so in some -- and not |
492 | + * necessarily pathological -- cases, events may be missed. This is |
493 | + * unfortunately exacerbated by the design of the bridge which creates |
494 | + * watches on the parent directory. This takes time, but in the window |
495 | + * when the watch is being created, files may have been modified |
496 | + * undetectably. |
497 | + * |
498 | + * For example, if the user requests a watch on '/var/log/app/foo.log', |
499 | + * the following might happen: |
500 | + * |
501 | + * (1) Watch is created for existing directory '/var/log/'. |
502 | + * (2) A process creates '/var/log/app/'. |
503 | + * (3) The bridge detects this and moves the watch from |
504 | + * '/var/log/' to '/var/log/app/'. |
505 | + * (4) Whilst (3) is happening, some process removes '/var/log/app/'. |
506 | + * (5) The bridge now has an impotent watch on the now-deleted |
507 | + * '/var/log/app/'. |
508 | + * (6) The app starts and (re-)creates '/var/log/app/'. |
509 | + * (7) The app now creates '/var/log/app/foo.log'. |
510 | + * (8) No event is emitted due to the impotent watch in (5). |
511 | + * |
512 | + * The situation is sadly actually worse than this: if a job watches for |
513 | + * a deep directory, if any one of the directory elements that is |
514 | + * created gets missed due to a race between the directory creation and |
515 | + * this bridge creating or moving a watch, the event will not be |
516 | + * emitted. |
517 | + * |
518 | + * = Advice = |
519 | + * |
520 | + * - Attempt to only watch for files to be created/modified/deleted |
521 | + * in directories that are guaranteed to already exist at |
522 | + * system startup. This avoids the racy behaviour between |
523 | + * directory creation and inotify watch manipulation. |
524 | + * |
525 | + * - If the directory is not guaranteed to exist at system startup, |
526 | + * create an Upstart job that creates the directory before the bridge |
527 | + * starts ('start on starting upstart-file-bridge'). |
528 | + * |
529 | + * = Alternative Approaches = |
530 | + * |
531 | + * fanotify is an alternative but again, it is limited: |
532 | + * |
533 | + * == Pros == |
534 | + * |
535 | + * + Supports recursive watches. |
536 | + * |
537 | + * == Cons == |
538 | + * |
539 | + * - Does not support a file delete event. |
540 | + * |
541 | + * - Potentially high system performance impact since _every_ file |
542 | + * operation on the partition (except delete) is inspected. |
543 | + * |
544 | + *---------- |
545 | + * |
546 | + * (*) - this is half true: inotify alarmingly does allow a watch to be |
547 | + * created on a non-existent entity, but is impotent - if that entity is |
548 | + * ever created, no event is received. |
549 | + **/ |
550 | + |
551 | +#ifdef HAVE_CONFIG_H |
552 | +# include <config.h> |
553 | +#endif /* HAVE_CONFIG_H */ |
554 | + |
555 | +#include <stdlib.h> |
556 | +#include <string.h> |
557 | +#include <syslog.h> |
558 | +#include <unistd.h> |
559 | +#include <libgen.h> |
560 | +#include <sys/types.h> |
561 | +#include <sys/stat.h> |
562 | +#include <fnmatch.h> |
563 | +#include <glob.h> |
564 | +#include <pwd.h> |
565 | + |
566 | +#include <nih/alloc.h> |
567 | +#include <nih/command.h> |
568 | +#include <nih/error.h> |
569 | +#include <nih/hash.h> |
570 | +#include <nih/io.h> |
571 | +#include <nih/list.h> |
572 | +#include <nih/logging.h> |
573 | +#include <nih/macros.h> |
574 | +#include <nih/main.h> |
575 | +#include <nih/option.h> |
576 | +#include <nih/string.h> |
577 | +#include <nih/test.h> |
578 | +#include <nih/timer.h> |
579 | +#include <nih/watch.h> |
580 | + |
581 | +#include <nih-dbus/dbus_connection.h> |
582 | +#include <nih-dbus/dbus_proxy.h> |
583 | + |
584 | +#include "dbus/upstart.h" |
585 | +#include "com.ubuntu.Upstart.h" |
586 | +#include "com.ubuntu.Upstart.Job.h" |
587 | + |
588 | +/** |
589 | + * FILE_EVENT: |
590 | + * |
591 | + * Name of event this program handles |
592 | + **/ |
593 | +#define FILE_EVENT "file" |
594 | + |
595 | +/** |
596 | + * ALL_FILE_EVENTS: |
597 | + * |
598 | + * All the inotify file events we care about. |
599 | + **/ |
600 | +#define ALL_FILE_EVENTS (IN_CREATE|IN_MODIFY|IN_CLOSE_WRITE|IN_DELETE) |
601 | + |
602 | +/** |
603 | + * GLOB_CHARS: |
604 | + * |
605 | + * Wildcard characters recognised by glob(3) and fnmatch(3). |
606 | + **/ |
607 | +#define GLOB_CHARS "*?[]" |
608 | + |
609 | +/** |
610 | + * original_path: |
611 | + * |
612 | + * @file: WatchedFile: |
613 | + * |
614 | + * Obtain the appropriate WatchedFile path: either the original if the |
615 | + * path underwent expansion, else the initial unexpanded path. |
616 | + * |
617 | + * Required for emitting events since jobs need the unexpanded path to |
618 | + * allow their start/stop condition to match even if the path has |
619 | + * subsequently been expanded by this bridge. |
620 | + **/ |
621 | +#define original_path(file) \ |
622 | + (file->original ? file->original : file->path) |
623 | + |
624 | +/** |
625 | + * skip_slashes: |
626 | + * |
627 | + * @path: pointer to path string. |
628 | + * |
629 | + * Increment @path to skip over multiple leading slashes. |
630 | + **/ |
631 | +#define skip_slashes(path) \ |
632 | + while (*(path) == '/' && (path)+1 && *((path)+1) == '/') \ |
633 | + (path)++ |
634 | + |
635 | +/** |
636 | + * Job: |
637 | + * |
638 | + * @entry: list header, |
639 | + * @path: D-Bus path of Upstart job, |
640 | + * @files: list of pointers to WatchedFile files Job will watch. |
641 | + * |
642 | + * Structure we use for tracking Upstart jobs. |
643 | + **/ |
644 | +typedef struct job { |
645 | + NihList entry; |
646 | + char *path; |
647 | + NihList files; |
648 | +} Job; |
649 | + |
650 | +/** |
651 | + * WatchedDir: |
652 | + * |
653 | + * @entry: list header, |
654 | + * @path: full path of directory being watched, |
655 | + * @files: hash of WatchedFile objects representing all files |
656 | + * watched in directory @path and sub-directories, |
657 | + * @watch: watch object. |
658 | + * |
659 | + * Every watched file is handled by watching the first parent |
660 | + * directory that currently exists. This allows use to: |
661 | + * |
662 | + * - minimise watch descriptors |
663 | + * - easily handle the case where a job wants to watch for a file being |
664 | + * created when that file doesn't yet exist (*). |
665 | + * |
666 | + * The drawback to this strategy is the complexity of handling watched |
667 | + * files and directories when files are created and deleted. |
668 | + * |
669 | + * Note that the WatchedFiles in @files are not necessarily _immediate_ |
670 | + * children of @path, but they are children. |
671 | + * |
672 | + * (*) Irritatingly, inotify _does_ allow for a watch on a |
673 | + * non-existing file to be created, but the watch is |
674 | + * impotent in that when the file _is_ created, no inotify |
675 | + * event results. |
676 | + * |
677 | + **/ |
678 | +typedef struct watched_dir { |
679 | + NihList entry; |
680 | + char *path; |
681 | + NihHash *files; |
682 | + NihWatch *watch; |
683 | +} WatchedDir; |
684 | + |
685 | +/** |
686 | + * WatchedFile: |
687 | + * |
688 | + * @entry: list header, |
689 | + * @path: full path to file being watched (or a glob), |
690 | + * @original: original (relative) path as specified by job |
691 | + * (or NULL if path expansion was not necessary), |
692 | + * @glob: glob file pattern (or NULL if globbing disabled), |
693 | + * @dir: TRUE if @path is a directory, |
694 | + * @events: mask of inotify events file is interested in, |
695 | + * @parent: parent who is watching over us. |
696 | + * |
697 | + * Details of the file being watched. |
698 | + **/ |
699 | +typedef struct watched_file { |
700 | + NihList entry; |
701 | + char *path; |
702 | + char *original; |
703 | + char *glob; |
704 | + int dir; |
705 | + uint32_t events; |
706 | + WatchedDir *parent; |
707 | +} WatchedFile; |
708 | + |
709 | +/** |
710 | + * FileEvent: |
711 | + * |
712 | + * @entry: list header, |
713 | + * @path: full path to file being watched, |
714 | + * @event: event to emit, |
715 | + * @match: optional file match if @path is a directory or glob. |
716 | + * |
717 | + * Details of the event to be emitted. |
718 | + **/ |
719 | +typedef struct file_event { |
720 | + NihList entry; |
721 | + char *path; |
722 | + uint32_t event; |
723 | + char *match; |
724 | +} FileEvent; |
725 | + |
726 | +/* Prototypes for static functions */ |
727 | +static WatchedDir *watched_dir_new (const char *path, const struct stat *statbuf) |
728 | + __attribute__ ((warn_unused_result)); |
729 | + |
730 | +static WatchedFile *watched_file_new (const char *path, |
731 | + const char *original, |
732 | + uint32_t events, |
733 | + const char *glob) |
734 | + __attribute__ ((warn_unused_result)); |
735 | + |
736 | +static Job *job_new (const char *class_path) |
737 | + __attribute__ ((warn_unused_result)); |
738 | + |
739 | +static int file_filter (WatchedDir *dir, const char *path, int is_dir); |
740 | + |
741 | +static void create_handler (WatchedDir *dir, NihWatch *watch, |
742 | + const char *path, struct stat *statbuf); |
743 | + |
744 | +static void modify_handler (WatchedDir *dir, NihWatch *watch, |
745 | + const char *path, struct stat *statbuf); |
746 | + |
747 | +static void delete_handler (WatchedDir *dir, NihWatch *watch, |
748 | + const char *path); |
749 | + |
750 | +static void upstart_job_added (void *data, NihDBusMessage *message, |
751 | + const char *job_path); |
752 | + |
753 | +static void upstart_job_removed (void *data, NihDBusMessage *message, |
754 | + const char *job_path); |
755 | + |
756 | +static void job_add_file (Job *job, char **file_info); |
757 | + |
758 | +static void emit_event_error (void *data, NihDBusMessage *message); |
759 | +static int emit_event (const char *path, uint32_t event_type, |
760 | + const char *match); |
761 | + |
762 | +static FileEvent *file_event_new (void *parent, const char *path, |
763 | + uint32_t event, const char *match); |
764 | + |
765 | +static void upstart_disconnected (DBusConnection *connection); |
766 | + |
767 | +static void handle_event (NihHash *handled, const char *path, |
768 | + uint32_t event, const char *match); |
769 | + |
770 | +static int job_destroy (Job *job); |
771 | + |
772 | +static char * find_first_parent (const char *path) |
773 | + __attribute__ ((warn_unused_result)); |
774 | + |
775 | +void watched_dir_init (void); |
776 | + |
777 | +static void ensure_watched (Job *job, WatchedFile *file); |
778 | + |
779 | +static int string_match (const char *a, const char *b) |
780 | + __attribute__ ((warn_unused_result)); |
781 | + |
782 | +char * expand_path (const void *parent, const char *path) |
783 | + __attribute__ ((warn_unused_result)); |
784 | + |
785 | +static int path_valid (const char *path) |
786 | + __attribute__ ((warn_unused_result)); |
787 | + |
788 | +/** |
789 | + * daemonise: |
790 | + * |
791 | + * Set to TRUE if we should become a daemon, rather than just running |
792 | + * in the foreground. |
793 | + **/ |
794 | +static int daemonise = FALSE; |
795 | + |
796 | +/** |
797 | + * jobs: |
798 | + * |
799 | + * Hash of Upstart jobs that we're monitoring. |
800 | + **/ |
801 | +static NihHash *jobs = NULL; |
802 | + |
803 | +/** |
804 | + * watched_dirs: |
805 | + * |
806 | + * Hash of WatchedDir objects representing the minimum set of existing |
807 | + * parent directories that allow all WatchedFiles to be watched for. |
808 | + **/ |
809 | +static NihHash *watched_dirs = NULL; |
810 | + |
811 | +/** |
812 | + * upstart: |
813 | + * |
814 | + * Proxy to Upstart daemon. |
815 | + **/ |
816 | +static NihDBusProxy *upstart = NULL; |
817 | + |
818 | +/** |
819 | + * user: |
820 | + * |
821 | + * If TRUE, run in User Session mode connecting to the Session Init |
822 | + * rather than PID 1. In this mode, certain relative paths are also |
823 | + * expanded. |
824 | + **/ |
825 | +static int user = FALSE; |
826 | + |
827 | +/** |
828 | + * home_dir: |
829 | + * |
830 | + * Full path to home directory. |
831 | + **/ |
832 | +char home_dir[PATH_MAX]; |
833 | + |
834 | +/** |
835 | + * options: |
836 | + * |
837 | + * Command-line options accepted by this program. |
838 | + **/ |
839 | +static NihOption options[] = { |
840 | + { 0, "daemon", N_("Detach and run in the background"), |
841 | + NULL, NULL, &daemonise, NULL }, |
842 | + { 0, "user", N_("Connect to user session"), |
843 | + NULL, NULL, &user, NULL }, |
844 | + |
845 | + NIH_OPTION_LAST |
846 | +}; |
847 | + |
848 | + |
849 | +int |
850 | +main (int argc, |
851 | + char *argv[]) |
852 | +{ |
853 | + char **args; |
854 | + DBusConnection *connection; |
855 | + char **job_class_paths; |
856 | + int ret; |
857 | + char *user_session_addr = NULL; |
858 | + |
859 | + nih_main_init (argv[0]); |
860 | + |
861 | + nih_option_set_synopsis (_("Bridge inotify events into upstart")); |
862 | + nih_option_set_help ( |
863 | + _("By default, upstart-inotify-bridge does not detach from the " |
864 | + "console and remains in the foreground. Use the --daemon " |
865 | + "option to have it detach.")); |
866 | + |
867 | + args = nih_option_parser (NULL, argc, argv, options, FALSE); |
868 | + if (! args) |
869 | + exit (EXIT_FAILURE); |
870 | + |
871 | + if (user) { |
872 | + struct passwd *pw; |
873 | + user_session_addr = getenv ("UPSTART_SESSION"); |
874 | + if (! user_session_addr) { |
875 | + nih_fatal (_("UPSTART_SESSION isn't set in environment")); |
876 | + exit (EXIT_FAILURE); |
877 | + } |
878 | + |
879 | + pw = getpwuid (getuid ()); |
880 | + |
881 | + if (! pw) { |
882 | + nih_error ("Failed to get password entry"); |
883 | + exit (EXIT_FAILURE); |
884 | + } |
885 | + |
886 | + nih_assert (pw->pw_dir); |
887 | + |
888 | + strcpy (home_dir, (pw->pw_dir)); |
889 | + } |
890 | + |
891 | + /* Allocate jobs hash table */ |
892 | + jobs = NIH_MUST (nih_hash_string_new (NULL, 0)); |
893 | + |
894 | + /* Initialise the connection to Upstart */ |
895 | + connection = NIH_SHOULD (nih_dbus_connect (user |
896 | + ? user_session_addr |
897 | + : DBUS_ADDRESS_UPSTART, |
898 | + upstart_disconnected)); |
899 | + if (! connection) { |
900 | + NihError *err; |
901 | + |
902 | + err = nih_error_get (); |
903 | + nih_fatal ("%s: %s", _("Could not connect to Upstart"), |
904 | + err->message); |
905 | + nih_free (err); |
906 | + |
907 | + exit (EXIT_FAILURE); |
908 | + } |
909 | + |
910 | + upstart = NIH_SHOULD (nih_dbus_proxy_new (NULL, connection, |
911 | + NULL, DBUS_PATH_UPSTART, |
912 | + NULL, NULL)); |
913 | + if (! upstart) { |
914 | + NihError *err; |
915 | + |
916 | + err = nih_error_get (); |
917 | + nih_fatal ("%s: %s", _("Could not create Upstart proxy"), |
918 | + err->message); |
919 | + nih_free (err); |
920 | + |
921 | + exit (EXIT_FAILURE); |
922 | + } |
923 | + |
924 | + /* Connect signals to be notified when jobs come and go */ |
925 | + if (! nih_dbus_proxy_connect (upstart, &upstart_com_ubuntu_Upstart0_6, "JobAdded", |
926 | + (NihDBusSignalHandler)upstart_job_added, NULL)) { |
927 | + NihError *err; |
928 | + |
929 | + err = nih_error_get (); |
930 | + nih_fatal ("%s: %s", _("Could not create JobAdded signal connection"), |
931 | + err->message); |
932 | + nih_free (err); |
933 | + |
934 | + exit (EXIT_FAILURE); |
935 | + } |
936 | + |
937 | + if (! nih_dbus_proxy_connect (upstart, &upstart_com_ubuntu_Upstart0_6, "JobRemoved", |
938 | + (NihDBusSignalHandler)upstart_job_removed, NULL)) { |
939 | + NihError *err; |
940 | + |
941 | + err = nih_error_get (); |
942 | + nih_fatal ("%s: %s", _("Could not create JobRemoved signal connection"), |
943 | + err->message); |
944 | + nih_free (err); |
945 | + |
946 | + exit (EXIT_FAILURE); |
947 | + } |
948 | + |
949 | + /* Request a list of all current jobs */ |
950 | + if (upstart_get_all_jobs_sync (NULL, upstart, &job_class_paths) < 0) { |
951 | + NihError *err; |
952 | + |
953 | + err = nih_error_get (); |
954 | + nih_fatal ("%s: %s", _("Could not obtain job list"), |
955 | + err->message); |
956 | + nih_free (err); |
957 | + |
958 | + exit (EXIT_FAILURE); |
959 | + } |
960 | + |
961 | + /* Look for jobs that specify the FILE_EVENT event and handle |
962 | + * them. |
963 | + */ |
964 | + for (char **job_class_path = job_class_paths; |
965 | + job_class_path && *job_class_path; job_class_path++) { |
966 | + upstart_job_added (NULL, NULL, *job_class_path); |
967 | + } |
968 | + |
969 | + nih_free (job_class_paths); |
970 | + |
971 | + /* Become daemon */ |
972 | + if (daemonise) { |
973 | + if (nih_main_daemonise () < 0) { |
974 | + NihError *err; |
975 | + |
976 | + err = nih_error_get (); |
977 | + nih_fatal ("%s: %s", _("Unable to become daemon"), |
978 | + err->message); |
979 | + nih_free (err); |
980 | + |
981 | + exit (EXIT_FAILURE); |
982 | + } |
983 | + |
984 | + /* Send all logging output to syslog */ |
985 | + openlog (program_name, LOG_PID, LOG_DAEMON); |
986 | + nih_log_set_logger (nih_logger_syslog); |
987 | + } |
988 | + |
989 | + if (user) { |
990 | + /* Ensure we are sitting in $HOME so relative FPATH |
991 | + * values work as expected. |
992 | + */ |
993 | + if (chdir (home_dir) < 0) { |
994 | + nih_error ("Failed to change working directory"); |
995 | + exit (EXIT_FAILURE); |
996 | + } |
997 | + } |
998 | + |
999 | + /* Handle TERM and INT signals gracefully */ |
1000 | + nih_signal_set_handler (SIGTERM, nih_signal_handler); |
1001 | + NIH_MUST (nih_signal_add_handler (NULL, SIGTERM, nih_main_term_signal, NULL)); |
1002 | + |
1003 | + if (! daemonise) { |
1004 | + nih_signal_set_handler (SIGINT, nih_signal_handler); |
1005 | + NIH_MUST (nih_signal_add_handler (NULL, SIGINT, nih_main_term_signal, NULL)); |
1006 | + } |
1007 | + |
1008 | + ret = nih_main_loop (); |
1009 | + |
1010 | + return ret; |
1011 | +} |
1012 | + |
1013 | +/** |
1014 | + * upstart_job_added: |
1015 | + * |
1016 | + * @data: (unused), |
1017 | + * @message: Nih D-Bus message (unused), |
1018 | + * @job_path: Upstart job class (D-Bus) path associated with job. |
1019 | + * |
1020 | + * Called automatically when a new Upstart job appears on D-Bus ("JobAdded" signal). |
1021 | + **/ |
1022 | +static void |
1023 | +upstart_job_added (void *data, |
1024 | + NihDBusMessage *message, |
1025 | + const char *job_path) |
1026 | +{ |
1027 | + Job *job; |
1028 | + nih_local NihDBusProxy *job_class = NULL; |
1029 | + nih_local char ***start_on = NULL; |
1030 | + nih_local char ***stop_on = NULL; |
1031 | + |
1032 | + nih_assert (job_path); |
1033 | + |
1034 | + /* Obtain a proxy to the job */ |
1035 | + job_class = nih_dbus_proxy_new (NULL, upstart->connection, |
1036 | + upstart->name, job_path, |
1037 | + NULL, NULL); |
1038 | + if (! job_class) { |
1039 | + NihError *err; |
1040 | + |
1041 | + err = nih_error_get (); |
1042 | + nih_error ("Could not create proxy for job %s: %s", |
1043 | + job_path, err->message); |
1044 | + nih_free (err); |
1045 | + |
1046 | + return; |
1047 | + } |
1048 | + |
1049 | + job_class->auto_start = FALSE; |
1050 | + |
1051 | + /* Obtain the start_on and stop_on properties of the job */ |
1052 | + if (job_class_get_start_on_sync (NULL, job_class, &start_on) < 0) { |
1053 | + NihError *err; |
1054 | + |
1055 | + err = nih_error_get (); |
1056 | + nih_error ("Could not obtain job start condition %s: %s", |
1057 | + job_path, err->message); |
1058 | + nih_free (err); |
1059 | + |
1060 | + return; |
1061 | + } |
1062 | + |
1063 | + if (job_class_get_stop_on_sync (NULL, job_class, &stop_on) < 0) { |
1064 | + NihError *err; |
1065 | + |
1066 | + err = nih_error_get (); |
1067 | + nih_error ("Could not obtain job stop condition %s: %s", |
1068 | + job_path, err->message); |
1069 | + nih_free (err); |
1070 | + |
1071 | + return; |
1072 | + } |
1073 | + |
1074 | + /* Free any existing record for the job (should never happen, |
1075 | + * but worth being safe). |
1076 | + */ |
1077 | + job = (Job *)nih_hash_lookup (jobs, job_path); |
1078 | + if (job) |
1079 | + nih_free (job); |
1080 | + |
1081 | + /* Create new record for the job */ |
1082 | + job = job_new (job_path); |
1083 | + if (! job) { |
1084 | + nih_error ("%s %s", |
1085 | + _("Failed to create job"), job_path); |
1086 | + return; |
1087 | + } |
1088 | + |
1089 | + /* Find out whether this job listens for any FILE_EVENT events */ |
1090 | + for (char ***event = start_on; event && *event && **event; event++) { |
1091 | + if (! strcmp (**event, FILE_EVENT)) |
1092 | + job_add_file (job, *event); |
1093 | + } |
1094 | + |
1095 | + for (char ***event = stop_on; event && *event && **event; event++) |
1096 | + if (! strcmp (**event, FILE_EVENT)) |
1097 | + job_add_file (job, *event); |
1098 | + |
1099 | + /* If we didn't end up with any files, free the job and move on */ |
1100 | + if (NIH_LIST_EMPTY (&job->files)) { |
1101 | + nih_free (job); |
1102 | + return; |
1103 | + } |
1104 | + |
1105 | + nih_message ("Job got added %s", job_path); |
1106 | +} |
1107 | + |
1108 | +/** |
1109 | + * upstart_job_removed: |
1110 | + * |
1111 | + * @data: (unused), |
1112 | + * @message: Nih D-Bus message (unused), |
1113 | + * @job_path: Upstart job class (D-Bus) path associated with job. |
1114 | + * |
1115 | + * Called automatically when an Upstart job disappears from D-Bus |
1116 | + * ("JobRemoved" signal). |
1117 | + * |
1118 | + **/ |
1119 | +static void |
1120 | +upstart_job_removed (void *data, |
1121 | + NihDBusMessage *message, |
1122 | + const char *job_path) |
1123 | +{ |
1124 | + Job *job; |
1125 | + |
1126 | + nih_assert (job_path); |
1127 | + |
1128 | + job = (Job *)nih_hash_lookup (jobs, job_path); |
1129 | + |
1130 | + if (! job) |
1131 | + return; |
1132 | + |
1133 | + nih_message ("Job went away %s", job_path); |
1134 | + |
1135 | + nih_free (job); |
1136 | +} |
1137 | + |
1138 | + |
1139 | +/** |
1140 | + * job_add_file: |
1141 | + * |
1142 | + * @job: Job, |
1143 | + * @file_info: environment variables Upstart job has specified |
1144 | + * relating to FILE_EVENT. |
1145 | + * |
1146 | + * Create a WatchedFile object based on @file_info and ensure that |
1147 | + * WatchedFile file (or glob) is watched. |
1148 | + **/ |
1149 | +static void |
1150 | +job_add_file (Job *job, |
1151 | + char **file_info) |
1152 | +{ |
1153 | + uint32_t events; |
1154 | + WatchedFile *file = NULL; |
1155 | + nih_local char *error = NULL; |
1156 | + nih_local char *glob_expr = NULL; |
1157 | + nih_local char *expanded = NULL; |
1158 | + char path[PATH_MAX]; |
1159 | + |
1160 | + nih_assert (job); |
1161 | + nih_assert (job->path); |
1162 | + nih_assert (file_info); |
1163 | + nih_assert (! strcmp (file_info[0], FILE_EVENT)); |
1164 | + |
1165 | + memset (path, '\0', sizeof (path)); |
1166 | + |
1167 | + for (char **env = file_info + 1; env && *env; env++) { |
1168 | + char *val; |
1169 | + size_t name_len; |
1170 | + |
1171 | + val = strchr (*env, '='); |
1172 | + if (! val) { |
1173 | + nih_warn ("%s: Ignored %s event without variable name", |
1174 | + job->path, FILE_EVENT); |
1175 | + goto error; |
1176 | + } |
1177 | + |
1178 | + name_len = val - *env; |
1179 | + val++; |
1180 | + |
1181 | + if (! strncmp (*env, "FPATH", name_len)) { |
1182 | + char dirpart[PATH_MAX]; |
1183 | + char basepart[PATH_MAX]; |
1184 | + char *dir; |
1185 | + char *base; |
1186 | + size_t len2; |
1187 | + |
1188 | + strcpy (path, val); |
1189 | + |
1190 | + if (user && path[0] != '/') { |
1191 | + expanded = expand_path (NULL, path); |
1192 | + if (! expanded) { |
1193 | + nih_error ("Failed to expand path"); |
1194 | + goto error; |
1195 | + } |
1196 | + } |
1197 | + |
1198 | + if (! path_valid (path)) |
1199 | + goto error; |
1200 | + |
1201 | + strcpy (dirpart, path); |
1202 | + dir = dirname (dirpart); |
1203 | + |
1204 | + /* See dirname(3) */ |
1205 | + nih_assert (*dir != '.'); |
1206 | + |
1207 | + len2 = strlen (dir); |
1208 | + |
1209 | + if (strcspn (dir, GLOB_CHARS) < len2) { |
1210 | + nih_warn ("%s: %s", job->path, _("Directory globbing not supported")); |
1211 | + goto error; |
1212 | + } |
1213 | + |
1214 | + strcpy (basepart, path); |
1215 | + base = basename (basepart); |
1216 | + |
1217 | + /* See dirname(3) */ |
1218 | + nih_assert (strcmp (base, basepart)); |
1219 | + |
1220 | + len2 = strlen (base); |
1221 | + |
1222 | + if (strcspn (base, GLOB_CHARS) < len2) { |
1223 | + strcpy (path, dir); |
1224 | + glob_expr = NIH_MUST (nih_strdup (NULL, base)); |
1225 | + } |
1226 | + } else if (! strncmp (*env, "FEVENT", name_len)) { |
1227 | + if (! strcmp (val, "create")) { |
1228 | + events = IN_CREATE; |
1229 | + } else if (! strcmp (val, "modify")) { |
1230 | + events = (IN_MODIFY|IN_CLOSE_WRITE); |
1231 | + } else if (! strcmp (val, "delete")) { |
1232 | + events |= IN_DELETE; |
1233 | + } |
1234 | + } |
1235 | + } |
1236 | + |
1237 | + if (! *path) |
1238 | + goto error; |
1239 | + |
1240 | + if (! events) |
1241 | + events = ALL_FILE_EVENTS; |
1242 | + |
1243 | + file = watched_file_new (expanded ? expanded : path, |
1244 | + expanded ? path : NULL, |
1245 | + events, glob_expr); |
1246 | + |
1247 | + if (! file) { |
1248 | + nih_warn ("%s: %s", |
1249 | + _("Failed to add new file"), path); |
1250 | + goto error; |
1251 | + } |
1252 | + |
1253 | + /* If the job cares about the file or directory existing and it |
1254 | + * _already_ exists, emit the event. |
1255 | + * |
1256 | + * Although technically fraudulent (the file might not have _just |
1257 | + * been created_ - it may have existed forever), it is necessary |
1258 | + * since otherwise jobs will hang around wating for the file to |
1259 | + * be 'freshly-created'. However, although nih_watch_new() has |
1260 | + * been told to run the create handler for pre-existing files |
1261 | + * that doesn't help as we don't watch the files, we watch |
1262 | + * their first existing parent directory. |
1263 | + **/ |
1264 | + if ((file->events & IN_CREATE)) { |
1265 | + struct stat statbuf; |
1266 | + |
1267 | + if (glob_expr) { |
1268 | + glob_t globbuf; |
1269 | + char pattern[PATH_MAX]; |
1270 | + |
1271 | + sprintf (pattern, "%s/%s", |
1272 | + expanded ? expanded : path, glob_expr); |
1273 | + |
1274 | + if (! glob (pattern, 0, NULL, &globbuf)) { |
1275 | + size_t i; |
1276 | + char **results; |
1277 | + |
1278 | + results = globbuf.gl_pathv; |
1279 | + |
1280 | + /* emit one event per matching file */ |
1281 | + for (i = 0; i < globbuf.gl_pathc; i++) { |
1282 | + emit_event (pattern, IN_CREATE, results[i]); |
1283 | + } |
1284 | + } |
1285 | + |
1286 | + globfree (&globbuf); |
1287 | + } else { |
1288 | + if (! stat (file->path, &statbuf)) |
1289 | + emit_event (file->path, IN_CREATE, NULL); |
1290 | + } |
1291 | + } |
1292 | + |
1293 | + ensure_watched (job, file); |
1294 | + |
1295 | + return; |
1296 | + |
1297 | +error: |
1298 | + if (file) |
1299 | + nih_free (file); |
1300 | +} |
1301 | + |
1302 | +/** |
1303 | + * file_filter: |
1304 | + * |
1305 | + * @dir: WatchedDir, |
1306 | + * @path: full path to file to consider, |
1307 | + * @is_dir: TRUE if @path is a directory, else FALSE. |
1308 | + * |
1309 | + * Watch handler function to sift the wheat from the chaff. |
1310 | + * |
1311 | + * Returns: TRUE if @path should be ignored, FALSE otherwise. |
1312 | + **/ |
1313 | +int |
1314 | +file_filter (WatchedDir *dir, |
1315 | + const char *path, |
1316 | + int is_dir) |
1317 | +{ |
1318 | + nih_assert (dir); |
1319 | + nih_assert (path); |
1320 | + |
1321 | + skip_slashes (path); |
1322 | + |
1323 | + NIH_HASH_FOREACH_SAFE (dir->files, iter) { |
1324 | + WatchedFile *file = (WatchedFile *)iter; |
1325 | + |
1326 | + if (strstr (file->path, path) == file->path) { |
1327 | + /* Either an exact match or path is a child of the watched file. |
1328 | + * Paths in the latter category will be inspected more closely by |
1329 | + * the handlers. |
1330 | + */ |
1331 | + return FALSE; |
1332 | + } else if ((file->dir || file->glob) && strstr (path, file->path) == path) { |
1333 | + return FALSE; |
1334 | + } |
1335 | + } |
1336 | + |
1337 | + return TRUE; |
1338 | +} |
1339 | + |
1340 | +/** |
1341 | + * create_handler: |
1342 | + * |
1343 | + * @dir: WatchedDir, |
1344 | + * @watch: NihWatch for directory tree, |
1345 | + * @path: full path to file, |
1346 | + * @statbuf: stat of @path. |
1347 | + * |
1348 | + * Watch handler function called when a WatchedFile is created in @dir. |
1349 | + **/ |
1350 | +void |
1351 | +create_handler (WatchedDir *dir, |
1352 | + NihWatch *watch, |
1353 | + const char *path, |
1354 | + struct stat *statbuf) |
1355 | +{ |
1356 | + WatchedDir *new_dir; |
1357 | + char *p; |
1358 | + int add_dir = FALSE; |
1359 | + int empty; |
1360 | + |
1361 | + /* Hash of events already emitted (required to avoid sending |
1362 | + * same event multiple times). |
1363 | + */ |
1364 | + nih_local NihHash *handled = NULL; |
1365 | + |
1366 | + /* List of existing WatchedFiles that need to be added against |
1367 | + * @path (since @path either exactly matches their path, or |
1368 | + * @path is more specific ancestor of their path). |
1369 | + */ |
1370 | + NihList entries; |
1371 | + |
1372 | + nih_assert (dir); |
1373 | + nih_assert (watch); |
1374 | + nih_assert (path); |
1375 | + nih_assert (statbuf); |
1376 | + |
1377 | + skip_slashes (path); |
1378 | + |
1379 | + /* path should be a file below the WatchedDir */ |
1380 | + nih_assert (strstr (path, dir->path) == path); |
1381 | + |
1382 | + nih_list_init (&entries); |
1383 | + handled = NIH_MUST (nih_hash_string_new (NULL, 0)); |
1384 | + |
1385 | + NIH_HASH_FOREACH_SAFE (dir->files, iter) { |
1386 | + WatchedFile *file = (WatchedFile *)iter; |
1387 | + |
1388 | + if (file->dir) { |
1389 | + if (! strcmp (file->path, dir->path)) { |
1390 | + /* Watch is on the directory itself and a file within that |
1391 | + * watched directory was created, hence emit the _directory_ |
1392 | + * was modified. |
1393 | + */ |
1394 | + if (file->events & IN_MODIFY) |
1395 | + handle_event (handled, file->path, IN_MODIFY, path); |
1396 | + } else if (! strcmp (file->path, path)) { |
1397 | + /* Directory has been created */ |
1398 | + handle_event (handled, file->path, IN_CREATE, NULL); |
1399 | + add_dir = TRUE; |
1400 | + nih_list_add (&entries, &file->entry); |
1401 | + } |
1402 | + } else if (file->glob) { |
1403 | + char full_path[PATH_MAX]; |
1404 | + |
1405 | + /* reconstruct the full path */ |
1406 | + strcpy (full_path, file->path); |
1407 | + strcat (full_path, "/"); |
1408 | + strcat (full_path, file->glob); |
1409 | + |
1410 | + if (! fnmatch (full_path, path, FNM_PATHNAME) && (file->events & IN_CREATE)) |
1411 | + handle_event (handled, full_path, IN_CREATE, path); |
1412 | + } else { |
1413 | + if (! strcmp (file->path, path) && (file->events & IN_CREATE)) { |
1414 | + /* exact match, so emit event */ |
1415 | + handle_event (handled, file->path, IN_CREATE, NULL); |
1416 | + |
1417 | + } else if ((p=strstr (file->path, path)) && p == file->path |
1418 | + && S_ISDIR (statbuf->st_mode)) { |
1419 | + /* The created file is actually a directory |
1420 | + * more specific that the current watch |
1421 | + * directory associated with @file. |
1422 | + * |
1423 | + * As such, we can make the watch on @file more |
1424 | + * specific by dropping the old watch, creating |
1425 | + * a new WatchedDir for @path and adding @file |
1426 | + * to the new WatchedDir's files hash. |
1427 | + * |
1428 | + * This has to be handled carefully due to NIH |
1429 | + * list/hash handling constraints. First, the |
1430 | + * new directory is marked as needing to be |
1431 | + * added to the directory hash and secondly we |
1432 | + * add the WatchedFile to a list representing |
1433 | + * all WatchedFiles that need to be added for |
1434 | + * the new path. |
1435 | + */ |
1436 | + add_dir = TRUE; |
1437 | + nih_list_add (&entries, &file->entry); |
1438 | + } |
1439 | + } |
1440 | + } |
1441 | + |
1442 | + if (! add_dir) |
1443 | + return; |
1444 | + |
1445 | + /* we should have atleast 1 file to add to the new watch */ |
1446 | + nih_assert (! NIH_LIST_EMPTY (&entries)); |
1447 | + |
1448 | + new_dir = watched_dir_new (path, statbuf); |
1449 | + if (! new_dir) { |
1450 | + nih_warn ("%s: %s", |
1451 | + _("Failed to watch directory"), path); |
1452 | + return; |
1453 | + } |
1454 | + |
1455 | + /* Add all list entries to the newly-created WatchedDir */ |
1456 | + NIH_LIST_FOREACH_SAFE (&entries, iter) { |
1457 | + WatchedFile *file = (WatchedFile *)iter; |
1458 | + |
1459 | + nih_hash_add (new_dir->files, &file->entry); |
1460 | + } |
1461 | + |
1462 | + empty = TRUE; |
1463 | + NIH_HASH_FOREACH (dir->files, iter) { |
1464 | + empty = FALSE; |
1465 | + break; |
1466 | + } |
1467 | + |
1468 | + if (empty) { |
1469 | + /* Remove the old directory watch */ |
1470 | + nih_free (dir); |
1471 | + } |
1472 | +} |
1473 | + |
1474 | +/** |
1475 | + * modify_handler: |
1476 | + * |
1477 | + * @dir: WatchedDir, |
1478 | + * @watch: NihWatch for directory tree, |
1479 | + * @path: full path to file, |
1480 | + * @statbuf: stat of @path. |
1481 | + * |
1482 | + * Watch handler function called when a WatchedFile is modified in @dir. |
1483 | + **/ |
1484 | +void |
1485 | +modify_handler (WatchedDir *dir, |
1486 | + NihWatch *watch, |
1487 | + const char *path, |
1488 | + struct stat *statbuf) |
1489 | +{ |
1490 | + nih_local NihHash *handled = NULL; |
1491 | + |
1492 | + nih_assert (dir); |
1493 | + nih_assert (watch); |
1494 | + nih_assert (path); |
1495 | + nih_assert (statbuf); |
1496 | + |
1497 | + /* path should be a file below the WatchedDir */ |
1498 | + nih_assert (strstr (path, dir->path) == path); |
1499 | + |
1500 | + skip_slashes (path); |
1501 | + |
1502 | + handled = NIH_MUST (nih_hash_string_new (NULL, 0)); |
1503 | + |
1504 | + NIH_HASH_FOREACH_SAFE (dir->files, iter) { |
1505 | + WatchedFile *file = (WatchedFile *)iter; |
1506 | + |
1507 | + if (! (file->events & IN_MODIFY)) |
1508 | + continue; |
1509 | + |
1510 | + if (file->dir) { |
1511 | + if (! strcmp (file->path, dir->path)) { |
1512 | + /* Watch is on the directory itself and a file within that |
1513 | + * watched directory was modified, hence emit the _directory_ |
1514 | + * was modified. |
1515 | + */ |
1516 | + handle_event (handled, original_path (file), IN_MODIFY, path); |
1517 | + } |
1518 | + } else if (file->glob) { |
1519 | + char full_path[PATH_MAX]; |
1520 | + |
1521 | + /* reconstruct the full path */ |
1522 | + strcpy (full_path, file->path); |
1523 | + strcat (full_path, "/"); |
1524 | + strcat (full_path, file->glob); |
1525 | + if (! fnmatch (full_path, path, FNM_PATHNAME) && (file->events & IN_MODIFY)) |
1526 | + handle_event (handled, full_path, IN_MODIFY, path); |
1527 | + } else { |
1528 | + if (! strcmp (file->path, path)) { |
1529 | + /* exact match, so emit event */ |
1530 | + handle_event (handled, original_path (file), IN_MODIFY, NULL); |
1531 | + } else if (file->dir && strstr (path, file->path) == path) { |
1532 | + /* file in watched directory modified, so emit event */ |
1533 | + handle_event (handled, path, IN_MODIFY, NULL); |
1534 | + } |
1535 | + } |
1536 | + } |
1537 | +} |
1538 | + |
1539 | +/** |
1540 | + * delete_handler: |
1541 | + * |
1542 | + * @dir: WatchedDir, |
1543 | + * @watch: NihWatch for directory tree, |
1544 | + * @path: full path to file that was deleted. |
1545 | + * |
1546 | + * Watch handler function called when a WatchedFile is deleted in @dir. |
1547 | + */ |
1548 | +void |
1549 | +delete_handler (WatchedDir *dir, |
1550 | + NihWatch *watch, |
1551 | + const char *path) |
1552 | +{ |
1553 | + WatchedDir *new_dir; |
1554 | + char *parent; |
1555 | + char *p; |
1556 | + struct stat statbuf; |
1557 | + int rm_dir = FALSE; |
1558 | + nih_local NihHash *handled = NULL; |
1559 | + |
1560 | + /* List of existing WatchedFiles that need to be added against |
1561 | + * @path (since @path either exactly matches their path, or |
1562 | + * @path is more specific ancestor of their path). |
1563 | + */ |
1564 | + NihList entries; |
1565 | + |
1566 | + nih_assert (dir); |
1567 | + nih_assert (watch); |
1568 | + nih_assert (path); |
1569 | + |
1570 | + /* path should be a file below the WatchedDir */ |
1571 | + nih_assert (strstr (path, dir->path) == path); |
1572 | + |
1573 | + skip_slashes (path); |
1574 | + |
1575 | + nih_list_init (&entries); |
1576 | + handled = NIH_MUST (nih_hash_string_new (NULL, 0)); |
1577 | + |
1578 | + NIH_HASH_FOREACH_SAFE (dir->files, iter) { |
1579 | + WatchedFile *file = (WatchedFile *)iter; |
1580 | + |
1581 | + if (file->dir) { |
1582 | + if (! strcmp (file->path, path)) { |
1583 | + /* Directory itself was deleted */ |
1584 | + handle_event (handled, original_path (file), IN_DELETE, NULL); |
1585 | + } else if (! strcmp (file->path, dir->path)) { |
1586 | + /* Watch is on the directory itself and a file within that |
1587 | + * watched directory was deleted, hence emit the directory was |
1588 | + * modified. |
1589 | + */ |
1590 | + if (file->events & IN_MODIFY) |
1591 | + handle_event (handled, original_path (file), IN_MODIFY, path); |
1592 | + } |
1593 | + } else if (file->glob) { |
1594 | + char full_path[PATH_MAX]; |
1595 | + |
1596 | + /* reconstruct the full path */ |
1597 | + strcpy (full_path, file->path); |
1598 | + strcat (full_path, "/"); |
1599 | + strcat (full_path, file->glob); |
1600 | + |
1601 | + if (! fnmatch (full_path, path, FNM_PATHNAME) && (file->events & IN_DELETE)) |
1602 | + handle_event (handled, full_path, IN_DELETE, path); |
1603 | + } else { |
1604 | + if (! strcmp (file->path, path) && (file->events & IN_DELETE)) { |
1605 | + handle_event (handled, original_path (file), IN_DELETE, NULL); |
1606 | + } else if ((p=strstr (file->path, path)) && p == file->path) { |
1607 | + /* Create a new directory watch for all |
1608 | + * WatchedFiles whose immediate parent directory |
1609 | + * matches @path (in other words, |
1610 | + * make the watch looking after a WatchedFile |
1611 | + * less specific). This has to be handled |
1612 | + * carefully due to NIH list/hash handling |
1613 | + * constraints. First, the new directory is |
1614 | + * marked as needing to be added to the |
1615 | + * directory hash and secondly we add the |
1616 | + * WatchedFile to a list representing all |
1617 | + * WatchedFiles that need to be added for the |
1618 | + * new path. |
1619 | + */ |
1620 | + rm_dir = TRUE; |
1621 | + nih_list_add (&entries, &file->entry); |
1622 | + } else if (file->dir && strstr (path, file->path) == path && (file->events & IN_DELETE)) { |
1623 | + /* file in watched directory deleted, so emit event */ |
1624 | + handle_event (handled, path, IN_DELETE, NULL); |
1625 | + } |
1626 | + } |
1627 | + } |
1628 | + |
1629 | + if (! rm_dir) |
1630 | + return; |
1631 | + |
1632 | + /* Remove the old directory watch */ |
1633 | + nih_free (dir); |
1634 | + |
1635 | + nih_assert (! NIH_LIST_EMPTY (&entries)); |
1636 | + |
1637 | + parent = find_first_parent (dir->path); |
1638 | + if (! parent) { |
1639 | + nih_warn ("%s: %s", |
1640 | + _("Failed to find parent directory"), dir->path); |
1641 | + return; |
1642 | + } |
1643 | + |
1644 | + /* Check to see if there is already an existing watch for the |
1645 | + * parent. |
1646 | + */ |
1647 | + new_dir = (WatchedDir *)nih_hash_lookup (watched_dirs, parent); |
1648 | + |
1649 | + if (! new_dir) { |
1650 | + if (stat (parent, &statbuf) < 0) { |
1651 | + nih_warn ("%s: %s", |
1652 | + _("Failed to stat directory"), parent); |
1653 | + return; |
1654 | + } |
1655 | + |
1656 | + new_dir = watched_dir_new (parent, &statbuf); |
1657 | + if (! new_dir) { |
1658 | + nih_warn ("%s: %s", |
1659 | + _("Failed to watch directory"), parent); |
1660 | + return; |
1661 | + } |
1662 | + } |
1663 | + |
1664 | + /* Add all list entries to the newly-created WatchedDir. */ |
1665 | + NIH_LIST_FOREACH_SAFE (&entries, iter) { |
1666 | + WatchedFile *file = (WatchedFile *)iter; |
1667 | + |
1668 | + nih_hash_add (new_dir->files, &file->entry); |
1669 | + } |
1670 | +} |
1671 | + |
1672 | +/** |
1673 | + * upstart_disconnected: |
1674 | + * |
1675 | + * @connection: connection to Upstart. |
1676 | + * |
1677 | + * Handler called when bridge disconnected from Upstart. |
1678 | + **/ |
1679 | +static void |
1680 | +upstart_disconnected (DBusConnection *connection) |
1681 | +{ |
1682 | + nih_fatal (_("Disconnected from Upstart")); |
1683 | + nih_main_loop_exit (1); |
1684 | +} |
1685 | + |
1686 | +/** |
1687 | + * ensure_watched: |
1688 | + * |
1689 | + * @job: job, |
1690 | + * @file: file we want to watch. |
1691 | + * |
1692 | + * Ensure that the WatchedFile file specified is watched. |
1693 | + * |
1694 | + * For regular files, this is achieved by adding a watch to |
1695 | + * the first *existing* _parent_ directory encountered and adding |
1696 | + * that WatchedDir to the watched_dirs hash. |
1697 | + * |
1698 | + * For directories, if they do not yet exist, the strategy is as for |
1699 | + * regular files. If the directories do exist, the watch is placed on |
1700 | + * the directory itself. |
1701 | + **/ |
1702 | +static void |
1703 | +ensure_watched (Job *job, |
1704 | + WatchedFile *file) |
1705 | +{ |
1706 | + WatchedDir *dir = NULL; |
1707 | + nih_local char *path = NULL; |
1708 | + NihListEntry *entry; |
1709 | + struct stat statbuf; |
1710 | + |
1711 | + nih_assert (job); |
1712 | + nih_assert (file); |
1713 | + |
1714 | + watched_dir_init (); |
1715 | + |
1716 | + if (file->dir || file->glob) { |
1717 | + if (! stat (file->path, &statbuf)) { |
1718 | + /* Directory already exists, so we can watch it, |
1719 | + * not its parent as is done for file watches. |
1720 | + */ |
1721 | + path = file->path; |
1722 | + goto lookup; |
1723 | + } |
1724 | + } |
1725 | + |
1726 | + path = find_first_parent (file->path); |
1727 | + if (! path) { |
1728 | + nih_warn ("%s: %s", |
1729 | + _("Failed to find parent directory"), file->path); |
1730 | + return; |
1731 | + } |
1732 | + |
1733 | +lookup: |
1734 | + dir = (WatchedDir *)nih_hash_lookup (watched_dirs, path); |
1735 | + if (! dir) { |
1736 | + dir = watched_dir_new (path, &statbuf); |
1737 | + if (! dir) |
1738 | + return; |
1739 | + } |
1740 | + |
1741 | + /* Associate the WatchedFile with the job such that when the job |
1742 | + * is freed, the corresponding files are removed from their |
1743 | + * containing WatchedDirs. |
1744 | + */ |
1745 | + nih_ref (file, job); |
1746 | + |
1747 | + file->parent = dir; |
1748 | + nih_hash_add (dir->files, &file->entry); |
1749 | + |
1750 | + /* Create a link from the job to the WatchedFile. |
1751 | + */ |
1752 | + entry = NIH_MUST (nih_list_entry_new (job)); |
1753 | + entry->data = file; |
1754 | + nih_list_add (&job->files, &entry->entry); |
1755 | +} |
1756 | + |
1757 | +/** |
1758 | + * dir_watched_init: |
1759 | + * |
1760 | + * Initialise the watched_dirs hash table. |
1761 | + **/ |
1762 | +void |
1763 | +watched_dir_init (void) |
1764 | +{ |
1765 | + if (! watched_dirs) |
1766 | + watched_dirs = NIH_MUST (nih_hash_string_new (NULL, 0)); |
1767 | +} |
1768 | + |
1769 | +/** |
1770 | + * emit_event: |
1771 | + * |
1772 | + * @path: original path as specified by a registered job, |
1773 | + * @event_type: inotify event type that occured, |
1774 | + * @match: file match that resulted from @path if it contains glob |
1775 | + * wildcards (or NULL). |
1776 | + * |
1777 | + * Emit an Upstart event. |
1778 | + **/ |
1779 | +static int |
1780 | +emit_event (const char *path, |
1781 | + uint32_t event_type, |
1782 | + const char *match) |
1783 | +{ |
1784 | + DBusPendingCall *pending_call; |
1785 | + nih_local char **env = NULL; |
1786 | + nih_local char *var = NULL; |
1787 | + size_t env_len = 0; |
1788 | + |
1789 | + nih_assert (path); |
1790 | + nih_assert (event_type == IN_CREATE || |
1791 | + event_type == IN_MODIFY || |
1792 | + event_type == IN_DELETE); |
1793 | + |
1794 | + env = NIH_MUST (nih_str_array_new (NULL)); |
1795 | + |
1796 | + var = NIH_MUST (nih_sprintf (NULL, "FPATH=%s", path)); |
1797 | + NIH_MUST (nih_str_array_addp (&env, NULL, &env_len, var)); |
1798 | + |
1799 | + var = NIH_MUST (nih_sprintf (NULL, "FEVENT=%s", |
1800 | + event_type == IN_CREATE ? "create" : |
1801 | + event_type == IN_MODIFY ? "modify" : |
1802 | + "delete")); |
1803 | + NIH_MUST (nih_str_array_addp (&env, NULL, &env_len, var)); |
1804 | + |
1805 | + if (match) { |
1806 | + var = NIH_MUST (nih_sprintf (NULL, "FMATCH=%s", match)); |
1807 | + NIH_MUST (nih_str_array_addp (&env, NULL, &env_len, var)); |
1808 | + } |
1809 | + |
1810 | + pending_call = NIH_SHOULD (upstart_emit_event (upstart, |
1811 | + FILE_EVENT, env, FALSE, |
1812 | + NULL, emit_event_error, NULL, |
1813 | + NIH_DBUS_TIMEOUT_NEVER)); |
1814 | + if (! pending_call) { |
1815 | + NihError *err; |
1816 | + |
1817 | + err = nih_error_get (); |
1818 | + nih_warn ("%s", err->message); |
1819 | + nih_free (err); |
1820 | + return FALSE; |
1821 | + } |
1822 | + |
1823 | + return TRUE; |
1824 | +} |
1825 | + |
1826 | +/** |
1827 | + * emit_event_error: |
1828 | + * |
1829 | + * @data: (unused), |
1830 | + * @message: Nih D-Bus message (unused), |
1831 | + * |
1832 | + * Handle failure to emit an event by consuming raised error and |
1833 | + * displaying its details. |
1834 | + **/ |
1835 | +static void |
1836 | +emit_event_error (void *data, |
1837 | + NihDBusMessage *message) |
1838 | +{ |
1839 | + NihError *err; |
1840 | + |
1841 | + err = nih_error_get (); |
1842 | + nih_warn ("%s", err->message); |
1843 | + nih_free (err); |
1844 | +} |
1845 | + |
1846 | +/** |
1847 | + * watched_dir_new: |
1848 | + * |
1849 | + * @path: Absolute path to watch. |
1850 | + * @statbuf: stat of @path. |
1851 | + * |
1852 | + * Create a new directory watch object for @path. |
1853 | + * |
1854 | + * Returns: WatchedDir or NULL on error. |
1855 | + **/ |
1856 | +static WatchedDir * |
1857 | +watched_dir_new (const char *path, |
1858 | + const struct stat *statbuf) |
1859 | +{ |
1860 | + char watched_path[PATH_MAX]; |
1861 | + size_t len; |
1862 | + WatchedDir *dir; |
1863 | + |
1864 | + nih_assert (path); |
1865 | + nih_assert (statbuf); |
1866 | + |
1867 | + /* we shouldn't already be watching this directory */ |
1868 | + nih_assert (! nih_hash_lookup (watched_dirs, path)); |
1869 | + |
1870 | + watched_dir_init (); |
1871 | + |
1872 | + strcpy (watched_path, path); |
1873 | + len = strlen (watched_path); |
1874 | + |
1875 | + if (len > 1 && watched_path[len-1] == '/') { |
1876 | + /* Better to remove a trailing slash before handing to |
1877 | + * inotify since although all works as expected, the |
1878 | + * path handed to inotify also gets given to the |
1879 | + * create/modify/delete handlers which can then lead to |
1880 | + * multiple consecutive slashes which could result in |
1881 | + * jobs failing to start as they would not expect FMATCH |
1882 | + * to contain such values. |
1883 | + * |
1884 | + * Note that we do not (cannot) do this if @path is |
1885 | + * the root directory. |
1886 | + */ |
1887 | + watched_path[len-1] = '\0'; |
1888 | + } |
1889 | + |
1890 | + dir = nih_new (watched_dirs, WatchedDir); |
1891 | + if (! dir) |
1892 | + return NULL; |
1893 | + |
1894 | + nih_list_init (&dir->entry); |
1895 | + |
1896 | + nih_alloc_set_destructor (dir, nih_list_destroy); |
1897 | + |
1898 | + dir->path = nih_strdup (dir, path); |
1899 | + if (! dir->path) |
1900 | + goto error; |
1901 | + |
1902 | + dir->files = nih_hash_string_new (dir, 0); |
1903 | + if (! dir->files) |
1904 | + goto error; |
1905 | + |
1906 | + nih_hash_add (watched_dirs, &dir->entry); |
1907 | + |
1908 | + /* Create a watch on the specified directory. |
1909 | + * |
1910 | + * Don't set a recursive watch as there is no need |
1911 | + * (individual jobs only care about a single directory, |
1912 | + * and anyway the parent directory may be arbitrarily |
1913 | + * deep so it could be prohibitively expensive). |
1914 | + */ |
1915 | + dir->watch = nih_watch_new (dir, watched_path, |
1916 | + FALSE, TRUE, |
1917 | + (NihFileFilter)file_filter, |
1918 | + (NihCreateHandler)create_handler, |
1919 | + (NihModifyHandler)modify_handler, |
1920 | + (NihDeleteHandler)delete_handler, |
1921 | + dir); |
1922 | + if (! dir->watch) { |
1923 | + NihError *err; |
1924 | + |
1925 | + err = nih_error_get (); |
1926 | + nih_fatal ("%s %s: %s", _("Could not create watch for path"), |
1927 | + path, |
1928 | + err->message); |
1929 | + nih_free (err); |
1930 | + |
1931 | + goto error; |
1932 | + } |
1933 | + |
1934 | + return dir; |
1935 | + |
1936 | +error: |
1937 | + nih_free (dir); |
1938 | + return NULL; |
1939 | +} |
1940 | + |
1941 | +/** |
1942 | + * watched_file_new: |
1943 | + * |
1944 | + * @path: full path to file, |
1945 | + * @original: original (relative) path specified by job, |
1946 | + * @events: events job wishes to watch for @path, |
1947 | + * @glob: glob file pattern, or NULL, |
1948 | + * |
1949 | + * Create a WatchedFile object representing @path. |
1950 | + * |
1951 | + * If path expansion was required, @original must specify the original |
1952 | + * path as specified by the job else it may be NULL. |
1953 | + * |
1954 | + * If @glob is set, @path will be the directory portion of the original |
1955 | + * path with @glob being the file (or basename) portion. |
1956 | + * |
1957 | + * Returns: WatchedFile object, or NULL on insufficient memory. |
1958 | + **/ |
1959 | +static WatchedFile * |
1960 | +watched_file_new (const char *path, |
1961 | + const char *original, |
1962 | + uint32_t events, |
1963 | + const char *glob) |
1964 | +{ |
1965 | + size_t len; |
1966 | + WatchedFile *file; |
1967 | + |
1968 | + nih_assert (path); |
1969 | + nih_assert (events); |
1970 | + |
1971 | + file = nih_new (NULL, WatchedFile); |
1972 | + if (! file) |
1973 | + return NULL; |
1974 | + |
1975 | + nih_list_init (&file->entry); |
1976 | + |
1977 | + nih_alloc_set_destructor (file, nih_list_destroy); |
1978 | + |
1979 | + len = strlen (path); |
1980 | + |
1981 | + file->dir = (path[len-1] == '/'); |
1982 | + |
1983 | + /* optionally one or the other, but not both */ |
1984 | + if (file->dir || file->glob) |
1985 | + nih_assert (file->dir || file->glob); |
1986 | + |
1987 | + file->path = nih_strdup (file, path); |
1988 | + if (! file->path) |
1989 | + goto error; |
1990 | + |
1991 | + file->original = NULL; |
1992 | + if (original) { |
1993 | + file->original = nih_strdup (file, original); |
1994 | + if (! file->original) |
1995 | + goto error; |
1996 | + } |
1997 | + |
1998 | + file->glob = NULL; |
1999 | + if (glob) { |
2000 | + file->glob = nih_strdup (file, glob); |
2001 | + if (! file->glob) |
2002 | + goto error; |
2003 | + } |
2004 | + |
2005 | + file->events = events; |
2006 | + |
2007 | + return file; |
2008 | + |
2009 | +error: |
2010 | + nih_free (file); |
2011 | + return NULL; |
2012 | +} |
2013 | + |
2014 | +/** |
2015 | + * job_new: |
2016 | + * |
2017 | + * @path: Upstart job class (D-Bus) path job is registered on. |
2018 | + * |
2019 | + * Create a new Job object representing an Upstart job. |
2020 | + * |
2021 | + * Returns: job, or NULL on insufficient memory. |
2022 | + **/ |
2023 | +static Job * |
2024 | +job_new (const char *path) |
2025 | +{ |
2026 | + Job *job; |
2027 | + |
2028 | + nih_assert (path); |
2029 | + |
2030 | + job = nih_new (NULL, Job); |
2031 | + if (! job) |
2032 | + return NULL; |
2033 | + |
2034 | + nih_list_init (&job->entry); |
2035 | + nih_list_init (&job->files); |
2036 | + |
2037 | + nih_alloc_set_destructor (job, job_destroy); |
2038 | + |
2039 | + job->path = nih_strdup (job, path); |
2040 | + if (! job->path) |
2041 | + goto error; |
2042 | + |
2043 | + nih_hash_add (jobs, &job->entry); |
2044 | + |
2045 | + return job; |
2046 | + |
2047 | +error: |
2048 | + nih_free (job); |
2049 | + return NULL; |
2050 | +} |
2051 | + |
2052 | +/** |
2053 | + * job_destroy: |
2054 | + * |
2055 | + * @job: job. |
2056 | + * |
2057 | + * Destructor that handles the replacement and deletion of a Job, |
2058 | + * ensuring that it is removed from the containing linked list and that |
2059 | + * the item attached to it is destroyed if not currently in use. |
2060 | + * |
2061 | + * Normally used or called from an nih_alloc() destructor so that the |
2062 | + * list item is automatically removed from its containing list when |
2063 | + * freed. |
2064 | + * |
2065 | + * Returns: zero. |
2066 | + **/ |
2067 | +static int |
2068 | +job_destroy (Job *job) |
2069 | +{ |
2070 | + nih_assert (job); |
2071 | + |
2072 | + nih_list_destroy (&job->entry); |
2073 | + |
2074 | + NIH_LIST_FOREACH_SAFE (&job->files, iter) { |
2075 | + NihListEntry *entry = (NihListEntry *)iter; |
2076 | + WatchedFile *file; |
2077 | + |
2078 | + nih_assert (entry->data); |
2079 | + |
2080 | + file = (WatchedFile *)entry->data; |
2081 | + |
2082 | + /* Remove file from associated WatchedDir */ |
2083 | + nih_free (file); |
2084 | + } |
2085 | + |
2086 | + return 0; |
2087 | +} |
2088 | + |
2089 | +/** |
2090 | + * find_first_parent: |
2091 | + * @path: initial absolute path to start search from. |
2092 | + * |
2093 | + * Starting at @path, search for the first existing path by |
2094 | + * progressively removing individual path elements until an existing |
2095 | + * path is found. |
2096 | + * |
2097 | + * Returns: Newly-allocated string representing path closest to @path |
2098 | + * that currently exists, or NULL on insufficient memory. |
2099 | + **/ |
2100 | +static char * |
2101 | +find_first_parent (const char *path) |
2102 | +{ |
2103 | + char current[PATH_MAX]; |
2104 | + char tmp[PATH_MAX]; |
2105 | + char *parent; |
2106 | + WatchedDir *dir = NULL; |
2107 | + struct stat statbuf; |
2108 | + |
2109 | + nih_assert (path); |
2110 | + |
2111 | + /* Ensure path is absolute */ |
2112 | + nih_assert (path[0] == '/'); |
2113 | + |
2114 | + strncpy (current, path, sizeof (current)); |
2115 | + /* ensure termination */ |
2116 | + current[PATH_MAX-1] = '\0'; |
2117 | + |
2118 | + do { |
2119 | + /* save parent for next time through the loop */ |
2120 | + strcpy (tmp, current); |
2121 | + parent = dirname (tmp); |
2122 | + |
2123 | + /* Ensure dirname returned something sane */ |
2124 | + nih_assert (strcmp (parent, ".")); |
2125 | + |
2126 | + dir = (WatchedDir *)nih_hash_lookup (watched_dirs, current); |
2127 | + |
2128 | + if (dir || ! stat (current, &statbuf)) { |
2129 | + /* either path is already a watched directory |
2130 | + * (and hence must exist), or it actually does exist. |
2131 | + */ |
2132 | + return nih_strdup (NULL, current); |
2133 | + } |
2134 | + |
2135 | + /* Failed to find path, so make parent the path to look |
2136 | + * for. |
2137 | + */ |
2138 | + memmove (current, parent, 1+strlen (parent)); |
2139 | + } while (TRUE); |
2140 | + |
2141 | + /* If your root directory doesn't exist, you have problems :) */ |
2142 | + nih_assert_not_reached (); |
2143 | +} |
2144 | + |
2145 | +/** |
2146 | + * file_event_new: |
2147 | + * |
2148 | + * @parent: parent, |
2149 | + * @path: path that event should contain, |
2150 | + * @event: inotify event, |
2151 | + * @match: file match if @path contains glob wildcards. |
2152 | + * |
2153 | + * Returns: newly-allocated FileEvent or NULL on insufficient memory. |
2154 | + **/ |
2155 | +static FileEvent * |
2156 | +file_event_new (void *parent, const char *path, uint32_t event, const char *match) |
2157 | +{ |
2158 | + FileEvent *file_event; |
2159 | + |
2160 | + nih_assert (path); |
2161 | + nih_assert (event); |
2162 | + |
2163 | + file_event = nih_new (parent, FileEvent); |
2164 | + if (! file_event) |
2165 | + return NULL; |
2166 | + |
2167 | + nih_list_init (&file_event->entry); |
2168 | + |
2169 | + nih_alloc_set_destructor (file_event, nih_list_destroy); |
2170 | + |
2171 | + file_event->path = NIH_MUST (nih_strdup (file_event, path)); |
2172 | + file_event->event = event; |
2173 | + file_event->match = match |
2174 | + ? NIH_MUST (nih_strdup (file_event, match)) |
2175 | + : NULL; |
2176 | + |
2177 | + return file_event; |
2178 | +} |
2179 | + |
2180 | +/** |
2181 | + * handle_event: |
2182 | + * |
2183 | + * @handled: hash of FileEvents already handled, |
2184 | + * @file_event: FileEvent to consider. |
2185 | + * |
2186 | + * Determine if @file_event has already been handled; if not emit the |
2187 | + * event and record its details in @handled. |
2188 | + **/ |
2189 | +static void |
2190 | +handle_event (NihHash *handled, |
2191 | + const char *path, |
2192 | + uint32_t event, |
2193 | + const char *match) |
2194 | +{ |
2195 | + FileEvent *file_event; |
2196 | + |
2197 | + nih_assert (handled); |
2198 | + nih_assert (path); |
2199 | + nih_assert (event); |
2200 | + |
2201 | + file_event = (FileEvent *)nih_hash_search (handled, path, NULL); |
2202 | + |
2203 | + while (file_event) { |
2204 | + if ((file_event->event & event) && string_match (file_event->match, match)) { |
2205 | + return; |
2206 | + } |
2207 | + |
2208 | + file_event = (FileEvent *)nih_hash_search (handled, path, |
2209 | + &file_event->entry); |
2210 | + } |
2211 | + |
2212 | + nih_assert (! file_event); |
2213 | + |
2214 | + /* Event has not yet been handled, so emit it and record fact |
2215 | + * it's now been handled. |
2216 | + */ |
2217 | + file_event = NIH_MUST (file_event_new (handled, path, event, match)); |
2218 | + nih_hash_add (handled, &file_event->entry); |
2219 | + |
2220 | + emit_event (path, event, match); |
2221 | +} |
2222 | + |
2223 | +/** |
2224 | + * string_match: |
2225 | + * |
2226 | + * @a: first string, |
2227 | + * @b: second string. |
2228 | + * |
2229 | + * Compare @a and @b either or both of which may be NULL. |
2230 | + * |
2231 | + * Returns TRUE if strings are identical or both NULL, else FALSE. |
2232 | + **/ |
2233 | +static int |
2234 | +string_match (const char *a, const char *b) |
2235 | +{ |
2236 | + if (!a && !b) |
2237 | + return TRUE; |
2238 | + |
2239 | + if (!a || !b) |
2240 | + return FALSE; |
2241 | + |
2242 | + if (strcmp (a, b)) |
2243 | + return FALSE; |
2244 | + |
2245 | + return TRUE; |
2246 | +} |
2247 | + |
2248 | +/** |
2249 | + * expand_path: |
2250 | + * |
2251 | + * @parent: parent, |
2252 | + * @path: path. |
2253 | + * |
2254 | + * Expand @path by replacing a leading '~/', './' or no path prefix by |
2255 | + * the users home directory. |
2256 | + * |
2257 | + * Limitations: Does not expand '~user'. |
2258 | + * |
2259 | + * Returns: Newly-allocated fully-expanded path, or NULL on error. |
2260 | + **/ |
2261 | +char * |
2262 | +expand_path (const void *parent, const char *path) |
2263 | +{ |
2264 | + char *new; |
2265 | + const char *p; |
2266 | + |
2267 | + nih_assert (path); |
2268 | + |
2269 | + /* Only user instances support this limited form of relative |
2270 | + * path. |
2271 | + */ |
2272 | + nih_assert (user); |
2273 | + |
2274 | + /* Avoid looking up users password entry again */ |
2275 | + nih_assert (home_dir[0]); |
2276 | + |
2277 | + /* absolute path so nothing to do */ |
2278 | + nih_assert (path[0] != '/'); |
2279 | + |
2280 | + p = path; |
2281 | + |
2282 | + if (strstr (path, "~/") == path || strstr (path, "./") == path) |
2283 | + p += 2; |
2284 | + |
2285 | + new = nih_sprintf (parent, "%s/%s", home_dir, p); |
2286 | + |
2287 | + return new; |
2288 | +} |
2289 | + |
2290 | +/** |
2291 | + * path_valid: |
2292 | + * |
2293 | + * @path: path. |
2294 | + * |
2295 | + * Perform basic tests to determine if @path is valid for |
2296 | + * the purposes of this bridge. |
2297 | + * |
2298 | + * Returns: TRUE if @path is acceptable, else FALSE. |
2299 | + **/ |
2300 | +static int |
2301 | +path_valid (const char *path) |
2302 | +{ |
2303 | + size_t len; |
2304 | + |
2305 | + nih_assert (path); |
2306 | + |
2307 | + len = strlen (path); |
2308 | + |
2309 | + if (len > PATH_MAX-1) { |
2310 | + nih_debug ("%s: %.*s...", |
2311 | + _("Path too long"), PATH_MAX-1, path); |
2312 | + return FALSE; |
2313 | + } |
2314 | + |
2315 | + if (user) { |
2316 | + /* Support absolute or relative paths where the latter |
2317 | + * begins with a directory name implicitly below $HOME. |
2318 | + */ |
2319 | + if (*path == '.') { |
2320 | + nih_warn ("%s: %s", _("Path must be absolute"), path); |
2321 | + return FALSE; |
2322 | + } |
2323 | + } else { |
2324 | + if (*path != '/') { |
2325 | + nih_warn ("%s: %s", _("Path must be absolute"), path); |
2326 | + return FALSE; |
2327 | + } |
2328 | + } |
2329 | + |
2330 | + if (strstr (path, "../")) { |
2331 | + nih_warn ("%s: %s", _("Path must not contain parent reference"), path); |
2332 | + return FALSE; |
2333 | + } |
2334 | + |
2335 | + return TRUE; |
2336 | +} |
Note that to test this bridge, a build of NIH containing a fix for bug 777097 is required (use lp:~upstart-devel/libnih/nih for example).
Additionally, a proper fix for bug 1103406 is needed.