~jamesodhunt/upstart/async-spawn.20140310

1471.1.2 by James Hunt
* Makefile.am: Added 'test'.
1
/* upstart
2
 *
1471.1.4 by James Hunt
* test/test_util.[ch]: Renamed to test_util_common.[ch] and updated all
3
 * test_util_common.c - common test utilities
1471.1.2 by James Hunt
* Makefile.am: Added 'test'.
4
 *
1471.1.4 by James Hunt
* test/test_util.[ch]: Renamed to test_util_common.[ch] and updated all
5
 * Copyright © 2012-2013 Canonical Ltd.
1471.1.2 by James Hunt
* Makefile.am: Added 'test'.
6
 * Author: James Hunt <james.hunt@canonical.com>
7
 *
8
 * This program is free software; you can redistribute it and/or modify
9
 * it under the terms of the GNU General Public License version 2, as
10
 * published by the Free Software Foundation.
11
 *
12
 * This program is distributed in the hope that it will be useful,
13
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15
 * GNU General Public License for more details.
16
 *
17
 * You should have received a copy of the GNU General Public License along
18
 * with this program; if not, write to the Free Software Foundation, Inc.,
19
 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
20
 */
21
22
#ifdef HAVE_CONFIG_H
23
# include <config.h>
24
#endif /* HAVE_CONFIG_H */
25
26
#include <nih/test.h>
27
#include <nih/file.h>
28
#include <nih/string.h>
29
#include <nih/signal.h>
1471.1.3 by James Hunt
* init/Makefile.am: Added test_main.
30
#include <nih/logging.h>
1471.1.2 by James Hunt
* Makefile.am: Added 'test'.
31
#include <nih-dbus/test_dbus.h>
32
33
#include <dbus/dbus.h>
34
35
#include <stdio.h>
36
#include <signal.h>
37
#include <unistd.h>
38
#include <regex.h>
39
#include <sys/types.h>
40
#include <sys/stat.h>
41
#include <ctype.h>
42
43
#include <nih-dbus/dbus_error.h>
44
#include <nih-dbus/dbus_connection.h>
45
#include <nih-dbus/dbus_proxy.h>
46
#include <nih-dbus/errors.h>  
47
48
#include "dbus/upstart.h"
49
1471.1.4 by James Hunt
* test/test_util.[ch]: Renamed to test_util_common.[ch] and updated all
50
#include "test_util_common.h"
1471.1.2 by James Hunt
* Makefile.am: Added 'test'.
51
52
#ifndef UPSTART_BINARY
53
#error unable to find init binary as UPSTART_BINARY not defined
54
#endif /* UPSTART_BINARY */
55
56
#ifndef INITCTL_BINARY
57
#error unable to find initctl binary as INITCTL_BINARY not defined
58
#endif /* INITCTL_BINARY */
59
60
static void selfpipe_write (int n);
61
static void selfpipe_setup (void);
62
63
/**
64
 * wait_for_upstart:
65
 *
66
 * @user: TRUE if waiting for a Session Init (which uses a private bus
67
 * rather than the session bus), else FALSE.
68
 *
69
 * Wait for Upstart to appear on D-Bus denoting its completion of
70
 * initialisation. Wait time is somewhat arbitrary (but more
71
 * than adequate!).
72
 **/
73
void
74
wait_for_upstart (int user)
75
{
76
	nih_local NihDBusProxy *upstart = NULL;
77
	DBusConnection         *connection;
78
	char                   *address;
79
	NihError               *err;
80
	int                     running = FALSE;
81
82
	/* XXX: arbitrary value */
83
	int                     attempts = 10;
84
85
	if (user) {
86
		TEST_TRUE (set_upstart_session ());
87
		address = getenv ("UPSTART_SESSION");
88
	} else {
89
		address = getenv ("DBUS_SESSION_BUS_ADDRESS");
90
	}
91
92
	TEST_TRUE (address);
93
94
	while (attempts) {
95
		attempts--;
96
		sleep (1);
97
		connection = nih_dbus_connect (address, NULL);
98
99
		if (! connection) {
100
			err = nih_error_get ();
101
			nih_free (err);
102
			continue;
103
		}
104
105
		upstart = nih_dbus_proxy_new (NULL, connection,
106
				      	      NULL,
107
					      DBUS_PATH_UPSTART,
108
				      	      NULL, NULL);
109
110
		if (! upstart) {
111
			err = nih_error_get ();
112
			nih_free (err);
113
			dbus_connection_unref (connection);
114
		} else {
115
			running = TRUE;
116
			break;
117
		}
118
	}
119
	TEST_EQ (running, TRUE);
120
}
121
122
/* TRUE to denote that Upstart is running in user session mode
123
 * (FALSE to denote it's using the users D-Bus session bus).
124
 */
