~vorlon/ubuntu/raring/upstart/lp.1199778

« back to all changes in this revision

Viewing changes to test/test_util_common.c

  • Committer: Steve Langasek
  • Date: 2013-11-07 03:06:51 UTC
  • Revision ID: steve.langasek@canonical.com-20131107030651-f1jzeyi7ifvvw1h8
Attempt to cherry-pick fixes for bug #1199778 to raring; something is still
missing though, the test suite segfaults on one of the json loads.

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
/* upstart
 
2
 *
 
3
 * test_util_common.c - common test utilities
 
4
 *
 
5
 * Copyright © 2012-2013 Canonical Ltd.
 
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>
 
30
#include <nih/logging.h>
 
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
 
 
50
#include "test_util_common.h"
 
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
 * @session_init_pid: pid of Session Init (which uses a private bus
 
67
 * rather than the session bus), else 0.
 
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 session_init_pid)
 
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 (session_init_pid) {
 
86
                TEST_TRUE (set_upstart_session (session_init_pid));
 
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
 * @session_init_pid: pid of Session Init.
 
131
 *
 
132
 * Attempt to "enter" an Upstart session by setting UPSTART_SESSION to
 
133
 * the value of the session running under pid @session_init_pid.
 
134
 *
 
135
 * Returns: TRUE if it was possible to enter the currently running
 
136
 * Upstart session, else FALSE.
 
137
 **/
 
138
int
 
139
set_upstart_session (pid_t session_init_pid)
 
140
{
 
141
        char                     *value;
 
142
        nih_local char           *cmd = NULL;
 
143
        nih_local char          **output = NULL;
 
144
        size_t                    lines = 0;
 
145
        int                       got = FALSE;
 
146
        int                       i;
 
147
        pid_t                     pid;
 
148
 
 
149
        /* XXX: arbitrary value */
 
150
        int                       loops = 5;
 
151
 
 
152
        nih_assert (session_init_pid);
 
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
 
 
169
        if (lines < 1)
 
170
            continue;
 
171
 
 
172
        /* Look for the specific session */
 
173
        for (size_t line = 0; line < lines; lines++) {
 
174
 
 
175
            /* No pid in output */
 
176
            if (! isdigit(output[line][0]))
 
177
                continue;
 
178
 
 
179
            pid = (pid_t)atoi(output[line]);
 
180
            nih_assert (pid > 0);
 
181
 
 
182
            if (pid != session_init_pid)
 
183
                continue;
 
184
 
 
185
            /* look for separator between pid and value of
 
186
             * UPSTART_SESSION.
 
187
             */
 
188
            value = strstr (output[0], " ");
 
189
            if (! value)
 
190
                continue;
 
191
 
 
192
            /* jump over space */
 
193
            value  += 1;
 
194
            if (! value)
 
195
                continue;
 
196
 
 
197
            /* No socket address */
 
198
            if (strstr (value, "unix:abstract") == value) {
 
199
                got = TRUE;
 
200
                goto out;
 
201
            }
 
202
        }
 
203
        }
 
204
 
 
205
out:
 
206
 
 
207
        if (got != TRUE)
 
208
                return FALSE;
 
209
 
 
210
        assert0 (setenv ("UPSTART_SESSION", value, 1));
 
211
 
 
212
        return TRUE;
 
213
}
 
214
 
 
215
/**
 
216
 * selfpipe:
 
217
 *
 
218
 * Used to allow a timed process wait.
 
219
 **/
 
220
static int selfpipe[2] = { -1, -1 };
 
221
 
 
222
static void
 
223
selfpipe_write (int n)
 
224
{
 
225
    assert (selfpipe[1] != -1);
 
226
 
 
227
    TEST_EQ (write (selfpipe[1], "", 1), 1);
 
228
}
 
229
 
 
230
/**
 
231
 * selfpipe_setup:
 
232
 *
 
233
 * Arrange for SIGCHLD to write to selfpipe such that we can select(2)
 
234
 * on child process status changes.
 
235
 **/
 
236
static void
 
237
selfpipe_setup (void)
 
