/[baselayout]/trunk/src/rc.c
Gentoo

Contents of /trunk/src/rc.c

Parent Directory Parent Directory | Revision Log Revision Log


Revision 2835 - (show annotations) (download) (as text)
Thu Aug 16 17:09:57 2007 UTC (10 years, 4 months ago) by uberlord
File MIME type: text/x-csrc
File size: 35940 byte(s)
Fully move esyslog into rc
1 /*
2 rc.c
3 rc - manager for init scripts which control the startup, shutdown
4 and the running of daemons on a Gentoo system.
5
6 Also a multicall binary for various commands that can be used in shell
7 scripts to query service state, mark service state and provide the
8 Gentoo einfo family of informational functions.
9
10 Copyright 2007 Gentoo Foundation
11 Released under the GPLv2
12 */
13
14 #define APPLET "rc"
15
16 #define SYSLOG_NAMES
17
18 #include <sys/types.h>
19 #include <sys/stat.h>
20 #include <sys/utsname.h>
21 #include <sys/wait.h>
22 #include <errno.h>
23 #include <ctype.h>
24 #include <getopt.h>
25 #include <libgen.h>
26 #include <limits.h>
27 #include <stdbool.h>
28 #include <stdio.h>
29 #include <stdlib.h>
30 #include <signal.h>
31 #include <string.h>
32 #include <syslog.h>
33 #include <termios.h>
34 #include <unistd.h>
35
36 #include "builtins.h"
37 #include "einfo.h"
38 #include "rc.h"
39 #include "rc-misc.h"
40 #include "rc-plugin.h"
41 #include "strlist.h"
42
43 #define INITSH RC_LIBDIR "/sh/init.sh"
44 #define INITEARLYSH RC_LIBDIR "/sh/init-early.sh"
45 #define HALTSH RC_INITDIR "/halt.sh"
46 #define SULOGIN "/sbin/sulogin"
47
48 #define INTERACTIVE RC_SVCDIR "/interactive"
49
50 #define DEVBOOT "/dev/.rcboot"
51
52 /* Cleanup anything in main */
53 #define CHAR_FREE(_item) if (_item) { \
54 free (_item); \
55 _item = NULL; \
56 }
57
58 extern char **environ;
59
60 static char *RUNLEVEL = NULL;
61 static char *PREVLEVEL = NULL;
62
63 static char *applet = NULL;
64 static char **env = NULL;
65 static char **newenv = NULL;
66 static char **coldplugged_services = NULL;
67 static char **stop_services = NULL;
68 static char **start_services = NULL;
69 static rc_depinfo_t *deptree = NULL;
70 static char **types = NULL;
71 static char *tmp = NULL;
72
73 struct termios *termios_orig = NULL;
74
75 typedef struct pidlist
76 {
77 pid_t pid;
78 struct pidlist *next;
79 } pidlist_t;
80 static pidlist_t *service_pids = NULL;
81
82 static void cleanup (void)
83 {
84 if (applet && strcmp (applet, "rc") == 0) {
85 pidlist_t *pl = service_pids;
86
87 rc_plugin_unload ();
88
89 if (! rc_in_plugin && termios_orig) {
90 tcsetattr (fileno (stdin), TCSANOW, termios_orig);
91 free (termios_orig);
92 }
93
94 while (pl) {
95 pidlist_t *p = pl->next;
96 free (pl);
97 pl = p;
98 }
99
100 rc_strlist_free (env);
101 rc_strlist_free (newenv);
102 rc_strlist_free (coldplugged_services);
103 rc_strlist_free (stop_services);
104 rc_strlist_free (start_services);
105 rc_free_deptree (deptree);
106 rc_strlist_free (types);
107
108 /* Clean runlevel start, stop markers */
109 if (! rc_in_plugin) {
110 if (rc_is_dir (RC_SVCDIR "softscripts.new"))
111 rc_rm_dir (RC_SVCDIR "softscripts.new", true);
112 if (rc_is_dir (RC_SVCDIR "softscripts.old"))
113 rc_rm_dir (RC_SVCDIR "softscripts.old", true);
114 }
115 }
116
117 free (applet);
118 }
119
120 static int syslog_decode (char *name, CODE *codetab)
121 {
122 CODE *c;
123
124 if (isdigit (*name))
125 return (atoi (name));
126
127 for (c = codetab; c->c_name; c++)
128 if (! strcasecmp (name, c->c_name))
129 return (c->c_val);
130
131 return (-1);
132 }
133
134 static int do_e (int argc, char **argv)
135 {
136 int retval = EXIT_SUCCESS;
137 int i;
138 int l = 0;
139 char *message = NULL;
140 char *p;
141 char *fmt = NULL;
142 int level = 0;
143
144 if (strcmp (applet, "eval_ecolors") == 0) {
145 printf ("GOOD='%s'\nWARN='%s'\nBAD='%s'\nHILITE='%s'\nBRACKET='%s'\nNORMAL='%s'\n",
146 ecolor (ecolor_good),
147 ecolor (ecolor_warn),
148 ecolor (ecolor_bad),
149 ecolor (ecolor_hilite),
150 ecolor (ecolor_bracket),
151 ecolor (ecolor_normal));
152 exit (EXIT_SUCCESS);
153 }
154
155 if (argc > 0) {
156
157 if (strcmp (applet, "eend") == 0 ||
158 strcmp (applet, "ewend") == 0 ||
159 strcmp (applet, "veend") == 0 ||
160 strcmp (applet, "vweend") == 0)
161 {
162 errno = 0;
163 retval = strtol (argv[0], NULL, 0);
164 if (errno != 0)
165 retval = EXIT_FAILURE;
166 else {
167 argc--;
168 argv++;
169 }
170 } else if (strcmp (applet, "esyslog") == 0 ||
171 strcmp (applet, "elog") == 0) {
172 char *dot = strchr (argv[0], '.');
173 if ((level = syslog_decode (dot + 1, prioritynames)) == -1)
174 eerrorx ("%s: invalid log level `%s'", applet, argv[0]);
175
176 if (argc < 3)
177 eerrorx ("%s: not enough arguments", applet);
178
179 unsetenv ("RC_ELOG");
180 setenv ("RC_ELOG", argv[1], 1);
181
182 argc -= 2;
183 argv += 2;
184 }
185 }
186
187 if (argc > 0) {
188 for (i = 0; i < argc; i++)
189 l += strlen (argv[i]) + 1;
190
191 message = rc_xmalloc (l);
192 p = message;
193
194 for (i = 0; i < argc; i++) {
195 if (i > 0)
196 *p++ = ' ';
197 memcpy (p, argv[i], strlen (argv[i]));
198 p += strlen (argv[i]);
199 }
200 *p = 0;
201 }
202
203 if (message)
204 fmt = rc_xstrdup ("%s");
205
206 if (strcmp (applet, "einfo") == 0)
207 einfo (fmt, message);
208 else if (strcmp (applet, "einfon") == 0)
209 einfon (fmt, message);
210 else if (strcmp (applet, "ewarn") == 0)
211 ewarn (fmt, message);
212 else if (strcmp (applet, "ewarnn") == 0)
213 ewarnn (fmt, message);
214 else if (strcmp (applet, "eerror") == 0) {
215 eerror (fmt, message);
216 retval = 1;
217 } else if (strcmp (applet, "eerrorn") == 0) {
218 eerrorn (fmt, message);
219 retval = 1;
220 } else if (strcmp (applet, "ebegin") == 0)
221 ebegin (fmt, message);
222 else if (strcmp (applet, "eend") == 0)
223 eend (retval, fmt, message);
224 else if (strcmp (applet, "ewend") == 0)
225 ewend (retval, fmt, message);
226 else if (strcmp (applet, "esyslog") == 0)
227 elog (level, fmt, message);
228 else if (strcmp (applet, "veinfo") == 0)
229 einfov (fmt, message);
230 else if (strcmp (applet, "veinfon") == 0)
231 einfovn (fmt, message);
232 else if (strcmp (applet, "vewarn") == 0)
233 ewarnv (fmt, message);
234 else if (strcmp (applet, "vewarnn") == 0)
235 ewarnvn (fmt, message);
236 else if (strcmp (applet, "vebegin") == 0)
237 ebeginv (fmt, message);
238 else if (strcmp (applet, "veend") == 0)
239 eendv (retval, fmt, message);
240 else if (strcmp (applet, "vewend") == 0)
241 ewendv (retval, fmt, message);
242 else if (strcmp (applet, "eindent") == 0)
243 eindent ();
244 else if (strcmp (applet, "eoutdent") == 0)
245 eoutdent ();
246 else if (strcmp (applet, "veindent") == 0)
247 eindentv ();
248 else if (strcmp (applet, "veoutdent") == 0)
249 eoutdentv ();
250 else {
251 eerror ("%s: unknown applet", applet);
252 retval = EXIT_FAILURE;
253 }
254
255 if (fmt)
256 free (fmt);
257 if (message)
258 free (message);
259 return (retval);
260 }
261
262 static int do_service (int argc, char **argv)
263 {
264 bool ok = false;
265
266 if (argc < 1 || ! argv[0] || strlen (argv[0]) == 0)
267 eerrorx ("%s: no service specified", applet);
268
269 if (strcmp (applet, "service_started") == 0)
270 ok = rc_service_state (argv[0], rc_service_started);
271 else if (strcmp (applet, "service_stopped") == 0)
272 ok = rc_service_state (argv[0], rc_service_stopped);
273 else if (strcmp (applet, "service_inactive") == 0)
274 ok = rc_service_state (argv[0], rc_service_inactive);
275 else if (strcmp (applet, "service_starting") == 0)
276 ok = rc_service_state (argv[0], rc_service_starting);
277 else if (strcmp (applet, "service_stopping") == 0)
278 ok = rc_service_state (argv[0], rc_service_stopping);
279 else if (strcmp (applet, "service_coldplugged") == 0)
280 ok = rc_service_state (argv[0], rc_service_coldplugged);
281 else if (strcmp (applet, "service_wasinactive") == 0)
282 ok = rc_service_state (argv[0], rc_service_wasinactive);
283 else if (strcmp (applet, "service_started_daemon") == 0) {
284 int idx = 0;
285 if (argc > 2)
286 sscanf (argv[2], "%d", &idx);
287 exit (rc_service_started_daemon (argv[0], argv[1], idx)
288 ? 0 : 1);
289 } else
290 eerrorx ("%s: unknown applet", applet);
291
292 return (ok ? EXIT_SUCCESS : EXIT_FAILURE);
293 }
294
295 static int do_mark_service (int argc, char **argv)
296 {
297 bool ok = false;
298 char *svcname = getenv ("SVCNAME");
299
300 if (argc < 1 || ! argv[0] || strlen (argv[0]) == 0)
301 eerrorx ("%s: no service specified", applet);
302
303 if (strcmp (applet, "mark_service_started") == 0)
304 ok = rc_mark_service (argv[0], rc_service_started);
305 else if (strcmp (applet, "mark_service_stopped") == 0)
306 ok = rc_mark_service (argv[0], rc_service_stopped);
307 else if (strcmp (applet, "mark_service_inactive") == 0)
308 ok = rc_mark_service (argv[0], rc_service_inactive);
309 else if (strcmp (applet, "mark_service_starting") == 0)
310 ok = rc_mark_service (argv[0], rc_service_starting);
311 else if (strcmp (applet, "mark_service_stopping") == 0)
312 ok = rc_mark_service (argv[0], rc_service_stopping);
313 else if (strcmp (applet, "mark_service_coldplugged") == 0)
314 ok = rc_mark_service (argv[0], rc_service_coldplugged);
315 else
316 eerrorx ("%s: unknown applet", applet);
317
318 /* If we're marking ourselves then we need to inform our parent runscript
319 process so they do not mark us based on our exit code */
320 if (ok && svcname && strcmp (svcname, argv[0]) == 0) {
321 char *runscript_pid = getenv ("RC_RUNSCRIPT_PID");
322 char *mtime;
323 pid_t pid = 0;
324 int l;
325
326 if (runscript_pid && sscanf (runscript_pid, "%d", &pid) == 1)
327 if (kill (pid, SIGHUP) != 0)
328 eerror ("%s: failed to signal parent %d: %s",
329 applet, pid, strerror (errno));
330
331 /* Remove the exclsive time test. This ensures that it's not
332 in control as well */
333 l = strlen (RC_SVCDIR "exclusive") +
334 strlen (svcname) +
335 strlen (runscript_pid) +
336 4;
337 mtime = rc_xmalloc (l);
338 snprintf (mtime, l, RC_SVCDIR "exclusive/%s.%s",
339 svcname, runscript_pid);
340 if (rc_exists (mtime) && unlink (mtime) != 0)
341 eerror ("%s: unlink: %s", applet, strerror (errno));
342 free (mtime);
343 }
344
345 return (ok ? EXIT_SUCCESS : EXIT_FAILURE);
346 }
347
348 static int do_options (int argc, char **argv)
349 {
350 bool ok = false;
351 char *service = getenv ("SVCNAME");
352
353 if (! service)
354 eerrorx ("%s: no service specified", applet);
355
356 if (argc < 1 || ! argv[0] || strlen (argv[0]) == 0)
357 eerrorx ("%s: no option specified", applet);
358
359 if (strcmp (applet, "get_options") == 0) {
360 char buffer[1024];
361 memset (buffer, 0, 1024);
362 ok = rc_get_service_option (service, argv[0], buffer);
363 if (ok)
364 printf ("%s", buffer);
365 } else if (strcmp (applet, "save_options") == 0)
366 ok = rc_set_service_option (service, argv[0], argv[1]);
367 else
368 eerrorx ("%s: unknown applet", applet);
369
370 return (ok ? EXIT_SUCCESS : EXIT_FAILURE);
371 }
372
373 #ifdef __linux__
374 static char *proc_getent (const char *ent)
375 {
376 FILE *fp;
377 char buffer[RC_LINEBUFFER];
378 char *p;
379 char *value = NULL;
380 int i;
381
382 if (! (fp = fopen ("/proc/cmdline", "r"))) {
383 eerror ("failed to open `/proc/cmdline': %s", strerror (errno));
384 return (NULL);
385 }
386
387 memset (buffer, 0, sizeof (buffer));
388 if (fgets (buffer, RC_LINEBUFFER, fp) &&
389 (p = strstr (buffer, ent)))
390 {
391 i = p - buffer;
392 if (i == '\0' || buffer[i - 1] == ' ') {
393 /* Trim the trailing carriage return if present */
394 i = strlen (buffer) - 1;
395 if (buffer[i] == '\n')
396 buffer[i] = 0;
397
398 p += strlen (ent);
399 if (*p == '=')
400 p++;
401 value = strdup (strsep (&p, " "));
402 }
403 } else
404 errno = ENOENT;
405 fclose (fp);
406
407 return (value);
408 }
409 #endif
410
411 static char read_key (bool block)
412 {
413 struct termios termios;
414 char c = 0;
415 int fd = fileno (stdin);
416
417 if (! isatty (fd))
418 return (false);
419
420 /* Now save our terminal settings. We need to restore them at exit as we
421 will be changing it for non-blocking reads for Interactive */
422 if (! termios_orig) {
423 termios_orig = rc_xmalloc (sizeof (struct termios));
424 tcgetattr (fd, termios_orig);
425 }
426
427 tcgetattr (fd, &termios);
428 termios.c_lflag &= ~(ICANON | ECHO);
429 if (block)
430 termios.c_cc[VMIN] = 1;
431 else {
432 termios.c_cc[VMIN] = 0;
433 termios.c_cc[VTIME] = 0;
434 }
435 tcsetattr (fd, TCSANOW, &termios);
436
437 read (fd, &c, 1);
438
439 tcsetattr (fd, TCSANOW, termios_orig);
440
441 return (c);
442 }
443
444 static bool want_interactive (void)
445 {
446 char c;
447
448 if (PREVLEVEL &&
449 strcmp (PREVLEVEL, "N") != 0 &&
450 strcmp (PREVLEVEL, "S") != 0 &&
451 strcmp (PREVLEVEL, "1") != 0)
452 return (false);
453
454 if (! rc_is_env ("RC_INTERACTIVE", "yes"))
455 return (false);
456
457 c = read_key (false);
458 return ((c == 'I' || c == 'i') ? true : false);
459 }
460
461 static void mark_interactive (void)
462 {
463 FILE *fp = fopen (INTERACTIVE, "w");
464 if (fp)
465 fclose (fp);
466 }
467
468 static void sulogin (bool cont)
469 {
470 #ifdef __linux__
471 char *e = getenv ("RC_SYS");
472
473 /* VPS systems cannot do an sulogin */
474 if (e && strcmp (e, "VPS") == 0) {
475 execl ("/sbin/halt", "/sbin/halt", "-f", (char *) NULL);
476 eerrorx ("%s: unable to exec `/sbin/halt': %s", applet, strerror (errno));
477 }
478 #endif
479
480 newenv = rc_filter_env ();
481
482 if (cont) {
483 int status = 0;
484 #ifdef __linux__
485 char *tty = ttyname (fileno (stdout));
486 #endif
487
488 pid_t pid = vfork ();
489
490 if (pid == -1)
491 eerrorx ("%s: vfork: %s", applet, strerror (errno));
492 if (pid == 0) {
493 #ifdef __linux__
494 if (tty)
495 execle (SULOGIN, SULOGIN, tty, (char *) NULL, newenv);
496 else
497 execle (SULOGIN, SULOGIN, (char *) NULL, newenv);
498
499 eerror ("%s: unable to exec `%s': %s", applet, SULOGIN,
500 strerror (errno));
501 #else
502 execle ("/bin/sh", "/bin/sh", (char *) NULL, newenv);
503 eerror ("%s: unable to exec `/bin/sh': %s", applet,
504 strerror (errno));
505 #endif
506 _exit (EXIT_FAILURE);
507 }
508 waitpid (pid, &status, 0);
509 } else {
510 #ifdef __linux
511 execle ("/sbin/sulogin", "/sbin/sulogin", (char *) NULL, newenv);
512 eerrorx ("%s: unable to exec `/sbin/sulogin': %s", applet, strerror (errno));
513 #else
514 exit (EXIT_SUCCESS);
515 #endif
516 }
517 }
518
519 static void single_user (void)
520 {
521 #ifdef __linux__
522 execl ("/sbin/telinit", "/sbin/telinit", "S", (char *) NULL);
523 eerrorx ("%s: unable to exec `/sbin/telinit': %s",
524 applet, strerror (errno));
525 #else
526 if (kill (1, SIGTERM) != 0)
527 eerrorx ("%s: unable to send SIGTERM to init (pid 1): %s",
528 applet, strerror (errno));
529 exit (EXIT_SUCCESS);
530 #endif
531 }
532
533 static void set_ksoftlevel (const char *runlevel)
534 {
535 FILE *fp;
536
537 if (! runlevel ||
538 strcmp (runlevel, getenv ("RC_BOOTLEVEL")) == 0 ||
539 strcmp (runlevel, RC_LEVEL_SINGLE) == 0 ||
540 strcmp (runlevel, RC_LEVEL_SYSINIT) == 0)
541 {
542 if (rc_exists (RC_SVCDIR "ksoftlevel") &&
543 unlink (RC_SVCDIR "ksoftlevel") != 0)
544 eerror ("unlink `%s': %s", RC_SVCDIR "ksoftlevel", strerror (errno));
545 return;
546 }
547
548 if (! (fp = fopen (RC_SVCDIR "ksoftlevel", "w"))) {
549 eerror ("fopen `%s': %s", RC_SVCDIR "ksoftlevel", strerror (errno));
550 return;
551 }
552
553 fprintf (fp, "%s", runlevel);
554 fclose (fp);
555 }
556
557 static int get_ksoftlevel (char *buffer, int buffer_len)
558 {
559 FILE *fp;
560 int i = 0;
561
562 if (! rc_exists (RC_SVCDIR "ksoftlevel"))
563 return 0;
564
565 if (! (fp = fopen (RC_SVCDIR "ksoftlevel", "r"))) {
566 eerror ("fopen `%s': %s", RC_SVCDIR "ksoftlevel",
567 strerror (errno));
568 return -1;
569 }
570
571 if (fgets (buffer, buffer_len, fp)) {
572 i = strlen (buffer) - 1;
573 if (buffer[i] == '\n')
574 buffer[i] = 0;
575 }
576
577 fclose (fp);
578 return i;
579 }
580
581 static void wait_for_services ()
582 {
583 int status = 0;
584 while (wait (&status) != -1);
585 }
586
587 static void add_pid (pid_t pid)
588 {
589 pidlist_t *sp = service_pids;
590 if (sp) {
591 while (sp->next)
592 sp = sp->next;
593 sp->next = rc_xmalloc (sizeof (pidlist_t));
594 sp = sp->next;
595 } else
596 sp = service_pids = rc_xmalloc (sizeof (pidlist_t));
597 memset (sp, 0, sizeof (pidlist_t));
598 sp->pid = pid;
599 }
600
601 static void remove_pid (pid_t pid)
602 {
603 pidlist_t *last = NULL;
604 pidlist_t *pl;
605
606 for (pl = service_pids; pl; pl = pl->next) {
607 if (pl->pid == pid) {
608 if (last)
609 last->next = pl->next;
610 else
611 service_pids = pl->next;
612 free (pl);
613 break;
614 }
615 last = pl;
616 }
617 }
618
619 static void handle_signal (int sig)
620 {
621 int serrno = errno;
622 char signame[10] = { '\0' };
623 pidlist_t *pl;
624 pid_t pid;
625 int status = 0;
626
627 switch (sig) {
628 case SIGCHLD:
629 do {
630 pid = waitpid (-1, &status, WNOHANG);
631 if (pid < 0) {
632 if (errno != ECHILD)
633 eerror ("waitpid: %s", strerror (errno));
634 return;
635 }
636 } while (! WIFEXITED (status) && ! WIFSIGNALED (status));
637
638 /* Remove that pid from our list */
639 if (pid > 0)
640 remove_pid (pid);
641 break;
642
643 case SIGINT:
644 if (! signame[0])
645 snprintf (signame, sizeof (signame), "SIGINT");
646 case SIGTERM:
647 if (! signame[0])
648 snprintf (signame, sizeof (signame), "SIGTERM");
649 case SIGQUIT:
650 if (! signame[0])
651 snprintf (signame, sizeof (signame), "SIGQUIT");
652 eerrorx ("%s: caught %s, aborting", applet, signame);
653 case SIGUSR1:
654 eerror ("rc: Aborting!");
655 /* Kill any running services we have started */
656
657 signal (SIGCHLD, SIG_IGN);
658 for (pl = service_pids; pl; pl = pl->next)
659 kill (pl->pid, SIGTERM);
660
661 /* Notify plugins we are aborting */
662 rc_plugin_run (rc_hook_abort, NULL);
663
664 /* Only drop into single user mode if we're booting */
665 if ((PREVLEVEL &&
666 (strcmp (PREVLEVEL, "S") == 0 ||
667 strcmp (PREVLEVEL, "1") == 0)) ||
668 (RUNLEVEL &&
669 (strcmp (RUNLEVEL, "S") == 0 ||
670 strcmp (RUNLEVEL, "1") == 0)))
671 single_user ();
672
673 exit (EXIT_FAILURE);
674 break;
675
676 default:
677 eerror ("%s: caught unknown signal %d", applet, sig);
678 }
679
680 /* Restore errno */
681 errno = serrno;
682 }
683
684 static void run_script (const char *script) {
685 int status = 0;
686 pid_t pid = vfork ();
687
688 if (pid < 0)
689 eerrorx ("%s: vfork: %s", applet, strerror (errno));
690 else if (pid == 0) {
691 execl (script, script, (char *) NULL);
692 eerror ("%s: unable to exec `%s': %s",
693 script, applet, strerror (errno));
694 _exit (EXIT_FAILURE);
695 }
696
697 do {
698 pid_t wpid = waitpid (pid, &status, 0);
699 if (wpid < 1)
700 eerror ("waitpid: %s", strerror (errno));
701 } while (! WIFEXITED (status) && ! WIFSIGNALED (status));
702
703 if (! WIFEXITED (status) || ! WEXITSTATUS (status) == 0)
704 eerrorx ("%s: failed to exec `%s'", applet, script);
705 }
706
707 #include "_usage.h"
708 #define getoptstring getoptstring_COMMON
709 static struct option longopts[] = {
710 longopts_COMMON
711 { NULL, 0, NULL, 0}
712 };
713 #include "_usage.c"
714
715 int main (int argc, char **argv)
716 {
717 char *runlevel = NULL;
718 const char *bootlevel = NULL;
719 char *newlevel = NULL;
720 char *service = NULL;
721 char **deporder = NULL;
722 int i = 0;
723 int j = 0;
724 bool going_down = false;
725 bool interactive = false;
726 int depoptions = RC_DEP_STRICT | RC_DEP_TRACE;
727 char ksoftbuffer [PATH_MAX];
728 char pidstr[6];
729 int opt;
730
731 atexit (cleanup);
732 if (argv[0])
733 applet = rc_xstrdup (basename (argv[0]));
734
735 if (! applet)
736 eerrorx ("arguments required");
737
738 /* These used to be programs in their own right, so we shouldn't
739 * touch argc or argv for them */
740 if (strcmp (applet, "env-update") == 0)
741 exit (env_update (argc, argv));
742 else if (strcmp (applet, "fstabinfo") == 0)
743 exit (fstabinfo (argc, argv));
744 else if (strcmp (applet, "mountinfo") == 0)
745 exit (mountinfo (argc, argv));
746 else if (strcmp (applet, "rc-depend") == 0)
747 exit (rc_depend (argc, argv));
748 else if (strcmp (applet, "rc-status") == 0)
749 exit (rc_status (argc, argv));
750 else if (strcmp (applet, "rc-update") == 0 ||
751 strcmp (applet, "update-rc") == 0)
752 exit (rc_update (argc, argv));
753 else if (strcmp (applet, "runscript") == 0)
754 exit (runscript (argc, argv));
755 else if (strcmp (applet, "start-stop-daemon") == 0)
756 exit (start_stop_daemon (argc, argv));
757
758 argc--;
759 argv++;
760
761 /* Handle multicall stuff */
762 if (applet[0] == 'e' || (applet[0] == 'v' && applet[1] == 'e'))
763 exit (do_e (argc, argv));
764
765 if (strncmp (applet, "service_", strlen ("service_")) == 0)
766 exit (do_service (argc, argv));
767
768 if (strcmp (applet, "get_options") == 0 ||
769 strcmp (applet, "save_options") == 0)
770 exit (do_options (argc, argv));
771
772 if (strncmp (applet, "mark_service_", strlen ("mark_service_")) == 0)
773 exit (do_mark_service (argc, argv));
774
775 if (strcmp (applet, "is_runlevel_start") == 0)
776 exit (rc_runlevel_starting () ? 0 : 1);
777 else if (strcmp (applet, "is_runlevel_stop") == 0)
778 exit (rc_runlevel_stopping () ? 0 : 1);
779
780 if (strcmp (applet, "rc-abort") == 0) {
781 char *p = getenv ("RC_PID");
782 pid_t pid = 0;
783
784 if (p && sscanf (p, "%d", &pid) == 1) {
785 if (kill (pid, SIGUSR1) != 0)
786 eerrorx ("rc-abort: failed to signal parent %d: %s",
787 pid, strerror (errno));
788 exit (EXIT_SUCCESS);
789 }
790 exit (EXIT_FAILURE);
791 }
792
793 if (strcmp (applet, "rc" ) != 0)
794 eerrorx ("%s: unknown applet", applet);
795
796 /* Change dir to / to ensure all scripts don't use stuff in pwd */
797 chdir ("/");
798
799 /* RUNLEVEL is set by sysvinit as is a magic number
800 RC_SOFTLEVEL is set by us and is the name for this magic number
801 even though all our userland documentation refers to runlevel */
802 RUNLEVEL = getenv ("RUNLEVEL");
803 PREVLEVEL = getenv ("PREVLEVEL");
804
805 /* Setup a signal handler */
806 signal (SIGINT, handle_signal);
807 signal (SIGQUIT, handle_signal);
808 signal (SIGTERM, handle_signal);
809 signal (SIGUSR1, handle_signal);
810
811 /* Ensure our environment is pure
812 Also, add our configuration to it */
813 env = rc_filter_env ();
814 env = rc_config_env (env);
815
816 if (env) {
817 char *p;
818
819 #ifdef __linux__
820 /* clearenv isn't portable, but there's no harm in using it
821 if we have it */
822 clearenv ();
823 #else
824 char *var;
825 /* No clearenv present here then.
826 We could manipulate environ directly ourselves, but it seems that
827 some kernels bitch about this according to the environ man pages
828 so we walk though environ and call unsetenv for each value. */
829 while (environ[0]) {
830 tmp = rc_xstrdup (environ[0]);
831 p = tmp;
832 var = strsep (&p, "=");
833 unsetenv (var);
834 free (tmp);
835 }
836 tmp = NULL;
837 #endif
838
839 STRLIST_FOREACH (env, p, i)
840 if (strcmp (p, "RC_SOFTLEVEL") != 0 && strcmp (p, "SOFTLEVEL") != 0)
841 putenv (p);
842
843 /* We don't free our list as that would be null in environ */
844 }
845
846 argc++;
847 argv--;
848 while ((opt = getopt_long (argc, argv, getoptstring,
849 longopts, (int *) 0)) != -1)
850 {
851 switch (opt) {
852 case_RC_COMMON_GETOPT
853 }
854 }
855
856 newlevel = argv[optind++];
857
858 /* OK, so we really are the main RC process
859 Only root should be able to run us */
860 if (geteuid () != 0)
861 eerrorx ("%s: root access required", applet);
862
863 /* Enable logging */
864 setenv ("RC_ELOG", "rc", 1);
865
866 /* Export our PID */
867 snprintf (pidstr, sizeof (pidstr), "%d", getpid ());
868 setenv ("RC_PID", pidstr, 1);
869
870 interactive = rc_exists (INTERACTIVE);
871 rc_plugin_load ();
872
873 /* Load current softlevel */
874 bootlevel = getenv ("RC_BOOTLEVEL");
875 runlevel = rc_get_runlevel ();
876
877 /* Check we're in the runlevel requested, ie from
878 rc single
879 rc shutdown
880 rc reboot
881 */
882 if (newlevel) {
883 if (strcmp (newlevel, RC_LEVEL_SYSINIT) == 0 &&
884 RUNLEVEL &&
885 (strcmp (RUNLEVEL, "S") == 0 ||
886 strcmp (RUNLEVEL, "1") == 0))
887 {
888 /* OK, we're either in runlevel 1 or single user mode */
889 struct utsname uts;
890 #ifdef __linux__
891 char *cmd;
892 #endif
893
894 /* exec init-early.sh if it exists
895 * This should just setup the console to use the correct
896 * font. Maybe it should setup the keyboard too? */
897 if (rc_exists (INITEARLYSH))
898 run_script (INITEARLYSH);
899
900 uname (&uts);
901
902 printf ("\n");
903 printf (" %sGentoo/%s; %shttp://www.gentoo.org/%s"
904 "\n Copyright 1999-2007 Gentoo Foundation; "
905 "Distributed under the GPLv2\n\n",
906 ecolor (ecolor_good), uts.sysname, ecolor (ecolor_bracket),
907 ecolor (ecolor_normal));
908
909 if (rc_is_env ("RC_INTERACTIVE", "yes"))
910 printf ("Press %sI%s to enter interactive boot mode\n\n",
911 ecolor (ecolor_good), ecolor (ecolor_normal));
912
913 setenv ("RC_SOFTLEVEL", newlevel, 1);
914 rc_plugin_run (rc_hook_runlevel_start_in, newlevel);
915 run_script (INITSH);
916
917 #ifdef __linux__
918 /* If we requested a softlevel, save it now */
919 set_ksoftlevel (NULL);
920 if ((cmd = proc_getent ("softlevel"))) {
921 set_ksoftlevel (cmd);
922 free (cmd);
923 }
924
925 #endif
926 rc_plugin_run (rc_hook_runlevel_start_out, newlevel);
927
928 if (want_interactive ())
929 mark_interactive ();
930
931 exit (EXIT_SUCCESS);
932 } else if (strcmp (newlevel, RC_LEVEL_SINGLE) == 0) {
933 if (! RUNLEVEL ||
934 (strcmp (RUNLEVEL, "S") != 0 &&
935 strcmp (RUNLEVEL, "1") != 0))
936 {
937 /* Remember the current runlevel for when we come back */
938 set_ksoftlevel (runlevel);
939 single_user ();
940 }
941 } else if (strcmp (newlevel, RC_LEVEL_REBOOT) == 0) {
942 if (! RUNLEVEL ||
943 strcmp (RUNLEVEL, "6") != 0)
944 {
945 execl ("/sbin/shutdown", "/sbin/shutdown", "-r", "now", (char *) NULL);
946 eerrorx ("%s: unable to exec `/sbin/shutdown': %s",
947 applet, strerror (errno));
948 }
949 } else if (strcmp (newlevel, RC_LEVEL_SHUTDOWN) == 0) {
950 if (! RUNLEVEL ||
951 strcmp (RUNLEVEL, "0") != 0)
952 {
953 execl ("/sbin/shutdown", "/sbin/shutdown",
954 #ifdef __linux
955 "-h",
956 #else
957 "-p",
958 #endif
959 "now", (char *) NULL);
960 eerrorx ("%s: unable to exec `/sbin/shutdown': %s",
961 applet, strerror (errno));
962 }
963 }
964 }
965
966 /* Now we start handling our children */
967 signal (SIGCHLD, handle_signal);
968
969 /* We should only use ksoftlevel if we were in single user mode
970 If not, we need to erase ksoftlevel now. */
971 if (PREVLEVEL &&
972 (strcmp (PREVLEVEL, "1") == 0 ||
973 strcmp (PREVLEVEL, "S") == 0 ||
974 strcmp (PREVLEVEL, "N") == 0))
975 {
976 if (get_ksoftlevel (ksoftbuffer, sizeof (ksoftbuffer)))
977 newlevel = ksoftbuffer;
978 } else if (! RUNLEVEL ||
979 (strcmp (RUNLEVEL, "1") != 0 &&
980 strcmp (RUNLEVEL, "S") != 0 &&
981 strcmp (RUNLEVEL, "N") != 0))
982 {
983 set_ksoftlevel (NULL);
984 }
985
986 if (newlevel &&
987 (strcmp (newlevel, RC_LEVEL_REBOOT) == 0 ||
988 strcmp (newlevel, RC_LEVEL_SHUTDOWN) == 0 ||
989 strcmp (newlevel, RC_LEVEL_SINGLE) == 0))
990 {
991 going_down = true;
992 rc_set_runlevel (newlevel);
993 setenv ("RC_SOFTLEVEL", newlevel, 1);
994 rc_plugin_run (rc_hook_runlevel_stop_in, newlevel);
995 } else {
996 rc_plugin_run (rc_hook_runlevel_stop_in, runlevel);
997 }
998
999 /* Check if runlevel is valid if we're changing */
1000 if (newlevel && strcmp (runlevel, newlevel) != 0 && ! going_down) {
1001 tmp = rc_strcatpaths (RC_RUNLEVELDIR, newlevel, (char *) NULL);
1002 if (! rc_is_dir (tmp))
1003 eerrorx ("%s: is not a valid runlevel", newlevel);
1004 CHAR_FREE (tmp);
1005 }
1006
1007 /* Load our deptree now */
1008 if ((deptree = rc_load_deptree ()) == NULL)
1009 eerrorx ("failed to load deptree");
1010
1011 /* Clean the failed services state dir now */
1012 if (rc_is_dir (RC_SVCDIR "failed"))
1013 rc_rm_dir (RC_SVCDIR "failed", false);
1014
1015 mkdir (RC_SVCDIR "/softscripts.new", 0755);
1016
1017 #ifdef __linux__
1018 /* udev likes to start services before we're ready when it does
1019 its coldplugging thing. runscript knows when we're not ready so it
1020 stores a list of coldplugged services in DEVBOOT for us to pick up
1021 here when we are ready for them */
1022 if (rc_is_dir (DEVBOOT)) {
1023 start_services = rc_ls_dir (NULL, DEVBOOT, RC_LS_INITD);
1024 rc_rm_dir (DEVBOOT, true);
1025
1026 STRLIST_FOREACH (start_services, service, i)
1027 if (rc_allow_plug (service))
1028 rc_mark_service (service, rc_service_coldplugged);
1029 /* We need to dump this list now.
1030 This may seem redunant, but only Linux needs this and saves on
1031 code bloat. */
1032 rc_strlist_free (start_services);
1033 start_services = NULL;
1034 }
1035 #else
1036 /* BSD's on the other hand populate /dev automagically and use devd.
1037 The only downside of this approach and ours is that we have to hard code
1038 the device node to the init script to simulate the coldplug into
1039 runlevel for our dependency tree to work. */
1040 if (newlevel && strcmp (newlevel, bootlevel) == 0 &&
1041 (strcmp (runlevel, RC_LEVEL_SINGLE) == 0 ||
1042 strcmp (runlevel, RC_LEVEL_SYSINIT) == 0) &&
1043 rc_is_env ("RC_COLDPLUG", "yes"))
1044 {
1045 #if defined(__DragonFly__) || defined(__FreeBSD__)
1046 /* The net interfaces are easy - they're all in net /dev/net :) */
1047 start_services = rc_ls_dir (NULL, "/dev/net", 0);
1048 STRLIST_FOREACH (start_services, service, i) {
1049 j = (strlen ("net.") + strlen (service) + 1);
1050 tmp = rc_xmalloc (sizeof (char *) * j);
1051 snprintf (tmp, j, "net.%s", service);
1052 if (rc_service_exists (tmp) && rc_allow_plug (tmp))
1053 rc_mark_service (tmp, rc_service_coldplugged);
1054 CHAR_FREE (tmp);
1055 }
1056 rc_strlist_free (start_services);
1057 #endif
1058
1059 /* The mice are a little more tricky.
1060 If we coldplug anything else, we'll probably do it here. */
1061 start_services = rc_ls_dir (NULL, "/dev", 0);
1062 STRLIST_FOREACH (start_services, service, i) {
1063 if (strncmp (service, "psm", 3) == 0 ||
1064 strncmp (service, "ums", 3) == 0)
1065 {
1066 char *p = service + 3;
1067 if (p && isdigit (*p)) {
1068 j = (strlen ("moused.") + strlen (service) + 1);
1069 tmp = rc_xmalloc (sizeof (char *) * j);
1070 snprintf (tmp, j, "moused.%s", service);
1071 if (rc_service_exists (tmp) && rc_allow_plug (tmp))
1072 rc_mark_service (tmp, rc_service_coldplugged);
1073 CHAR_FREE (tmp);
1074 }
1075 }
1076 }
1077 rc_strlist_free (start_services);
1078 start_services = NULL;
1079 }
1080 #endif
1081
1082 /* Build a list of all services to stop and then work out the
1083 correct order for stopping them */
1084 stop_services = rc_ls_dir (stop_services, RC_SVCDIR_STARTING, RC_LS_INITD);
1085 stop_services = rc_ls_dir (stop_services, RC_SVCDIR_INACTIVE, RC_LS_INITD);
1086 stop_services = rc_ls_dir (stop_services, RC_SVCDIR_STARTED, RC_LS_INITD);
1087
1088 types = rc_strlist_add (NULL, "ineed");
1089 types = rc_strlist_add (types, "iuse");
1090 types = rc_strlist_add (types, "iafter");
1091 deporder = rc_get_depends (deptree, types, stop_services,
1092 runlevel, depoptions | RC_DEP_STOP);
1093 rc_strlist_free (stop_services);
1094 rc_strlist_free (types);
1095 stop_services = deporder;
1096 deporder = NULL;
1097 types = NULL;
1098 rc_strlist_reverse (stop_services);
1099
1100 /* Load our list of coldplugged services */
1101 coldplugged_services = rc_ls_dir (coldplugged_services,
1102 RC_SVCDIR_COLDPLUGGED, RC_LS_INITD);
1103
1104 /* Load our start services now.
1105 We have different rules dependent on runlevel. */
1106 if (newlevel && strcmp (newlevel, bootlevel) == 0) {
1107 if (coldplugged_services) {
1108 einfon ("Device initiated services:");
1109 STRLIST_FOREACH (coldplugged_services, service, i) {
1110 printf (" %s", service);
1111 start_services = rc_strlist_add (start_services, service);
1112 }
1113 printf ("\n");
1114 }
1115 tmp = rc_strcatpaths (RC_RUNLEVELDIR, newlevel ? newlevel : runlevel,
1116 (char *) NULL);
1117 start_services = rc_ls_dir (start_services, tmp, RC_LS_INITD);
1118 CHAR_FREE (tmp);
1119 } else {
1120 /* Store our list of coldplugged services */
1121 coldplugged_services = rc_ls_dir (coldplugged_services, RC_SVCDIR_COLDPLUGGED,
1122 RC_LS_INITD);
1123 if (strcmp (newlevel ? newlevel : runlevel, RC_LEVEL_SINGLE) != 0 &&
1124 strcmp (newlevel ? newlevel : runlevel, RC_LEVEL_SHUTDOWN) != 0 &&
1125 strcmp (newlevel ? newlevel : runlevel, RC_LEVEL_REBOOT) != 0)
1126 {
1127 /* We need to include the boot runlevel services if we're not in it */
1128 char **services = rc_services_in_runlevel (bootlevel);
1129
1130 start_services = rc_strlist_join (start_services, services);
1131 services = rc_services_in_runlevel (newlevel ? newlevel : runlevel);
1132 start_services = rc_strlist_join (start_services, services);
1133 services = NULL;
1134
1135 STRLIST_FOREACH (coldplugged_services, service, i)
1136 start_services = rc_strlist_add (start_services, service);
1137
1138 }
1139 }
1140
1141 /* Save out softlevel now */
1142 if (going_down)
1143 rc_set_runlevel (newlevel);
1144
1145 types = rc_strlist_add (NULL, "needsme");
1146 types = rc_strlist_add (types, "usesme");
1147 /* Now stop the services that shouldn't be running */
1148 STRLIST_FOREACH (stop_services, service, i) {
1149 bool found = false;
1150 char *conf = NULL;
1151 char **stopdeps = NULL;
1152 char *svc1 = NULL;
1153 char *svc2 = NULL;
1154 int k;
1155
1156 if (rc_service_state (service, rc_service_stopped))
1157 continue;
1158
1159 /* We always stop the service when in these runlevels */
1160 if (going_down) {
1161 pid_t pid = rc_stop_service (service);
1162 if (pid > 0 && ! rc_is_env ("RC_PARALLEL", "yes"))
1163 rc_waitpid (pid);
1164 continue;
1165 }
1166
1167 /* If we're in the start list then don't bother stopping us */
1168 STRLIST_FOREACH (start_services, svc1, j)
1169 if (strcmp (svc1, service) == 0) {
1170 found = true;
1171 break;
1172 }
1173
1174 /* Unless we would use a different config file */
1175 if (found) {
1176 int len;
1177 if (! newlevel)
1178 continue;
1179
1180 len = strlen (service) + strlen (runlevel) + 2;
1181 tmp = rc_xmalloc (sizeof (char *) * len);
1182 snprintf (tmp, len, "%s.%s", service, runlevel);
1183 conf = rc_strcatpaths (RC_CONFDIR, tmp, (char *) NULL);
1184 found = rc_exists (conf);
1185 CHAR_FREE (conf);
1186 CHAR_FREE (tmp);
1187 if (! found) {
1188 len = strlen (service) + strlen (newlevel) + 2;
1189 tmp = rc_xmalloc (sizeof (char *) * len);
1190 snprintf (tmp, len, "%s.%s", service, newlevel);
1191 conf = rc_strcatpaths (RC_CONFDIR, tmp, (char *) NULL);
1192 found = rc_exists (conf);
1193 CHAR_FREE (conf);
1194 CHAR_FREE (tmp);
1195 if (!found)
1196 continue;
1197 }
1198 } else {
1199 /* Allow coldplugged services not to be in the runlevels list */
1200 if (rc_service_state (service, rc_service_coldplugged))
1201 continue;
1202 }
1203
1204 /* We got this far! Or last check is to see if any any service that
1205 going to be started depends on us */
1206 stopdeps = rc_strlist_add (stopdeps, service);
1207 deporder = rc_get_depends (deptree, types, stopdeps,
1208 runlevel, RC_DEP_STRICT);
1209 rc_strlist_free (stopdeps);
1210 stopdeps = NULL;
1211 found = false;
1212 STRLIST_FOREACH (deporder, svc1, j) {
1213 STRLIST_FOREACH (start_services, svc2, k)
1214 if (strcmp (svc1, svc2) == 0) {
1215 found = true;
1216 break;
1217 }
1218 if (found)
1219 break;
1220 }
1221 rc_strlist_free (deporder);
1222 deporder = NULL;
1223
1224 /* After all that we can finally stop the blighter! */
1225 if (! found) {
1226 pid_t pid = rc_stop_service (service);
1227 if (pid > 0 && ! rc_is_env ("RC_PARALLEL", "yes"))
1228 rc_waitpid (pid);
1229 }
1230 }
1231 rc_strlist_free (types);
1232 types = NULL;
1233
1234 /* Wait for our services to finish */
1235 wait_for_services ();
1236
1237 /* Notify the plugins we have finished */
1238 rc_plugin_run (rc_hook_runlevel_stop_out, runlevel);
1239
1240 rmdir (RC_SVCDIR "/softscripts.new");
1241
1242 /* Store the new runlevel */
1243 if (newlevel) {
1244 rc_set_runlevel (newlevel);
1245 runlevel = newlevel;
1246 setenv ("RC_SOFTLEVEL", runlevel, 1);
1247 }
1248
1249 /* Run the halt script if needed */
1250 if (strcmp (runlevel, RC_LEVEL_SHUTDOWN) == 0 ||
1251 strcmp (runlevel, RC_LEVEL_REBOOT) == 0)
1252 {
1253 execl (HALTSH, HALTSH, runlevel, (char *) NULL);
1254 eerrorx ("%s: unable to exec `%s': %s",
1255 applet, HALTSH, strerror (errno));
1256 }
1257
1258 /* Single user is done now */
1259 if (strcmp (runlevel, RC_LEVEL_SINGLE) == 0) {
1260 if (rc_exists (INTERACTIVE))
1261 unlink (INTERACTIVE);
1262 sulogin (false);
1263 }
1264
1265 mkdir (RC_SVCDIR "softscripts.old", 0755);
1266 rc_plugin_run (rc_hook_runlevel_start_in, runlevel);
1267
1268 /* Re-add our coldplugged services if they stopped */
1269 STRLIST_FOREACH (coldplugged_services, service, i)
1270 rc_mark_service (service, rc_service_coldplugged);
1271
1272 /* Order the services to start */
1273 types = rc_strlist_add (NULL, "ineed");
1274 types = rc_strlist_add (types, "iuse");
1275 types = rc_strlist_add (types, "iafter");
1276 deporder = rc_get_depends (deptree, types, start_services,
1277 runlevel, depoptions | RC_DEP_START);
1278 rc_strlist_free (types);
1279 types = NULL;
1280 rc_strlist_free (start_services);
1281 start_services = deporder;
1282 deporder = NULL;
1283
1284 #ifdef __linux__
1285 /* mark any services skipped as started */
1286 if (PREVLEVEL && strcmp (PREVLEVEL, "N") == 0) {
1287 if ((service = proc_getent ("noinitd"))) {
1288 char *p = service;
1289 char *token;
1290
1291 while ((token = strsep (&p, ",")))
1292 rc_mark_service (token, rc_service_started);
1293 free (service);
1294 }
1295 }
1296 #endif
1297
1298
1299 STRLIST_FOREACH (start_services, service, i) {
1300 if (rc_service_state (service, rc_service_stopped)) {
1301 pid_t pid;
1302
1303 if (! interactive)
1304 interactive = want_interactive ();
1305
1306 if (interactive) {
1307 interactive_retry:
1308 printf ("\n");
1309 einfo ("About to start the service %s", service);
1310 eindent ();
1311 einfo ("1) Start the service\t\t2) Skip the service");
1312 einfo ("3) Continue boot process\t\t4) Exit to shell");
1313 eoutdent ();
1314 interactive_option:
1315 switch (read_key (true)) {
1316 case '1': break;
1317 case '2': continue;
1318 case '3': interactive = false; break;
1319 case '4': sulogin (true); goto interactive_retry;
1320 default: goto interactive_option;
1321 }
1322 }
1323
1324 /* Remember the pid if we're running in parallel */
1325 if ((pid = rc_start_service (service)))
1326 add_pid (pid);
1327
1328 if (! rc_is_env ("RC_PARALLEL", "yes")) {
1329 rc_waitpid (pid);
1330 remove_pid (pid);
1331 }
1332 }
1333 }
1334
1335 /* Wait for our services to finish */
1336 wait_for_services ();
1337
1338 rc_plugin_run (rc_hook_runlevel_start_out, runlevel);
1339
1340 #ifdef __linux__
1341 /* mark any services skipped as stopped */
1342 if (PREVLEVEL && strcmp (PREVLEVEL, "N") == 0) {
1343 if ((service = proc_getent ("noinitd"))) {
1344 char *p = service;
1345 char *token;
1346
1347 while ((token = strsep (&p, ",")))
1348 rc_mark_service (token, rc_service_stopped);
1349 free (service);
1350 }
1351 }
1352 #endif
1353
1354 /* Store our interactive status for boot */
1355 if (interactive && strcmp (runlevel, bootlevel) == 0)
1356 mark_interactive ();
1357 else {
1358 if (rc_exists (INTERACTIVE))
1359 unlink (INTERACTIVE);
1360 }
1361
1362 return (EXIT_SUCCESS);
1363 }
1364

  ViewVC Help
Powered by ViewVC 1.1.20