125
int test_user_mode = FALSE;
126
127
/**
128
 * set_upstart_session:
129
 *
130
 * Attempt to "enter" an Upstart session by setting UPSTART_SESSION to
131
 * the value of the currently running session.
132
 *
133
 * It is only legitimate to call this function if you have previously
134
 * started a Session Init process.
135
 *
136
 * Limitations: Assumes that at most 1 session is running.
137
 *
138
 * Returns: TRUE if it was possible to enter the currently running
139
 * Upstart session, else FALSE.
140
 **/
141
int
142
set_upstart_session (void)
143
{
144
	char                     *value;
145
	nih_local char           *cmd = NULL;
146
	nih_local char          **output = NULL;
147
	size_t                    lines = 0;
148
	int                       got = FALSE;
149
	int                       i;
150
151
	/* XXX: arbitrary value */
152
	int                       loops = 5;
153
154
	/* list-sessions relies on this */
155
	if (! getenv ("XDG_RUNTIME_DIR"))
156
		return FALSE;
157
158
	cmd = nih_sprintf (NULL, "%s list-sessions 2>&1", INITCTL_BINARY);
159
	TEST_NE_P (cmd, NULL);
160
161
	/* We expect the list-sessions command to return a valid session
162
	 * within a reasonable period of time.
163
	 */
164
	for (i = 0; i < loops; i++) {
165
		sleep (1);
166
167
		RUN_COMMAND (NULL, cmd, &output, &lines);
168
		if (lines != 1)
169
			continue;
170
171
		/* No pid in output */
172
		if (! isdigit(output[0][0]))
173
			continue;
174
175
		/* look for separator between pid and value of
176
		 * UPSTART_SESSION.
177
		 */
178
		value = strstr (output[0], " ");
179
		if (! value)
180
			continue;
181
182
		/* jump over space */
183
		value  += 1;
184
		if (! value)
185
			continue;
186
187
		/* No socket address */
188
		if (strstr (value, "unix:abstract") == value) {
189
			got = TRUE;
190
			break;
191
		}
192
	}
193
194
	if (got != TRUE)
195
		return FALSE;
196
197
	assert0 (setenv ("UPSTART_SESSION", value, 1));
198
199
	return TRUE;
200
}
201
202
/**
203
 * selfpipe:
204
 *
205
 * Used to allow a timed process wait.
206
 **/
207
static int selfpipe[2] = { -1, -1 };
208
209
static void
210
selfpipe_write (int n)
211
{
212
    assert (selfpipe[1] != -1);
213
214
    TEST_EQ (write (selfpipe[1], "", 1), 1);
215
}
216
217
/**
218
 * selfpipe_setup:
219
 *
220
 * Arrange for SIGCHLD to write to selfpipe such that we can select(2)
221
 * on child process status changes.
222
 **/