238
{
 
239
    static struct sigaction  act;
 
240
    int                      read_flags;
 
241
    int                      write_flags;
 
242
 
 
243
    assert (selfpipe[0] == -1);
 
244
 
 
245
    assert (! pipe (selfpipe));
 
246
 
 
247
    /* Set non-blocking */
 
248
    read_flags = fcntl (selfpipe[0], F_GETFL);
 
249
    write_flags = fcntl (selfpipe[1], F_GETFL);
 
250
 
 
251
    read_flags |= O_NONBLOCK;
 
252
    write_flags |= O_NONBLOCK;
 
253
 
 
254
    assert (fcntl (selfpipe[0], F_SETFL, read_flags) == 0);
 
255
    assert (fcntl (selfpipe[1], F_SETFL, write_flags) == 0);
 
256
 
 
257
    /* Don't leak */
 
258
    assert (fcntl (selfpipe[0], F_SETFD, FD_CLOEXEC) == 0);
 
259
    assert (fcntl (selfpipe[1], F_SETFD, FD_CLOEXEC) == 0);
 
260
 
 
261
    memset (&act, 0, sizeof (act));
 
262
 
 
263
    /* register SIGCHLD handler which will cause pipe write when child
 
264
     * changes state.
 
265
     */
 
266
    act.sa_handler = selfpipe_write;
 
267
 
 
268
    sigaction (SIGCHLD, &act, NULL);
 
269
}
 
270
 
 
271
/**
 
272
 * timed_waitpid:
 
273
 *
 
274
 * @pid: pid to wait for,
 
275
 * @timeout: seconds to wait for @pid to change state.
 
276
 *
 
277
 * Simplified waitpid(2) with timeout using a pipe to allow select(2)
 
278
 * with timeout to be used to wait for process state change.
 
279
 **/
 
280
pid_t
 
281
timed_waitpid (pid_t pid, time_t timeout)
 
282
{
 
283
    static char     buffer[1];
 
284
    fd_set          read_fds;
 
285
    struct timeval  tv;
 
286
    int             status;
 
287
    int             nfds;
 
288
    int             ret;
 
289
    pid_t           ret2;
 
290
 
 
291
    assert (pid);
 
292
    assert (timeout);
 
293
 
 
294
    if (selfpipe[0] == -1)
 
295
            selfpipe_setup ();
 
296
 
 
297
    FD_ZERO (&read_fds);
 
298
    FD_SET (selfpipe[0], &read_fds);
 
299
 
 
300
    nfds = 1 + selfpipe[0];
 
301
 
 
302
    tv.tv_sec   = timeout;
 
303
    tv.tv_usec  = 0;
 
304
 
 
305
    /* wait for some activity */
 
306
    ret = select (nfds, &read_fds, NULL, NULL, &tv);
 
307
 
 
308
    if (! ret)
 
309
            /* timed out */
 
310
            return 0;
 
311
 
 
312
    /* discard any data written to pipe */
 
313
    while (read (selfpipe[0], buffer, sizeof (buffer)) > 0)
 
314
            ;
 
315
 
 
316
    while (TRUE) {
 
317
            /* wait for status change or error */
 
318
            ret2 = waitpid (pid, &status, WNOHANG);
 
319
 
 
320
            if (ret2 < 0)
 
321
                    return -1;
 
322
 
 
323
            if (! ret2)
 
324
                    /* give child a chance to change state */
 
325
                    sleep (1);
 
326
 
 
327
            if (ret2) {
 
328
                    if (WIFEXITED (status))
 
329
                            return ret2;
 
330
 
 
331
                    /* unexpected status change */
 
332
                    return -1;
 
333
            }
 
334
    }
 
335
}
 
336
 
 
337
 
 
338
/**
 
339
 * get_initctl():
 
340
 *
 
341
 * Determine a suitable initctl command-line for testing purposes.
 
342
 *
 
343
 * Returns: Static string representing full path to initctl binary with
 
344
 * default option to allow communication with an Upstart started using
 
345
 * START_UPSTART().
 
346
 **/
 
347
char *
 
348
get_initctl (void)
 
349
{
 
350
        static char path[PATH_MAX + 1024] = { 0 };
 
351
        int         ret;
 
352
 
 
353
        ret = sprintf (path, "%s %s",
 
354
                        INITCTL_BINARY,
 
355
                        test_user_mode
 
356
                        ? "--user"
 
357
                        : "--session");
 
358
 
 
359
        assert (ret > 0);
 
360
 
 
361
        return path;
 
362
}
 
363
 
 
364
/*
 
365
 * _start_upstart:
 
366
 *
 
367
 * @pid: PID of running instance,
 
368
 * @user: TRUE if upstart will run in User Session mode (FALSE to
 
369
 *  use the users D-Bus session bus),
 
370
 * @args: optional list of arguments to specify.
 
371
 *
 
372
 * Start an instance of Upstart.
 
373
 *
 
374
 * If the instance fails to start, abort(3) is called.
 
375
 **/
 
