source: rtems-libbsd/rtemsbsd/rtems/rtems-bsd-rc-conf-net.c @ 573b1982

55-freebsd-126-freebsd-12freebsd-9.3
Last change on this file since 573b1982 was 573b1982, checked in by Chris Johns <chrisj@…>, on 06/29/16 at 23:19:52

Add DHCP support to rc.conf.

  • Property mode set to 100644
File size: 17.9 KB
Line 
1/*
2 * Copyright (c) 2016 Chris Johns <chrisj@rtems.org>.  All rights reserved.
3 *
4 * Redistribution and use in source and binary forms, with or without
5 * modification, are permitted provided that the following conditions
6 * are met:
7 * 1. Redistributions of source code must retain the above copyright
8 *    notice, this list of conditions and the following disclaimer.
9 * 2. Redistributions in binary form must reproduce the above copyright
10 *    notice, this list of conditions and the following disclaimer in the
11 *    documentation and/or other materials provided with the distribution.
12 *
13 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
14 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
15 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
16 * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
17 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
18 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
19 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
20 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
21 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
22 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
23 * SUCH DAMAGE.
24 */
25
26/*
27 * Handle the networking directives found in rc.conf.
28 *  - ifconfig_*
29 *  - cloned_interfaces
30 *  - autobridge_interfaces
31 *  - autobridge_bridge*
32 *  - defaultrouter
33 */
34
35#include <rtems/bsd/sys/param.h>
36#include <sys/types.h>
37#include <sys/queue.h>
38#include <sys/kernel.h>
39#include <sysexits.h>
40
41#include <ifaddrs.h>
42#include <net/if.h>
43#include <net/route.h>
44#include <netinet/in.h>
45#include <sys/socket.h>
46
47#include <errno.h>
48#include <stddef.h>
49#include <stdio.h>
50#include <stdlib.h>
51#include <sys/stat.h>
52#include <unistd.h>
53
54#include <rtems.h>
55#include <rtems/chain.h>
56
57#include <machine/rtems-bsd-commands.h>
58#include <machine/rtems-bsd-rc-conf-services.h>
59
60#include <rtems/rtems-routes.h>
61
62/*
63 * Default defaultroute_delay is 30seconds.
64 */
65static int defaultroute_delay_secs = 30;
66
67/*
68 * Show a result.
69 */
70static void
71show_result(const char* label, int r)
72{
73  if (r < 0)
74    fprintf(stderr, "error: %s: %s\n", label, strerror(errno));
75}
76
77/*
78 * cloned_interfaces
79 *
80 * eg cloned_interfaces="vlan0 bridge0 tap1 tap2"
81 *
82 * See 'man rc.conf(5)' on FreeBSD.
83 */
84static int
85cloned_interfaces(rtems_bsd_rc_conf* rc_conf, rtems_bsd_rc_conf_argc_argv* aa)
86{
87  int arg;
88  int r = 0;
89
90  r = rtems_bsd_rc_conf_find(rc_conf, "cloned_interfaces", aa);
91  if (r < 0) {
92    if (errno == ENOENT)
93      r = 0;
94    return r;
95  }
96
97  for (arg = 1; arg < aa->argc; ++arg) {
98    const char* ifconfg_args[] = {
99      "ifconfig", aa->argv[arg], "create", NULL
100    };
101    rtems_bsd_rc_conf_print_cmd(rc_conf, "cloning_interfaces", 3, ifconfg_args);
102    rtems_bsd_command_ifconfig(3, (char**) ifconfg_args);
103  }
104
105  return 0;
106}
107
108/*
109 * create_args_'interface'
110 *
111 * eg create_args_myvlan="vlan 102"
112 *
113 * See 'man rc.conf(5)' on FreeBSD.
114 */
115typedef struct {
116  rtems_chain_node node;
117  const char*      label;
118  int              argc;
119  const char**     argv;
120} create_args_item;
121
122static RTEMS_CHAIN_DEFINE_EMPTY(create_args_items);
123
124static int
125load_create_args(rtems_bsd_rc_conf* rc_conf, rtems_bsd_rc_conf_argc_argv* aa)
126{
127  int r = 0;
128
129  r = rtems_bsd_rc_conf_find(rc_conf, "create_args_.*", aa);
130
131  while (r == 0) {
132    rtems_chain_node* node;
133    const char*       label;
134    create_args_item* item;
135    int               arg;
136
137    rtems_bsd_rc_conf_print_cmd(rc_conf, "create_args_", aa->argc, aa->argv);
138
139    label = aa->argv[0] + strlen("create_args_");
140
141    node = rtems_chain_first(&create_args_items);
142
143    while (!rtems_chain_is_tail(&create_args_items, node)) {
144      item = (create_args_item*) node;
145      if (strcasecmp(item->label, label) == 0) {
146        fprintf(stderr, "error: %s:%d: duplicate create args entry: %s\n",
147                rtems_bsd_rc_conf_name(rc_conf),
148                rtems_bsd_rc_conf_line(rc_conf),
149                aa->argv[0]);
150        errno = EEXIST;
151        return -1;
152      }
153      node = rtems_chain_next(node);
154    }
155
156    item = calloc(1, sizeof(*item));
157    if (item == NULL) {
158      errno = ENOMEM;
159      fprintf(stderr, "error: %s:%d: %s\n",
160              rtems_bsd_rc_conf_name(rc_conf),
161              rtems_bsd_rc_conf_line(rc_conf),
162              strerror(errno));
163      return -1;
164    }
165
166    item->argc = aa->argc;
167
168    item->label = strdup(label);
169    if (item->label == NULL) {
170      free(item);
171      errno = ENOMEM;
172      fprintf(stderr, "error: %s:%d: %s\n",
173              rtems_bsd_rc_conf_name(rc_conf),
174              rtems_bsd_rc_conf_line(rc_conf),
175              strerror(errno));
176      return -1;
177    }
178
179    item->argv = calloc(aa->argc + 1, sizeof(char*));
180    if (item->argv == NULL) {
181      free((void*) item->label);
182      free(item);
183      errno = ENOMEM;
184      fprintf(stderr, "error: %s:%d: %s\n",
185              rtems_bsd_rc_conf_name(rc_conf),
186              rtems_bsd_rc_conf_line(rc_conf),
187              strerror(errno));
188      return -1;
189    }
190
191    for (arg = 0; arg < aa->argc; ++arg) {
192      item->argv[arg] = strdup(aa->argv[0]);
193      if (item->argv[arg] == NULL) {
194        int a;
195        for (a = 0; a < arg; ++a)
196          free((void*) item->argv[a]);
197        free(item->argv);
198        free((void*) item->label);
199        free(item);
200        errno = ENOMEM;
201        fprintf(stderr, "error: %s:%d: %s\n",
202                rtems_bsd_rc_conf_name(rc_conf),
203                rtems_bsd_rc_conf_line(rc_conf),
204                strerror(errno));
205        return -1;
206      }
207    }
208
209    rtems_chain_append(&create_args_items, &item->node);
210
211    r = rtems_bsd_rc_conf_find_next(rc_conf, aa);
212  }
213
214  /*
215   * ignore not found.
216   */
217  if (r < 0 && errno == ENOENT)
218    r = 0;
219
220  return 0;
221}
222
223/*
224 * ifconfig_'interface'
225 *
226 * eg ifconfig_em0="inet 10.10.5.33 netmask 255.255.255.0"
227 *
228 * See 'man rc.conf(5)' on FreeBSD.
229 */
230static int
231ifconfig_(rtems_bsd_rc_conf* rc_conf,
232          const char*        ifname,
233          int                argc,
234          const char**       argv)
235{
236  const char**      args;
237  int               arg;
238  int               r;
239  const char const* ifconfig_show[] = { "ifconfig", ifname, NULL };
240
241  for (arg = 1; arg < argc; ++arg) {
242    if (strcasecmp(argv[arg], "NOAUTO") == 0)
243      return 0;
244  }
245
246  args = calloc(argc + 3, sizeof(char*));
247  if (args == NULL) {
248    errno = ENOMEM;
249    return -1;
250  }
251
252  args[0] = "ifconfig";
253  args[1] = ifname;
254
255  for (arg = 1; arg < argc; ++arg)
256    args[arg + 1] = argv[arg];
257
258  args[argc + 1] = "up";
259
260  rtems_bsd_rc_conf_print_cmd(rc_conf, "ifconfig", argc + 2, args);
261
262  r = rtems_bsd_command_ifconfig(argc + 2, (char**) args);
263
264  free(args);
265
266  if (r != EX_OK) {
267    errno = ECANCELED;
268    return -1;
269  }
270
271  r = rtems_bsd_command_ifconfig(2, (char**) ifconfig_show);
272
273  return r;
274}
275
276/*
277 * hostname
278 *
279 * eg hostname="myhost"
280 *
281 * See 'man rc.conf(5)' on FreeBSD.
282 */
283static int
284hostname(rtems_bsd_rc_conf* rc_conf, rtems_bsd_rc_conf_argc_argv* aa)
285{
286  const char**      argv;
287  int               r;
288  const char const* default_argv[] = { "hostname", "Amnesiac", NULL };
289
290  r = rtems_bsd_rc_conf_find(rc_conf, "hostname", aa);
291  if (r < 0 && errno != ENOENT)
292    return -1;
293
294  if (r < 0 || (r == 0 && aa->argc != 2)) {
295    argv = default_argv;
296  }
297  else {
298    argv = aa->argv;
299  }
300
301  fprintf(stdout, "Setting hostname: %s.\n", argv[1]);
302
303  return sethostname(argv[1], strlen(argv[1]));
304}
305
306/*
307 * defaultrouter
308 *
309 * eg defaultrouter="1.2.3.4"
310 *
311 * See 'man rc.conf(5)' on FreeBSD.
312 */
313static int
314defaultrouter(rtems_bsd_rc_conf* rc_conf, rtems_bsd_rc_conf_argc_argv* aa)
315{
316  int r;
317
318  r = rtems_bsd_rc_conf_find(rc_conf, "defaultrouter", aa);
319  if (r < 0 && errno != ENOENT)
320    return -1;
321
322  if (r == 0) {
323    if (aa->argc > 2) {
324      errno = EINVAL;
325      return -1;
326    }
327
328    if (strcasecmp(aa->argv[1], "NO") != 0) {
329      const char* args[] = { "route", "add", "default", aa->argv[1], NULL };
330      int         r;
331
332      rtems_bsd_rc_conf_print_cmd(rc_conf, "defaultrouter", 4, args);
333
334      r = rtems_bsd_command_route(4, (char**) args);
335      if (r != EX_OK) {
336        errno = ECANCELED;
337        return -1;
338      }
339    }
340  }
341
342  return 0;
343}
344
345static int
346show_interfaces(const char* msg, struct ifaddrs* ifap)
347{
348  struct ifaddrs* ifa;
349
350  fprintf(stdout, msg);
351
352  /*
353   * Always have lo0 first.
354   */
355
356  for (ifa = ifap; ifa; ifa = ifa->ifa_next) {
357    if (strcasecmp("lo0", ifa->ifa_name) == 0) {
358      fprintf(stdout, "%s ", ifa->ifa_name);
359      break;
360    }
361  }
362
363  for (ifa = ifap; ifa; ifa = ifa->ifa_next) {
364    if (strcasecmp("lo0", ifa->ifa_name) != 0) {
365      fprintf(stdout, "%s ", ifa->ifa_name);
366    }
367  }
368
369  fprintf(stdout, "\b.\n");
370
371  return 0;
372}
373
374static int
375dhcp_check(rtems_bsd_rc_conf_argc_argv* aa)
376{
377  if (aa->argc == 2 &&
378      (strcasecmp("DHCP",     aa->argv[1]) == 0 ||
379       strcasecmp("SYNCDHCP", aa->argv[1]) == 0))
380    return true;
381  return false;
382}
383
384static int
385setup_lo0(rtems_bsd_rc_conf* rc_conf, struct ifaddrs* ifap)
386{
387  struct ifaddrs* ifa;
388
389  for (ifa = ifap; ifa; ifa = ifa->ifa_next) {
390    if (strcasecmp("lo0", ifa->ifa_name) == 0) {
391      const char* lo0_argv[] = {
392        "ifconfig_lo0", "inet", "127.0.0.1", "netmask", "255.0.0.0", NULL
393      };
394      show_result("lo0", ifconfig_(rc_conf, "lo0", 5, lo0_argv));
395      return 0;
396    }
397  }
398
399  fprintf(stderr, "warning: no loopback interface found\n");
400
401  return -1;
402}
403
404static int
405setup_interfaces(rtems_bsd_rc_conf*           rc_conf,
406                 rtems_bsd_rc_conf_argc_argv* aa,
407                 struct ifaddrs*              ifap,
408                 bool*                        dhcp)
409{
410  struct ifaddrs* ifa;
411  int             r;
412
413  for (ifa = ifap; ifa; ifa = ifa->ifa_next) {
414    if (strcasecmp("lo0", ifa->ifa_name) != 0) {
415      char iface[64];
416      snprintf(iface, sizeof(iface), "ifconfig_%s", ifa->ifa_name);
417      r = rtems_bsd_rc_conf_find(rc_conf, iface, aa);
418      if (r == 0) {
419        if (dhcp_check(aa)) {
420          *dhcp = true;
421        }
422        else {
423          show_result(iface, ifconfig_(rc_conf, ifa->ifa_name, aa->argc, aa->argv));
424        }
425      }
426    }
427  }
428
429  return 0;
430}
431
432static int
433setup_vlans(rtems_bsd_rc_conf*           rc_conf,
434            rtems_bsd_rc_conf_argc_argv* aa,
435            struct ifaddrs*              ifap,
436            bool*                        dhcp)
437{
438  rtems_bsd_rc_conf_argc_argv* vaa;
439  struct ifaddrs*              ifa;
440
441  vaa = rtems_bsd_rc_conf_argc_argv_create();
442  if (vaa == NULL)
443    return -1;
444
445  show_result("create_args", load_create_args(rc_conf, aa));
446
447  for (ifa = ifap; ifa; ifa = ifa->ifa_next) {
448    if (strcasecmp("lo0", ifa->ifa_name) != 0) {
449      char expr[128];
450      int  r;
451      /*
452       * Look for vlans_'iface'="101 102 103"
453       */
454      snprintf(expr, sizeof(expr), "vlans_%s", ifa->ifa_name);
455      r = rtems_bsd_rc_conf_find(rc_conf, expr, aa);
456      if (r == 0) {
457        int arg;
458        for (arg = 1; arg < aa->argc; ++arg) {
459          char vlan_name[64];
460          const char* vlan_create[] = {
461            "ifconfig", vlan_name, "create", NULL
462          };
463          /*
464           * Create the VLAN name as 'iface'.'vlan'.
465           */
466          snprintf(vlan_name, sizeof(vlan_name),
467                   "%s.%s", ifa->ifa_name, aa->argv[arg]);
468          rtems_bsd_rc_conf_print_cmd(rc_conf, "vlan", 3, vlan_create);
469          r = rtems_bsd_command_ifconfig(3, (char**) vlan_create);
470          if (r == 0) {
471            /*
472             * Look for ifconfig_'iface'_'vlan'="..."
473             */
474            snprintf(expr, sizeof(expr),
475                     "ifconfig_%s_%s", ifa->ifa_name, aa->argv[arg]);
476            r = rtems_bsd_rc_conf_find(rc_conf, expr, vaa);
477            if (r == 0) {
478              if (dhcp_check(vaa)) {
479                *dhcp = true;
480              }
481              else {
482                show_result(vlan_name, ifconfig_(rc_conf, vlan_name,
483                                                 vaa->argc, vaa->argv));
484              }
485            }
486          }
487        }
488      }
489    }
490  }
491
492  rtems_bsd_rc_conf_argc_argv_destroy(vaa);
493
494  return 0;
495}
496
497/*
498 * The rc_conf struct cannot be passed to a thread as a pointer. It can only be
499 * used in the rc.conf worker thread. As a result the values need to print a
500 * verbose message to aid debugging needs to have local oopies made and passed
501 * to the dhcpcd worker. The dhcpcd worker runs for ever.
502 */
503typedef struct dhcpcd_data {
504  rtems_bsd_rc_conf_argc_argv* argc_argv;
505  bool                         verbose;
506  const char*                  name;
507} dhcpcd_data;
508
509static void
510dhcpcd_worker(rtems_task_argument arg)
511{
512  dhcpcd_data*  dd = (dhcpcd_data*) arg;
513  int           argc;
514  const char**  argv;
515  const char*   dhcpcd_argv[] = { "dhcpcd", NULL };
516  struct stat   sb;
517  int           r;
518
519  r = stat("/var", &sb);
520  if (r < 0) {
521    mkdir("/var", S_IRWXU | S_IRWXG | S_IRWXO);
522  }
523
524  r = stat("/var/db", &sb);
525  if (r < 0) {
526    mkdir("/var/db", S_IRWXU | S_IRWXG | S_IRWXO);
527  }
528
529  if (dd->argc_argv->argc > 0) {
530    argc = dd->argc_argv->argc;
531    argv = dd->argc_argv->argv;
532  }
533  else {
534    argc = 1;
535    argv = dhcpcd_argv;
536  }
537
538  if (dd->verbose) {
539    fprintf(stdout, "rc.conf: %s: dhcpcd ", dd->name);
540    for (r = 1; r < argc; ++r)
541      fprintf(stdout, "%s ", argv[r]);
542    fprintf(stdout, "\n");
543  }
544
545  r = rtems_bsd_command_dhcpcd(argc, argv);
546  if (r != EX_OK)
547    fprintf(stderr, "error: dhcpcd: stopped\n");
548
549  free(dd->name);
550  rtems_bsd_rc_conf_argc_argv_destroy(dd->argc_argv);
551  free(dd);
552
553  rtems_task_delete(RTEMS_SELF);
554}
555
556static int
557run_dhcp(rtems_bsd_rc_conf* rc_conf, rtems_bsd_rc_conf_argc_argv* aa)
558{
559  dhcpcd_data*        dd;
560  rtems_status_code   sc;
561  rtems_id            id;
562  rtems_task_priority priority = RTEMS_MAXIMUM_PRIORITY - 1;
563  char*               end = NULL;
564  int                 delay = 30;
565  int                 r;
566
567  /*
568   * These are passed to the worker and cleaned up there if it ever exits. Do
569   * not destroy here unless an error before the thread runs.
570   */
571  dd = calloc(1, sizeof(*dd));
572  if (dd == NULL) {
573    fprintf(stderr, "error: dhcpcd data: no memory\n");
574    errno = ENOMEM;
575    return -1;
576  }
577
578  dd->name = strdup(rtems_bsd_rc_conf_name(rc_conf));
579  if (dd == NULL) {
580    free(dd);
581    fprintf(stderr, "error: dhcpcd data: no memory\n");
582    errno = ENOMEM;
583    return -1;
584  }
585
586  dd->argc_argv = rtems_bsd_rc_conf_argc_argv_create();
587  if (dd->argc_argv == NULL) {
588    free(dd->name);
589    free(dd);
590    errno = ENOMEM;
591    return -1;
592  }
593
594  dd->verbose = rtems_bsd_rc_conf_verbose(rc_conf);
595
596  r = rtems_bsd_rc_conf_find(rc_conf, "dhcpcd_priority", dd->argc_argv);
597  if (r == 0) {
598    if (dd->argc_argv->argc == 2) {
599      priority = strtoul(dd->argc_argv->argv[1], &end, 10);
600      if (priority == 0 || *end != '\0')
601        priority = RTEMS_MAXIMUM_PRIORITY - 1;
602    }
603  }
604
605  rtems_bsd_rc_conf_find(rc_conf, "dhcpcd_options", dd->argc_argv);
606
607  sc = rtems_task_create(rtems_build_name('D', 'H', 'C', 'P'),
608                         priority,
609                         2 * RTEMS_MINIMUM_STACK_SIZE,
610                         RTEMS_DEFAULT_MODES,
611                         RTEMS_FLOATING_POINT,
612                         &id);
613  if (sc == RTEMS_SUCCESSFUL)
614    sc = rtems_task_start(id, dhcpcd_worker, (rtems_task_argument) dd);
615  if (sc != RTEMS_SUCCESSFUL) {
616    fprintf(stderr,
617            "error: dhcpcd: thread create/start: %s\n", rtems_status_text(sc));
618    rtems_bsd_rc_conf_argc_argv_destroy(dd->argc_argv);
619    free(dd->name);
620    free(dd);
621    errno = EIO;
622    return -1;
623  }
624
625  /*
626   * See if a delay is specified else use default to 30 seconds. Wait for a
627   * valid default route.
628   */
629  r = rtems_bsd_rc_conf_find(rc_conf, "defaultroute_delay", aa);
630  if (r == 0 && aa->argc == 2) {
631    delay = (int) strtol(aa->argv[1], &end, 10);
632    if (*end != '\0') {
633      fprintf(stderr, "error: defaultroute_delay: invalid delay value\n");
634      delay = 30;
635    }
636  }
637
638  printf("Waiting %ds for default route interface: ", delay);
639  fflush(stdout);
640
641  while (delay > 0) {
642    struct sockaddr_in sin;
643    struct sockaddr*   rti_info[RTAX_MAX];
644
645    --delay;
646
647    memset(&sin, 0, sizeof(sin));
648    sin.sin_family = AF_INET;
649    inet_pton(AF_INET, "0.0.0.0.", &sin.sin_addr);
650
651    r = rtems_get_route(&sin, rti_info);
652    if (r == 0 && rti_info[RTAX_GATEWAY] != NULL) {
653      break;
654    }
655    else if (r < 0 && errno != ESRCH) {
656      fprintf(stderr,
657              "error: get routes %d: %d %s\n", r, errno, strerror(errno));
658    }
659
660    sleep(1);
661  }
662
663  /*
664   * We should print the interface but I cannot see how to get the interface
665   * with the default route without a lot of code.
666   */
667  if (delay > 0)
668    printf("found.\n");
669  else
670    printf("\nerror: no default route found\n");
671
672  return 0;
673}
674
675static int
676interfaces(rtems_bsd_rc_conf* rc_conf, rtems_bsd_rc_conf_argc_argv* aa)
677{
678  struct ifaddrs* ifap;
679  bool            dhcp = false;
680
681  if (getifaddrs(&ifap) != 0) {
682    fprintf(stderr, "error: interfaces: getifaddrs: %s\n", strerror(errno));
683    return -1;
684  }
685
686  show_interfaces("Starting network: ", ifap);
687  show_result("cloned_interfaces", cloned_interfaces(rc_conf, aa));
688  show_result("lo0", setup_lo0(rc_conf, ifap));
689  show_result("ifaces", setup_interfaces(rc_conf, aa, ifap, &dhcp));
690  show_result("vlans", setup_vlans(rc_conf, aa, ifap, &dhcp));
691  show_result("defaultrouter", defaultrouter(rc_conf, aa));
692  if (dhcp)
693    show_result("dhcp", run_dhcp(rc_conf, aa));
694
695  free(ifap);
696
697  return 0;
698}
699
700static int
701network_service(rtems_bsd_rc_conf* rc_conf)
702{
703  rtems_bsd_rc_conf_argc_argv* aa;
704  int                          r;
705
706  aa = rtems_bsd_rc_conf_argc_argv_create();
707  if (aa == NULL)
708    return -1;
709
710  show_result("hostname",    hostname(rc_conf, aa));
711
712  r = interfaces(rc_conf, aa);
713  if (r < 0) {
714    rtems_bsd_rc_conf_argc_argv_destroy(aa);
715    return -1;
716  }
717
718  rtems_bsd_rc_conf_argc_argv_destroy(aa);
719
720  return 0;
721}
722
723void
724rc_conf_net_init(void* arg)
725{
726  int r;
727  r = rtems_bsd_rc_conf_service_add("network",
728                                    "after:first;",
729                                    network_service);
730  if (r < 0)
731    fprintf(stderr,
732            "error: network service add failed: %s\n", strerror(errno));
733}
Note: See TracBrowser for help on using the repository browser.