223
static void
224
selfpipe_setup (void)
225
{
226
    static struct sigaction  act;
227
    int                      read_flags;
228
    int                      write_flags;
229
230
    assert (selfpipe[0] == -1);
231
232
    assert (! pipe (selfpipe));
233
234
    /* Set non-blocking */
235
    read_flags = fcntl (selfpipe[0], F_GETFL);
236
    write_flags = fcntl (selfpipe[1], F_GETFL);
237
238
    read_flags |= O_NONBLOCK;
239
    write_flags |= O_NONBLOCK;
240
241
    assert (fcntl (selfpipe[0], F_SETFL, read_flags) == 0);
242
    assert (fcntl (selfpipe[1], F_SETFL, write_flags) == 0);
243
244
    /* Don't leak */
245
    assert (fcntl (selfpipe[0], F_SETFD, FD_CLOEXEC) == 0);
246
    assert (fcntl (selfpipe[1], F_SETFD, FD_CLOEXEC) == 0);
247
248
    memset (&act, 0, sizeof (act));
249
250
    /* register SIGCHLD handler which will cause pipe write when child
251
     * changes state.
252
     */
253
    act.sa_handler = selfpipe_write;
254
255
    sigaction (SIGCHLD, &act, NULL);
256
}
257
258
/**
259
 * timed_waitpid:
260
 *
261
 * @pid: pid to wait for,
262
 * @timeout: seconds to wait for @pid to change state.
263
 *
264
 * Simplified waitpid(2) with timeout using a pipe to allow select(2)
265
 * with timeout to be used to wait for process state change.
266
 **/
267
pid_t
268
timed_waitpid (pid_t pid, time_t timeout)
269
{
270
    static char     buffer[1];
271
    fd_set          read_fds;
272
    struct timeval  tv;
273
    int             status;
274
    int             nfds;
275
    int             ret;
276
    pid_t           ret2;
277
278
    assert (pid);
279
    assert (timeout);
280
281
    if (selfpipe[0] == -1)
282
	    selfpipe_setup ();
283
284
    FD_ZERO (&read_fds);
285
    FD_SET (selfpipe[0], &read_fds);
286
287
    nfds = 1 + selfpipe[0];
288
289
    tv.tv_sec   = timeout;
290
    tv.tv_usec  = 0;
291
292
    /* wait for some activity */
293
    ret = select (nfds, &read_fds, NULL, NULL, &tv);
294
295
    if (! ret)
296
	    /* timed out */
297
	    return 0;
298
299
    /* discard any data written to pipe */
300
    while (read (selfpipe[0], buffer, sizeof (buffer)) > 0)
301
	    ;
302
303
    while (TRUE) {
304
	    /* wait for status change or error */
305
	    ret2 = waitpid (pid, &status, WNOHANG);
306
307
	    if (ret2 < 0)
308
		    return -1;
309
310
	    if (! ret2)
311
		    /* give child a chance to change state */
312
		    sleep (1);
313
314
	    if (ret2) {
315
		    if (WIFEXITED (status))
316
			    return ret2;
317
318
		    /* unexpected status change */
319
		    return -1;
320
	    }
321
    }
322
}
323
324
325
/**
326
 * get_initctl():
327
 *
328
 * Determine a suitable initctl command-line for testing purposes.
329
 *
330
 * Returns: Static string representing full path to initctl binary with
331
 * default option to allow communication with an Upstart started using
332
 * START_UPSTART().
333
 **/
334
char *
335
get_initctl (void)
336
{
337
	static char path[PATH_MAX + 1024] = { 0 };
338
	int         ret;
339
340
	ret = sprintf (path, "%s %s",
341
			INITCTL_BINARY,
342
			test_user_mode
343
			? "--user"
344
			: "--session");
345
346
	assert (ret > 0);
347
348
	return path;
349
}
350
351
/*
352
 * _start_upstart:
353
 *
354
 * @pid: PID of running instance,
355
 * @user: TRUE if upstart will run in User Session mode (FALSE to
356
 *  use the users D-Bus session bus),
357
 * @args: optional list of arguments to specify.
358
 *
359
 * Start an instance of Upstart.
360
 *
361
 * If the instance fails to start, abort(3) is called.
362
 **/