376
void
 
377
_start_upstart (pid_t *pid, int user, char * const *args)
 
378
{
 
379
        nih_local char  **argv = NULL;
 
380
        sigset_t          child_set, orig_set;
 
381
 
 
382
        assert (pid);
 
383
 
 
384
        argv = NIH_MUST (nih_str_array_new (NULL));
 
385
 
 
386
        NIH_MUST (nih_str_array_add (&argv, NULL, NULL,
 
387
                                UPSTART_BINARY));
 
388
 
 
389
        if (args)
 
390
                NIH_MUST (nih_str_array_append (&argv, NULL, NULL, args));
 
391
 
 
392
        sigfillset (&child_set);
 
393
        sigprocmask (SIG_BLOCK, &child_set, &orig_set);
 
394
 
 
395
        TEST_NE (*pid = fork (), -1);
 
396
 
 
397
        if (! *pid) {
 
398
                int fd;
 
399
                nih_signal_reset ();
 
400
                sigprocmask (SIG_SETMASK, &orig_set, NULL);
 
401
 
 
402
                if (! getenv ("UPSTART_TEST_VERBOSE")) {
 
403
                        fd = open ("/dev/null", O_RDWR);
 
404
                        assert (fd >= 0);
 
405
                        assert (dup2 (fd, STDIN_FILENO) != -1);
 
406
                        assert (dup2 (fd, STDOUT_FILENO) != -1);
 
407
                        assert (dup2 (fd, STDERR_FILENO) != -1);
 
408
                }
 
409
 
 
410
                assert (execv (argv[0], argv) != -1);
 
411
        }
 
412
 
 
413
        sigprocmask (SIG_SETMASK, &orig_set, NULL);
 
414
        wait_for_upstart (user ? *pid : 0);
 
415
}
 
416
 
 
417
/**
 
418
 * start_upstart_common:
 
419
 *
 
420
 * @pid: PID of running instance,
 
421
 * @user: TRUE if upstart should run in User Session mode (FALSE to
 
422
 * use the users D-Bus session bus),
 
423
 * @confdir: full path to configuration directory,
 
424
 * @logdir: full path to log directory,
 
425
 * @extra: optional extra arguments.
 
426
 *
 
427
 * Wrapper round _start_upstart() which specifies common options.
 
428
 **/
 
429
void
 
430
start_upstart_common (pid_t *pid, int user, const char *confdir,
 
431
                      const char *logdir, char * const *extra)
 
432
{
 
433
        nih_local char  **args = NULL;
 
434
 
 
435
        assert (pid);
 
436
 
 
437
        args = NIH_MUST (nih_str_array_new (NULL));
 
438
 
 
439
        if (user) {
 
440
                NIH_MUST (nih_str_array_add (&args, NULL, NULL,
 
441
                                        "--user"));
 
442
                test_user_mode = TRUE;
 
443
        } else {
 
444
                TEST_TRUE (getenv ("DBUS_SESSION_BUS_ADDRESS"));
 
445
                NIH_MUST (nih_str_array_add (&args, NULL, NULL,
 
446
                                        "--session"));
 
447
        }
 
448
 
 
449
        NIH_MUST (nih_str_array_add (&args, NULL, NULL,
 
450
                                "--no-startup-event"));
 
451
 
 
452
        NIH_MUST (nih_str_array_add (&args, NULL, NULL,
 
453
                                "--no-sessions"));
 
454
 
 
455
        NIH_MUST (nih_str_array_add (&args, NULL, NULL,
 
456
                                "--no-inherit-env"));
 
457
 
 
458
        if (confdir) {
 
459
                NIH_MUST (nih_str_array_add (&args, NULL, NULL,
 
460
                                        "--confdir"));
 
461
                NIH_MUST (nih_str_array_add (&args, NULL, NULL,
 
462
                                        confdir));
 
463
        }
 
464
 
 
465
        if (logdir) {
 
466
                NIH_MUST (nih_str_array_add (&args, NULL, NULL,
 
467
                                        "--logdir"));
 
468
                NIH_MUST (nih_str_array_add (&args, NULL, NULL,
 
469
                                        logdir));
 
470
        }
 
471
 
 
472
        if (extra)
 
473
                NIH_MUST (nih_str_array_append (&args, NULL, NULL, extra));
 
474
 
 
475
        _start_upstart (pid, user, args);
 
476
}
 
