chiark / gitweb /
net_setup/networkd: warn if matching is done on possibly unstable ifname
[elogind.git] / src / network / networkctl.c
1 /*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
2
3 /***
4   This file is part of systemd.
5
6   Copyright 2014 Lennart Poettering
7
8   systemd is free software; you can redistribute it and/or modify it
9   under the terms of the GNU Lesser General Public License as published by
10   the Free Software Foundation; either version 2.1 of the License, or
11   (at your option) any later version.
12
13   systemd is distributed in the hope that it will be useful, but
14   WITHOUT ANY WARRANTY; without even the implied warranty of
15   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16   Lesser General Public License for more details.
17
18   You should have received a copy of the GNU Lesser General Public License
19   along with systemd; If not, see <http://www.gnu.org/licenses/>.
20 ***/
21
22 #include <stdbool.h>
23 #include <getopt.h>
24
25 #include "sd-network.h"
26 #include "sd-rtnl.h"
27 #include "libudev.h"
28
29 #include "build.h"
30 #include "util.h"
31 #include "pager.h"
32 #include "rtnl-util.h"
33 #include "udev-util.h"
34 #include "arphrd-list.h"
35 #include "local-addresses.h"
36 #include "socket-util.h"
37 #include "ether-addr-util.h"
38
39 static bool arg_no_pager = false;
40 static bool arg_legend = true;
41 static bool arg_all = false;
42
43 static void pager_open_if_enabled(void) {
44
45         if (arg_no_pager)
46                 return;
47
48         pager_open(false);
49 }
50
51 static int link_get_type_string(int iftype, struct udev_device *d, char **ret) {
52         const char *t;
53         char *p;
54
55         if (iftype == ARPHRD_ETHER && d) {
56                 const char *devtype, *id = NULL;
57                 /* WLANs have iftype ARPHRD_ETHER, but we want
58                  * to show a more useful type string for
59                  * them */
60
61                 devtype = udev_device_get_devtype(d);
62                 if (streq_ptr(devtype, "wlan"))
63                         id = "wlan";
64                 else if (streq_ptr(devtype, "wwan"))
65                         id = "wwan";
66
67                 if (id) {
68                         p = strdup(id);
69                         if (!p)
70                                 return -ENOMEM;
71
72                         *ret = p;
73                         return 1;
74                 }
75         }
76
77         t = arphrd_to_name(iftype);
78         if (!t) {
79                 *ret = NULL;
80                 return 0;
81         }
82
83         p = strdup(t);
84         if (!p)
85                 return -ENOMEM;
86
87         ascii_strlower(p);
88         *ret = p;
89
90         return 0;
91 }
92
93 typedef struct LinkInfo {
94         const char *name;
95         int ifindex;
96         unsigned iftype;
97 } LinkInfo;
98
99 static int link_info_compare(const void *a, const void *b) {
100         const LinkInfo *x = a, *y = b;
101
102         return x->ifindex - y->ifindex;
103 }
104
105 static int decode_and_sort_links(sd_rtnl_message *m, LinkInfo **ret) {
106         _cleanup_free_ LinkInfo *links = NULL;
107         size_t size = 0, c = 0;
108         sd_rtnl_message *i;
109         int r;
110
111         for (i = m; i; i = sd_rtnl_message_next(i)) {
112                 const char *name;
113                 unsigned iftype;
114                 uint16_t type;
115                 int ifindex;
116
117                 r = sd_rtnl_message_get_type(i, &type);
118                 if (r < 0)
119                         return r;
120
121                 if (type != RTM_NEWLINK)
122                         continue;
123
124                 r = sd_rtnl_message_link_get_ifindex(i, &ifindex);
125                 if (r < 0)
126                         return r;
127
128                 r = sd_rtnl_message_read_string(i, IFLA_IFNAME, &name);
129                 if (r < 0)
130                         return r;
131
132                 r = sd_rtnl_message_link_get_type(i, &iftype);
133                 if (r < 0)
134                         return r;
135
136                 if (!GREEDY_REALLOC(links, size, c+1))
137                         return -ENOMEM;
138
139                 links[c].name = name;
140                 links[c].ifindex = ifindex;
141                 links[c].iftype = iftype;
142                 c++;
143         }
144
145         qsort_safe(links, c, sizeof(LinkInfo), link_info_compare);
146
147         *ret = links;
148         links = NULL;
149
150         return (int) c;
151 }
152
153 static void operational_state_to_color(const char *state, const char **on, const char **off) {
154         assert(on);
155         assert(off);
156
157         if (streq_ptr(state, "routable")) {
158                 *on = ansi_highlight_green();
159                 *off = ansi_highlight_off();
160         } else if (streq_ptr(state, "degraded")) {
161                 *on = ansi_highlight_yellow();
162                 *off = ansi_highlight_off();
163         } else
164                 *on = *off = "";
165 }
166
167 static void setup_state_to_color(const char *state, const char **on, const char **off) {
168         assert(on);
169         assert(off);
170
171         if (streq_ptr(state, "configured")) {
172                 *on = ansi_highlight_green();
173                 *off = ansi_highlight_off();
174         } else if (streq_ptr(state, "configuring")) {
175                 *on = ansi_highlight_yellow();
176                 *off = ansi_highlight_off();
177         } else if (streq_ptr(state, "failed") || streq_ptr(state, "linger")) {
178                 *on = ansi_highlight_red();
179                 *off = ansi_highlight_off();
180         } else
181                 *on = *off = "";
182 }
183
184 static int list_links(char **args, unsigned n) {
185         _cleanup_rtnl_message_unref_ sd_rtnl_message *req = NULL, *reply = NULL;
186         _cleanup_udev_unref_ struct udev *udev = NULL;
187         _cleanup_rtnl_unref_ sd_rtnl *rtnl = NULL;
188         _cleanup_free_ LinkInfo *links = NULL;
189         int r, c, i;
190
191         pager_open_if_enabled();
192
193         r = sd_rtnl_open(&rtnl, 0);
194         if (r < 0)
195                 return log_error_errno(r, "Failed to connect to netlink: %m");
196
197         udev = udev_new();
198         if (!udev)
199                 return log_error_errno(errno, "Failed to connect to udev: %m");
200
201         r = sd_rtnl_message_new_link(rtnl, &req, RTM_GETLINK, 0);
202         if (r < 0)
203                 return rtnl_log_create_error(r);
204
205         r = sd_rtnl_message_request_dump(req, true);
206         if (r < 0)
207                 return rtnl_log_create_error(r);
208
209         r = sd_rtnl_call(rtnl, req, 0, &reply);
210         if (r < 0)
211                 return log_error_errno(r, "Failed to enumerate links: %m");
212
213         if (arg_legend)
214                 printf("%3s %-16s %-18s %-11s %-10s\n", "IDX", "LINK", "TYPE", "OPERATIONAL", "SETUP");
215
216         c = decode_and_sort_links(reply, &links);
217         if (c < 0)
218                 return rtnl_log_parse_error(c);
219
220         for (i = 0; i < c; i++) {
221                 _cleanup_free_ char *setup_state = NULL, *operational_state = NULL;
222                 _cleanup_udev_device_unref_ struct udev_device *d = NULL;
223                 const char *on_color_operational, *off_color_operational,
224                            *on_color_setup, *off_color_setup;
225                  char devid[2 + DECIMAL_STR_MAX(int)];
226                 _cleanup_free_ char *t = NULL;
227
228                 sd_network_link_get_operational_state(links[i].ifindex, &operational_state);
229                 operational_state_to_color(operational_state, &on_color_operational, &off_color_operational);
230
231                 sd_network_link_get_setup_state(links[i].ifindex, &setup_state);
232                 setup_state_to_color(setup_state, &on_color_setup, &off_color_setup);
233
234                 sprintf(devid, "n%i", links[i].ifindex);
235                 d = udev_device_new_from_device_id(udev, devid);
236
237                 link_get_type_string(links[i].iftype, d, &t);
238
239                 printf("%3i %-16s %-18s %s%-11s%s %s%-10s%s\n",
240                        links[i].ifindex, links[i].name, strna(t),
241                        on_color_operational, strna(operational_state), off_color_operational,
242                        on_color_setup, strna(setup_state), off_color_setup);
243         }
244
245         if (arg_legend)
246                 printf("\n%i links listed.\n", c);
247
248         return 0;
249 }
250
251 /* IEEE Organizationally Unique Identifier vendor string */
252 static int ieee_oui(struct udev_hwdb *hwdb, struct ether_addr *mac, char **ret) {
253         struct udev_list_entry *entry;
254         char *description;
255         char str[strlen("OUI:XXYYXXYYXXYY") + 1];
256
257         /* skip commonly misused 00:00:00 (Xerox) prefix */
258         if (memcmp(mac, "\0\0\0", 3) == 0)
259                 return -EINVAL;
260
261         snprintf(str, sizeof(str), "OUI:" ETHER_ADDR_FORMAT_STR, ETHER_ADDR_FORMAT_VAL(*mac));
262
263         udev_list_entry_foreach(entry, udev_hwdb_get_properties_list_entry(hwdb, str, 0))
264                 if (strcmp(udev_list_entry_get_name(entry), "ID_OUI_FROM_DATABASE") == 0) {
265                         description = strdup(udev_list_entry_get_value(entry));
266                         if (!description)
267                                 return -ENOMEM;
268
269                         *ret = description;
270                         return 0;
271                 }
272
273         return -ENODATA;
274 }
275
276 static int get_gateway_description(sd_rtnl *rtnl, struct udev_hwdb *hwdb, int ifindex, int family,
277                                    union in_addr_union *gateway, char **gateway_description) {
278         _cleanup_rtnl_message_unref_ sd_rtnl_message *req = NULL, *reply = NULL;
279         sd_rtnl_message *m;
280         int r;
281
282         assert(rtnl);
283         assert(ifindex >= 0);
284         assert(family == AF_INET || family == AF_INET6);
285         assert(gateway);
286         assert(gateway_description);
287
288         r = sd_rtnl_message_new_neigh(rtnl, &req, RTM_GETNEIGH, ifindex, family);
289         if (r < 0)
290                 return r;
291
292         r = sd_rtnl_message_request_dump(req, true);
293         if (r < 0)
294                 return r;
295
296         r = sd_rtnl_call(rtnl, req, 0, &reply);
297         if (r < 0)
298                 return r;
299
300         for (m = reply; m; m = sd_rtnl_message_next(m)) {
301                 union in_addr_union gw = {};
302                 struct ether_addr mac = {};
303                 uint16_t type;
304                 int ifi, fam;
305
306                 r = sd_rtnl_message_get_errno(m);
307                 if (r < 0) {
308                         log_error_errno(r, "got error: %m");
309                         continue;
310                 }
311
312                 r = sd_rtnl_message_get_type(m, &type);
313                 if (r < 0) {
314                         log_error_errno(r, "could not get type: %m");
315                         continue;
316                 }
317
318                 if (type != RTM_NEWNEIGH) {
319                         log_error("type is not RTM_NEWNEIGH");
320                         continue;
321                 }
322
323                 r = sd_rtnl_message_neigh_get_family(m, &fam);
324                 if (r < 0) {
325                         log_error_errno(r, "could not get family: %m");
326                         continue;
327                 }
328
329                 if (fam != family) {
330                         log_error("family is not correct");
331                         continue;
332                 }
333
334                 r = sd_rtnl_message_neigh_get_ifindex(m, &ifi);
335                 if (r < 0) {
336                         log_error_errno(r, "could not get ifindex: %m");
337                         continue;
338                 }
339
340                 if (ifindex > 0 && ifi != ifindex)
341                         continue;
342
343                 switch (fam) {
344                 case AF_INET:
345                         r = sd_rtnl_message_read_in_addr(m, NDA_DST, &gw.in);
346                         if (r < 0)
347                                 continue;
348
349                         break;
350                 case AF_INET6:
351                         r = sd_rtnl_message_read_in6_addr(m, NDA_DST, &gw.in6);
352                         if (r < 0)
353                                 continue;
354
355                         break;
356                 default:
357                         continue;
358                 }
359
360                 if (!in_addr_equal(fam, &gw, gateway))
361                         continue;
362
363                 r = sd_rtnl_message_read_ether_addr(m, NDA_LLADDR, &mac);
364                 if (r < 0)
365                         continue;
366
367                 r = ieee_oui(hwdb, &mac, gateway_description);
368                 if (r < 0)
369                         continue;
370
371                 return 0;
372         }
373
374         return -ENODATA;
375 }
376
377 static int dump_gateways(sd_rtnl *rtnl, struct udev_hwdb *hwdb, const char *prefix, int ifindex) {
378         _cleanup_free_ struct local_address *local = NULL;
379         int r, n, i;
380
381         n = local_gateways(rtnl, ifindex, AF_UNSPEC, &local);
382         if (n < 0)
383                 return n;
384
385         for (i = 0; i < n; i++) {
386                 _cleanup_free_ char *gateway = NULL, *description = NULL;
387
388                 r = in_addr_to_string(local[i].family, &local[i].address, &gateway);
389                 if (r < 0)
390                         return r;
391
392                 r = get_gateway_description(rtnl, hwdb, ifindex, local[i].family, &local[i].address, &description);
393                 if (r < 0)
394                         log_debug_errno(r, "Could not get description of gateway: %m");
395
396                 if (description)
397                         printf("%*s%s (%s)\n",
398                                (int) strlen(prefix),
399                                i == 0 ? prefix : "",
400                                gateway, description);
401                 else
402                         printf("%*s%s\n",
403                                (int) strlen(prefix),
404                                i == 0 ? prefix : "",
405                                gateway);
406         }
407
408         return 0;
409 }
410
411 static int dump_addresses(sd_rtnl *rtnl, const char *prefix, int ifindex) {
412         _cleanup_free_ struct local_address *local = NULL;
413         int r, n, i;
414
415         n = local_addresses(rtnl, ifindex, AF_UNSPEC, &local);
416         if (n < 0)
417                 return n;
418
419         for (i = 0; i < n; i++) {
420                 _cleanup_free_ char *pretty = NULL;
421
422                 r = in_addr_to_string(local[i].family, &local[i].address, &pretty);
423                 if (r < 0)
424                         return r;
425
426                 printf("%*s%s\n",
427                        (int) strlen(prefix),
428                        i == 0 ? prefix : "",
429                        pretty);
430         }
431
432         return 0;
433 }
434
435 static void dump_list(const char *prefix, char **l) {
436         char **i;
437
438         STRV_FOREACH(i, l) {
439                 printf("%*s%s\n",
440                        (int) strlen(prefix),
441                        i == l ? prefix : "",
442                        *i);
443         }
444 }
445
446 static int link_status_one(sd_rtnl *rtnl, struct udev *udev, const char *name) {
447         _cleanup_strv_free_ char **dns = NULL, **ntp = NULL, **domains = NULL;
448         _cleanup_free_ char *setup_state = NULL, *operational_state = NULL;
449         _cleanup_rtnl_message_unref_ sd_rtnl_message *req = NULL, *reply = NULL;
450         _cleanup_udev_device_unref_ struct udev_device *d = NULL;
451         _cleanup_udev_hwdb_unref_ struct udev_hwdb *hwdb = NULL;
452         char devid[2 + DECIMAL_STR_MAX(int)];
453         _cleanup_free_ char *t = NULL, *network = NULL;
454         const char *driver = NULL, *path = NULL, *vendor = NULL, *model = NULL, *link = NULL;
455         const char *on_color_operational, *off_color_operational,
456                    *on_color_setup, *off_color_setup;
457         struct ether_addr e;
458         unsigned iftype;
459         int r, ifindex;
460         bool have_mac;
461         uint32_t mtu;
462
463         assert(rtnl);
464         assert(udev);
465         assert(name);
466
467         if (safe_atoi(name, &ifindex) >= 0 && ifindex > 0)
468                 r = sd_rtnl_message_new_link(rtnl, &req, RTM_GETLINK, ifindex);
469         else {
470                 r = sd_rtnl_message_new_link(rtnl, &req, RTM_GETLINK, 0);
471                 if (r < 0)
472                         return rtnl_log_create_error(r);
473
474                 r = sd_rtnl_message_append_string(req, IFLA_IFNAME, name);
475         }
476
477         if (r < 0)
478                 return rtnl_log_create_error(r);
479
480         r = sd_rtnl_call(rtnl, req, 0, &reply);
481         if (r < 0)
482                 return log_error_errno(r, "Failed to query link: %m");
483
484         r = sd_rtnl_message_link_get_ifindex(reply, &ifindex);
485         if (r < 0)
486                 return rtnl_log_parse_error(r);
487
488         r = sd_rtnl_message_read_string(reply, IFLA_IFNAME, &name);
489         if (r < 0)
490                 return rtnl_log_parse_error(r);
491
492         r = sd_rtnl_message_link_get_type(reply, &iftype);
493         if (r < 0)
494                 return rtnl_log_parse_error(r);
495
496         have_mac = sd_rtnl_message_read_ether_addr(reply, IFLA_ADDRESS, &e) >= 0;
497
498         if (have_mac) {
499                 const uint8_t *p;
500                 bool all_zeroes = true;
501
502                 for (p = (uint8_t*) &e; p < (uint8_t*) &e + sizeof(e); p++)
503                         if (*p != 0) {
504                                 all_zeroes = false;
505                                 break;
506                         }
507
508                 if (all_zeroes)
509                         have_mac = false;
510         }
511
512         sd_rtnl_message_read_u32(reply, IFLA_MTU, &mtu);
513
514         sd_network_link_get_operational_state(ifindex, &operational_state);
515         operational_state_to_color(operational_state, &on_color_operational, &off_color_operational);
516
517         sd_network_link_get_setup_state(ifindex, &setup_state);
518         setup_state_to_color(setup_state, &on_color_setup, &off_color_setup);
519
520         sd_network_link_get_dns(ifindex, &dns);
521         sd_network_link_get_ntp(ifindex, &ntp);
522         sd_network_link_get_domains(ifindex, &domains);
523         r = sd_network_link_get_wildcard_domain(ifindex);
524         if (r > 0) {
525                 char *wildcard;
526
527                 wildcard = strdup("*");
528                 if (!wildcard)
529                         return log_oom();
530
531                 if (strv_consume(&domains, wildcard) < 0)
532                         return log_oom();
533         }
534
535         sprintf(devid, "n%i", ifindex);
536         d = udev_device_new_from_device_id(udev, devid);
537
538         link_get_type_string(iftype, d, &t);
539
540         if (d) {
541                 link = udev_device_get_property_value(d, "ID_NET_LINK_FILE");
542                 driver = udev_device_get_property_value(d, "ID_NET_DRIVER");
543                 path = udev_device_get_property_value(d, "ID_PATH");
544
545                 vendor = udev_device_get_property_value(d, "ID_VENDOR_FROM_DATABASE");
546                 if (!vendor)
547                         vendor = udev_device_get_property_value(d, "ID_VENDOR");
548
549                 model = udev_device_get_property_value(d, "ID_MODEL_FROM_DATABASE");
550                 if (!model)
551                         model = udev_device_get_property_value(d, "ID_MODEL");
552         }
553
554         sd_network_link_get_network_file(ifindex, &network);
555
556         printf("%s%s%s %i: %s\n", on_color_operational, draw_special_char(DRAW_BLACK_CIRCLE), off_color_operational, ifindex, name);
557
558         printf("   Link File: %s\n"
559                "Network File: %s\n"
560                "        Type: %s\n"
561                "       State: %s%s%s (%s%s%s)\n",
562                strna(link),
563                strna(network),
564                strna(t),
565                on_color_operational, strna(operational_state), off_color_operational,
566                on_color_setup, strna(setup_state), off_color_setup);
567
568         if (path)
569                 printf("        Path: %s\n", path);
570         if (driver)
571                 printf("      Driver: %s\n", driver);
572         if (vendor)
573                 printf("      Vendor: %s\n", vendor);
574         if (model)
575                 printf("       Model: %s\n", model);
576
577         if (have_mac) {
578                 char ea[ETHER_ADDR_TO_STRING_MAX];
579                 printf("  HW Address: %s\n", ether_addr_to_string(&e, ea));
580         }
581
582         if (mtu > 0)
583                 printf("         MTU: %u\n", mtu);
584
585         hwdb = udev_hwdb_new(udev);
586
587         dump_gateways(rtnl, hwdb, "     Gateway: ", ifindex);
588
589         dump_addresses(rtnl, "     Address: ", ifindex);
590
591         if (!strv_isempty(dns))
592                 dump_list("         DNS: ", dns);
593         if (!strv_isempty(domains))
594                 dump_list("      Domain: ", domains);
595         if (!strv_isempty(ntp))
596                 dump_list("         NTP: ", ntp);
597
598         return 0;
599 }
600
601 static int link_status(char **args, unsigned n) {
602         _cleanup_udev_unref_ struct udev *udev = NULL;
603         _cleanup_rtnl_unref_ sd_rtnl *rtnl = NULL;
604         char **name;
605         int r;
606
607         r = sd_rtnl_open(&rtnl, 0);
608         if (r < 0)
609                 return log_error_errno(r, "Failed to connect to netlink: %m");
610
611         udev = udev_new();
612         if (!udev)
613                 return log_error_errno(errno, "Failed to connect to udev: %m");
614
615         if (n <= 1 && !arg_all) {
616                 _cleanup_free_ char *operational_state = NULL;
617                 _cleanup_strv_free_ char **dns = NULL, **ntp = NULL, **domains = NULL;
618                 _cleanup_free_ struct local_address *addresses = NULL;
619                 const char *on_color_operational, *off_color_operational;
620                 int i, c;
621
622                 sd_network_get_operational_state(&operational_state);
623                 operational_state_to_color(operational_state, &on_color_operational, &off_color_operational);
624
625                 printf("       State: %s%s%s\n", on_color_operational, strna(operational_state), off_color_operational);
626
627                 c = local_addresses(rtnl, 0, AF_UNSPEC, &addresses);
628                 for (i = 0; i < c; i++) {
629                         _cleanup_free_ char *pretty = NULL;
630
631                         r = in_addr_to_string(addresses[i].family, &addresses[i].address, &pretty);
632                         if (r < 0)
633                                 return log_oom();
634
635                         printf("%13s %s\n",
636                                i > 0 ? "" : "Address:", pretty);
637                 }
638
639                 sd_network_get_dns(&dns);
640                 if (!strv_isempty(dns))
641                         dump_list("         DNS: ", dns);
642
643                 sd_network_get_domains(&domains);
644                 if (!strv_isempty(domains))
645                         dump_list("      Domain: ", domains);
646
647                 sd_network_get_ntp(&ntp);
648                 if (!strv_isempty(ntp))
649                         dump_list("         NTP: ", ntp);
650
651                 return 0;
652         }
653
654         pager_open_if_enabled();
655
656         if (arg_all) {
657                 _cleanup_rtnl_message_unref_ sd_rtnl_message *req = NULL, *reply = NULL;
658                 _cleanup_free_ LinkInfo *links = NULL;
659                 int c, i;
660
661                 r = sd_rtnl_message_new_link(rtnl, &req, RTM_GETLINK, 0);
662                 if (r < 0)
663                         return rtnl_log_create_error(r);
664
665                 r = sd_rtnl_message_request_dump(req, true);
666                 if (r < 0)
667                         return rtnl_log_create_error(r);
668
669                 r = sd_rtnl_call(rtnl, req, 0, &reply);
670                 if (r < 0)
671                         return log_error_errno(r, "Failed to enumerate links: %m");
672
673                 c = decode_and_sort_links(reply, &links);
674                 if (c < 0)
675                         return rtnl_log_parse_error(c);
676
677                 for (i = 0; i < c; i++) {
678                         if (i > 0)
679                                 fputc('\n', stdout);
680
681                         link_status_one(rtnl, udev, links[i].name);
682                 }
683         }
684
685         STRV_FOREACH(name, args + 1) {
686                 if (name != args+1)
687                         fputc('\n', stdout);
688
689                 link_status_one(rtnl, udev, *name);
690         }
691
692         return 0;
693 }
694
695 static void help(void) {
696         printf("%s [OPTIONS...]\n\n"
697                "Query and control the networking subsystem.\n\n"
698                "  -h --help             Show this help\n"
699                "     --version          Show package version\n"
700                "     --no-pager         Do not pipe output into a pager\n"
701                "     --no-legend        Do not show the headers and footers\n"
702                "  -a --all              Show status for all links\n\n"
703                "Commands:\n"
704                "  list                  List links\n"
705                "  status LINK           Show link status\n"
706                , program_invocation_short_name);
707 }
708
709 static int parse_argv(int argc, char *argv[]) {
710
711         enum {
712                 ARG_VERSION = 0x100,
713                 ARG_NO_PAGER,
714                 ARG_NO_LEGEND,
715         };
716
717         static const struct option options[] = {
718                 { "help",      no_argument,       NULL, 'h'           },
719                 { "version",   no_argument,       NULL, ARG_VERSION   },
720                 { "no-pager",  no_argument,       NULL, ARG_NO_PAGER  },
721                 { "no-legend", no_argument,       NULL, ARG_NO_LEGEND },
722                 { "all",       no_argument,       NULL, 'a'           },
723                 {}
724         };
725
726         int c;
727
728         assert(argc >= 0);
729         assert(argv);
730
731         while ((c = getopt_long(argc, argv, "ha", options, NULL)) >= 0) {
732
733                 switch (c) {
734
735                 case 'h':
736                         help();
737                         return 0;
738
739                 case ARG_VERSION:
740                         puts(PACKAGE_STRING);
741                         puts(SYSTEMD_FEATURES);
742                         return 0;
743
744                 case ARG_NO_PAGER:
745                         arg_no_pager = true;
746                         break;
747
748                 case ARG_NO_LEGEND:
749                         arg_legend = false;
750                         break;
751
752                 case 'a':
753                         arg_all = true;
754                         break;
755
756                 case '?':
757                         return -EINVAL;
758
759                 default:
760                         assert_not_reached("Unhandled option");
761                 }
762         }
763
764         return 1;
765 }
766
767 static int networkctl_main(int argc, char *argv[]) {
768
769         static const struct {
770                 const char* verb;
771                 const enum {
772                         MORE,
773                         LESS,
774                         EQUAL
775                 } argc_cmp;
776                 const int argc;
777                 int (* const dispatch)(char **args, unsigned n);
778         } verbs[] = {
779                 { "list",   LESS, 1, list_links  },
780                 { "status", MORE, 1, link_status },
781         };
782
783         int left;
784         unsigned i;
785
786         assert(argc >= 0);
787         assert(argv);
788
789         left = argc - optind;
790
791         if (left <= 0)
792                 /* Special rule: no arguments means "list" */
793                 i = 0;
794         else {
795                 if (streq(argv[optind], "help")) {
796                         help();
797                         return 0;
798                 }
799
800                 for (i = 0; i < ELEMENTSOF(verbs); i++)
801                         if (streq(argv[optind], verbs[i].verb))
802                                 break;
803
804                 if (i >= ELEMENTSOF(verbs)) {
805                         log_error("Unknown operation %s", argv[optind]);
806                         return -EINVAL;
807                 }
808         }
809
810         switch (verbs[i].argc_cmp) {
811
812         case EQUAL:
813                 if (left != verbs[i].argc) {
814                         log_error("Invalid number of arguments.");
815                         return -EINVAL;
816                 }
817
818                 break;
819
820         case MORE:
821                 if (left < verbs[i].argc) {
822                         log_error("Too few arguments.");
823                         return -EINVAL;
824                 }
825
826                 break;
827
828         case LESS:
829                 if (left > verbs[i].argc) {
830                         log_error("Too many arguments.");
831                         return -EINVAL;
832                 }
833
834                 break;
835
836         default:
837                 assert_not_reached("Unknown comparison operator.");
838         }
839
840         return verbs[i].dispatch(argv + optind, left);
841 }
842
843 int main(int argc, char* argv[]) {
844         int r;
845
846         log_parse_environment();
847         log_open();
848
849         r = parse_argv(argc, argv);
850         if (r <= 0)
851                 goto finish;
852
853         r = networkctl_main(argc, argv);
854
855 finish:
856         pager_close();
857
858         return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS;
859 }