363
void
364
_start_upstart (pid_t *pid, int user, char * const *args)
365
{
366
	nih_local char  **argv = NULL;
367
	sigset_t          child_set, orig_set;
368
369
	assert (pid);
370
371
	argv = NIH_MUST (nih_str_array_new (NULL));
372
373
	NIH_MUST (nih_str_array_add (&argv, NULL, NULL,
374
				UPSTART_BINARY));
375
376
	if (args)
377
		NIH_MUST (nih_str_array_append (&argv, NULL, NULL, args));
378
379
	sigfillset (&child_set);
380
	sigprocmask (SIG_BLOCK, &child_set, &orig_set);
381
382
	TEST_NE (*pid = fork (), -1);
383
384
	if (! *pid) {
385
		int fd;
386
		nih_signal_reset ();
387
		sigprocmask (SIG_SETMASK, &orig_set, NULL);
388
389
		if (! getenv ("UPSTART_TEST_VERBOSE")) {
390
			fd = open ("/dev/null", O_RDWR);
391
			assert (fd >= 0);
392
			assert (dup2 (fd, STDIN_FILENO) != -1);
393
			assert (dup2 (fd, STDOUT_FILENO) != -1);
394
			assert (dup2 (fd, STDERR_FILENO) != -1);
395
		}
396
397
		assert (execv (argv[0], argv) != -1);
398
	}
399
400
	sigprocmask (SIG_SETMASK, &orig_set, NULL);
401
	wait_for_upstart (user);
402
}
403
404
/**
405
 * start_upstart_common:
406
 *
407
 * @pid: PID of running instance,
408
 * @user: TRUE if upstart should run in User Session mode (FALSE to
409
 * use the users D-Bus session bus),
410
 * @confdir: full path to configuration directory,
411
 * @logdir: full path to log directory,
412
 * @extra: optional extra arguments.
413
 *
414
 * Wrapper round _start_upstart() which specifies common options.
415
 **/
416
void
417
start_upstart_common (pid_t *pid, int user, const char *confdir,
418
		      const char *logdir, char * const *extra)
419
{
420
	nih_local char  **args = NULL;
421
422
	assert (pid);
423
424
	args = NIH_MUST (nih_str_array_new (NULL));
425
426
	if (user) {
427
		NIH_MUST (nih_str_array_add (&args, NULL, NULL,
428
					"--user"));
429
		test_user_mode = TRUE;
430
	} else {
431
		TEST_TRUE (getenv ("DBUS_SESSION_BUS_ADDRESS"));
432
		NIH_MUST (nih_str_array_add (&args, NULL, NULL,
433
					"--session"));
434
	}
435
436
	NIH_MUST (nih_str_array_add (&args, NULL, NULL,
437
				"--no-startup-event"));
438
439
	NIH_MUST (nih_str_array_add (&args, NULL, NULL,
440
				"--no-sessions"));
441
442
	NIH_MUST (nih_str_array_add (&args, NULL, NULL,
443
				"--no-inherit-env"));
444
445
	if (confdir) {
446
		NIH_MUST (nih_str_array_add (&args, NULL, NULL,
447
					"--confdir"));
448
		NIH_MUST (nih_str_array_add (&args, NULL, NULL,
449
					confdir));
450
	}
451
452
	if (logdir) {
453
		NIH_MUST (nih_str_array_add (&args, NULL, NULL,
454
					"--logdir"));
455
		NIH_MUST (nih_str_array_add (&args, NULL, NULL,
456
					logdir));
457
	}
458
459
	if (extra)
460
		NIH_MUST (nih_str_array_append (&args, NULL, NULL, extra));
461
462
	_start_upstart (pid, user, args);
463
}
464
465
/**
466
 * start_upstart:
467
 *
468
 * @pid: PID of running instance.
469
 *
470
 * Wrapper round _start_upstart() which just runs an instance with no
471
 * options.
472
 **/
473
void
474
start_upstart (pid_t *pid)
475
{
476
	start_upstart_common (pid, FALSE, NULL, NULL, NULL);
477
}
478
479
/**
480
 * job_to_pid:
481
 *
482
 * @job: job name.
483
 *
484
 * Determine pid of running job.
485
 *
486
 * WARNING: it is the callers responsibility to ensure that
487
 * @job is still running when this function is called!!
488
 *
489
 * Returns: pid of job, or -1 if not found.
490
 **/