477
 
 
478
/**
 
479
 * start_upstart:
 
480
 *
 
481
 * @pid: PID of running instance.
 
482
 *
 
483
 * Wrapper round _start_upstart() which just runs an instance with no
 
484
 * options.
 
485
 **/
 
486
void
 
487
start_upstart (pid_t *pid)
 
488
{
 
489
        start_upstart_common (pid, FALSE, NULL, NULL, NULL);
 
490
}
 
491
 
 
492
/**
 
493
 * job_to_pid:
 
494
 *
 
495
 * @job: job name.
 
496
 *
 
497
 * Determine pid of running job.
 
498
 *
 
499
 * WARNING: it is the callers responsibility to ensure that
 
500
 * @job is still running when this function is called!!
 
501
 *
 
502
 * Returns: pid of job, or -1 if not found.
 
503
 **/
 
504
pid_t
 
505
job_to_pid (const char *job)
 
506
{
 
507
        pid_t            pid;
 
508
        regex_t          regex;
 
509
        regmatch_t       regmatch[2];
 
510
        int              ret;
 
511
        nih_local char  *cmd = NULL;
 
512
        nih_local char  *pattern = NULL;
 
513
        size_t           lines;
 
514
        char           **status;
 
515
        nih_local char  *str_pid = NULL;
 
516
 
 
517
        assert (job);
 
518
 
 
519
        pattern = NIH_MUST (nih_sprintf
 
520
                        (NULL, "^\\b%s\\b .*, process ([0-9]+)", job));
 
521
 
 
522
        cmd = NIH_MUST (nih_sprintf (NULL, "%s status %s 2>&1",
 
523
                        get_initctl (), job));
 
524
        RUN_COMMAND (NULL, cmd, &status, &lines);
 
525
        TEST_EQ (lines, 1);
 
526
 
 
527
        ret = regcomp (&regex, pattern, REG_EXTENDED);
 
528
        assert0 (ret);
 
529
 
 
530
        ret = regexec (&regex, status[0], 2, regmatch, 0);
 
531
        if (ret == REG_NOMATCH) {
 
532
                ret = -1;
 
533
                goto out;
 
534
        }
 
535
        assert0 (ret);
 
536
 
 
537
        if (regmatch[1].rm_so == -1 || regmatch[1].rm_eo == -1) {
 
538
                ret = -1;
 
539
                goto out;
 
540
        }
 
541
 
 
542
        /* extract the pid */
 
543
        NIH_MUST (nih_strncat (&str_pid, NULL,
 
544
                        &status[0][regmatch[1].rm_so],
 
545
                        regmatch[1].rm_eo - regmatch[1].rm_so));
 
546
 
 
547
        nih_free (status);
 
548
 
 
549
        pid = (pid_t)atol (str_pid);
 
550
 
 
551
        /* check it's running */
 
552
        ret = kill (pid, 0);
 
553
        if (! ret)
 
554
                ret = pid;
 
555
 
 
556
out:
 
557
        regfree (&regex);
 
558
        return ret;
 
559
}
 
560
 
 
561
const char *
 
562
get_upstart_binary (void)
 
563
{
 
564
        return UPSTART_BINARY;
 
565
}
 
566
 
 
567
const char *
 
568
get_initctl_binary (void)
 
569
{
 
570
        return INITCTL_BINARY;
 
571
}
 
572
 
 
573
/**
 
574
 * string_check:
 
575
 *
 
576
 * @a: first string,
 
577
 * @b: second string.
 
578
 *
 
579
 * Compare @a and @b either or both of which may be NULL.
 
580
 *
 
581
 * Returns 0 if strings are identical or both NULL, else 1.
 
582
 **/
 
583
int
 
584
string_check (const char *a, const char *b)
 
585
{
 
586
        if (!a && !b)
 
587
                return 0;
 
588
 
 
589
        if (!a || !b)
 
590
                return 1;
 
591
 
 
592
        if (strcmp (a, b))
 
593
                return 1;
 
594
 
 
595
        return 0;
 
596
}
 
597
 
 
598
/**
 
599
 * strcmp_compar:
 
600
 *
 
601
 * @a: first string,
 
602
 * @b: second string.
 
603
 *
 
604
 * String comparison function suitable for passing to qsort(3).
 
605
 * See the qsort(3) man page for further details.
 
606
 **/
 
607
int
 
608
strcmp_compar (const void *a, const void *b)
 
609
{
 
610
        return strcmp(*(char * const *)a, *(char * const *)b);
 
611
}
 
612
 
 
613
/**
 
614
 * get_session_file:
 
615
 *
 
616
 * @xdg_runtime_dir: Directory to treat as XDG_RUNTIME_DIR,
 
617
 * @pid: pid of running Session Init instance.
 
618
 *
 
619
 * Determine full path to a Session Inits session file.
 
620
 *
 
621
 * Note: No check on the existence of the session file is performed.
 
622
 *
 
623
 * Returns: Newly-allocated string representing full path to Session
 
624
 *          Inits session file.
 
625
 **/
 
626
char *
 
627
get_session_file (const char *xdg_runtime_dir, pid_t pid)
 
628
{
 
629
        char *session_file;
 
630
        
 
631
        nih_assert (xdg_runtime_dir);
 
632
        nih_assert (pid);
 
633
 
 
634
        session_file = nih_sprintf (NULL, "%s/upstart/sessions/%d.session",
 
635
                        xdg_runtime_dir, (int)pid);
 
636
 
 
637
        nih_assert (session_file);
 
638
 
 
639
        return session_file;
 
640
}
 
641
 
 
642
/**
 
643
 * in_chroot:
 
644
 *
 
645
 * Determine if running inside a chroot environment.
 
646
 *
 
647
 * Failures are fatal.
 
648
 *
 
649
 * Returns TRUE if within a chroot, else FALSE.
 
650
 **/
 
651
int
 
652
in_chroot (void)
 
653
{
 
654
        struct stat st;
 
655
        int i;
 
656
        char dir[] = "/";
 
657
 
 
658
        i = stat(dir, &st);
 
659
            
 
660
        if ( i != 0 ) { 
 
661
                fprintf (stderr, "ERROR: cannot stat '%s'\n", dir);
 
662
                exit (EXIT_FAILURE);
 
663
        }
 
664
 
 
665
        if ( st.st_ino == 2 )
 
666
                return FALSE;
 
667
 
 
668
        return TRUE;
 
669
}
 
670
 
 
671
/**
 
672
 * dbus_configured
 
673
 *
 
674
 * Determine if D-Bus has been configured (with dbus-uuidgen).
 
675
 *
 
676
 * Returns TRUE if D-Bus appears to have been configured,
 
677
 * else FALSE.
 
678
 **/
 
679
int
 
680
dbus_configured (void)
 
681
{
 
682
        struct stat st;
 
683
        char path[] = "/var/lib/dbus/machine-id";
 
684
 
 
685
        return !stat (path, &st);
 
686
}
 
687
 
 
688
/**
 
689
 * search_and_replace:
 
690
 *
 
691
 * @parent: parent for returned string,
 
692
 * @str: string to operate on,
 
693
 * @from: string to look for,
 
694
 * @to: string to replace @from with.
 
695
 *
 
696
 * Replace all occurences of @from in @str with @to.
 
697
 *
 
698
 * Returns: Newly-allocated string, or NULL on error or
 
699
 * if @str does not contain any occurences of @from.
 
700
 **/
 
701
char *
 
702
search_and_replace (void        *parent,
 
703
                    const char  *str,
 
704
                    const char  *from,
 
705
                    const char  *to)
 
706
{
 
707
        const char *start;
 
708
        const char *match;
 
709
        char       *new = NULL;
 
710
        size_t      len;
 
711
 
 
712
        nih_assert (str);
 
713
        nih_assert (from);
 
714
        nih_assert (to);
 
715
 
 
716
        start = str;
 
717
        len = strlen (from);
 
718
 
 
719
        while (start && *start) {
 
720
                match = strstr (start, from);
 
721
 
 
722
                if (! match) {
 
723
                        /* No more matches, so copy the remainder of the original string */
 
724
                        if (! nih_strcat (&new, parent, start))
 
725
                                return NULL;
 
726
                        break;
 
727
                }
 
728
 
 
729
                /* Copy data from start of segment to the match */
 
730
                if (! nih_strncat (&new, parent , start, match - start))
 
731
                        return NULL;
 
732
 
 
733
                /* Replace the string */
 
734
                if (! nih_strcat (&new, parent, to))
 
735
                        return NULL;
 
736
 
 
737
                /* Make start move to 1 byte beyond the end of the match */
 
738
                start = match + len;
 
739
        }
 
740
 
 
741
        return new;
 
742
}