491
pid_t
492
job_to_pid (const char *job)
493
{
494
	pid_t            pid;
495
	regex_t          regex;
496
	regmatch_t       regmatch[2];
497
	int              ret;
498
	nih_local char  *cmd = NULL;
499
	nih_local char  *pattern = NULL;
500
	size_t           lines;
501
	char           **status;
502
	nih_local char  *str_pid = NULL;
503
504
	assert (job);
505
506
	pattern = NIH_MUST (nih_sprintf
507
			(NULL, "^\\b%s\\b .*, process ([0-9]+)", job));
508
509
	cmd = NIH_MUST (nih_sprintf (NULL, "%s status %s 2>&1",
510
			get_initctl (), job));
511
	RUN_COMMAND (NULL, cmd, &status, &lines);
512
	TEST_EQ (lines, 1);
513
514
	ret = regcomp (&regex, pattern, REG_EXTENDED);
515
	assert0 (ret);
516
517
	ret = regexec (&regex, status[0], 2, regmatch, 0);
518
	if (ret == REG_NOMATCH) {
519
		ret = -1;
520
		goto out;
521
	}
522
	assert0 (ret);
523
524
	if (regmatch[1].rm_so == -1 || regmatch[1].rm_eo == -1) {
525
		ret = -1;
526
		goto out;
527
	}
528
529
	/* extract the pid */
530
	NIH_MUST (nih_strncat (&str_pid, NULL,
531
			&status[0][regmatch[1].rm_so],
532
			regmatch[1].rm_eo - regmatch[1].rm_so));
533
534
	nih_free (status);
535
536
	pid = (pid_t)atol (str_pid);
537
538
	/* check it's running */
539
	ret = kill (pid, 0);
540
	if (! ret)
541
		ret = pid;
542
543
out:
544
	regfree (&regex);
545
	return ret;
546
}
547
548
const char *
549
get_upstart_binary (void)
550
{
551
	return UPSTART_BINARY;
552
}
553
554
const char *
555
get_initctl_binary (void)
556
{
557
	return INITCTL_BINARY;
558
}
559
560
/**
561
 * string_check:
562
 *
563
 * @a: first string,
564
 * @b: second string.
565
 *
566
 * Compare @a and @b either or both of which may be NULL.
567
 *
568
 * Returns 0 if strings are identical or both NULL, else 1.
569
 **/
570
int
571
string_check (const char *a, const char *b)
572
{
573
	if (!a && !b)
574
		return 0;
575
576
	if (!a || !b)
577
		return 1;
578
579
	if (strcmp (a, b))
580
		return 1;
581
582
	return 0;
583
}
1471.1.3 by James Hunt
* init/Makefile.am: Added test_main.
584
585
/**
586
 * strcmp_compar:
587
 *
588
 * @a: first string,
589
 * @b: second string.
590
 *
591
 * String comparison function suitable for passing to qsort(3).
592
 * See the qsort(3) man page for further details.
593
 **/
594
int
595
strcmp_compar (const void *a, const void *b)
596
{
597
	return strcmp(*(char * const *)a, *(char * const *)b);
598
}
599
600
/**
601
 * get_session_file:
602
 *
603
 * @xdg_runtime_dir: Directory to treat as XDG_RUNTIME_DIR,
604
 * @pid: pid of running Session Init instance.
605
 *
606
 * Determine full path to a Session Inits session file.
607
 *
608
 * Note: No check on the existence of the session file is performed.
609
 *
610
 * Returns: Newly-allocated string representing full path to Session
611
 *          Inits session file.
612
 **/
613
char *
614
get_session_file (const char *xdg_runtime_dir, pid_t pid)
615
{
616
	char *session_file;
617
	
618
	nih_assert (xdg_runtime_dir);
619
	nih_assert (pid);
620
621
	session_file = nih_sprintf (NULL, "%s/upstart/sessions/%d.session",
622
			xdg_runtime_dir, (int)pid);
623
624
	nih_assert (session_file);
625
626
	return session_file;
627
}