chiark / gitweb /
c8928697f65c8e37acfe8bda8ee193589d539b22
[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         if (!hwdb)
258                 return -EINVAL;
259
260         /* skip commonly misused 00:00:00 (Xerox) prefix */
261         if (memcmp(mac, "\0\0\0", 3) == 0)
262                 return -EINVAL;
263
264         snprintf(str, sizeof(str), "OUI:" ETHER_ADDR_FORMAT_STR, ETHER_ADDR_FORMAT_VAL(*mac));
265
266         udev_list_entry_foreach(entry, udev_hwdb_get_properties_list_entry(hwdb, str, 0))
267                 if (strcmp(udev_list_entry_get_name(entry), "ID_OUI_FROM_DATABASE") == 0) {
268                         description = strdup(udev_list_entry_get_value(entry));
269                         if (!description)
270                                 return -ENOMEM;
271
272                         *ret = description;
273                         return 0;
274                 }
275
276         return -ENODATA;
277 }
278
279 static int get_gateway_description(
280                 sd_rtnl *rtnl,
281                 struct udev_hwdb *hwdb,
282                 int ifindex,
283                 int family,
284                 union in_addr_union *gateway,
285                 char **gateway_description) {
286
287         _cleanup_rtnl_message_unref_ sd_rtnl_message *req = NULL, *reply = NULL;
288         sd_rtnl_message *m;
289         int r;
290
291         assert(rtnl);
292         assert(ifindex >= 0);
293         assert(family == AF_INET || family == AF_INET6);
294         assert(gateway);
295         assert(gateway_description);
296
297         r = sd_rtnl_message_new_neigh(rtnl, &req, RTM_GETNEIGH, ifindex, family);
298         if (r < 0)
299                 return r;
300
301         r = sd_rtnl_message_request_dump(req, true);
302         if (r < 0)
303                 return r;
304
305         r = sd_rtnl_call(rtnl, req, 0, &reply);
306         if (r < 0)
307                 return r;
308
309         for (m = reply; m; m = sd_rtnl_message_next(m)) {
310                 union in_addr_union gw = {};
311                 struct ether_addr mac = {};
312                 uint16_t type;
313                 int ifi, fam;
314
315                 r = sd_rtnl_message_get_errno(m);
316                 if (r < 0) {
317                         log_error_errno(r, "got error: %m");
318                         continue;
319                 }
320
321                 r = sd_rtnl_message_get_type(m, &type);
322                 if (r < 0) {
323                         log_error_errno(r, "could not get type: %m");
324                         continue;
325                 }
326
327                 if (type != RTM_NEWNEIGH) {
328                         log_error("type is not RTM_NEWNEIGH");
329                         continue;
330                 }
331
332                 r = sd_rtnl_message_neigh_get_family(m, &fam);
333                 if (r < 0) {
334                         log_error_errno(r, "could not get family: %m");
335                         continue;
336                 }
337
338                 if (fam != family) {
339                         log_error("family is not correct");
340                         continue;
341                 }
342
343                 r = sd_rtnl_message_neigh_get_ifindex(m, &ifi);
344                 if (r < 0) {
345                         log_error_errno(r, "could not get ifindex: %m");
346                         continue;
347                 }
348
349                 if (ifindex > 0 && ifi != ifindex)
350                         continue;
351
352                 switch (fam) {
353                 case AF_INET:
354                         r = sd_rtnl_message_read_in_addr(m, NDA_DST, &gw.in);
355                         if (r < 0)
356                                 continue;
357
358                         break;
359                 case AF_INET6:
360                         r = sd_rtnl_message_read_in6_addr(m, NDA_DST, &gw.in6);
361                         if (r < 0)
362                                 continue;
363
364                         break;
365                 default:
366                         continue;
367                 }
368
369                 if (!in_addr_equal(fam, &gw, gateway))
370                         continue;
371
372                 r = sd_rtnl_message_read_ether_addr(m, NDA_LLADDR, &mac);
373                 if (r < 0)
374                         continue;
375
376                 r = ieee_oui(hwdb, &mac, gateway_description);
377                 if (r < 0)
378                         continue;
379
380                 return 0;
381         }
382
383         return -ENODATA;
384 }
385
386 static int dump_gateways(
387                 sd_rtnl *rtnl,
388                 struct udev_hwdb *hwdb,
389                 const char *prefix,
390                 int ifindex) {
391
392         _cleanup_free_ struct local_address *local = NULL;
393         int r, n, i;
394
395         n = local_gateways(rtnl, ifindex, AF_UNSPEC, &local);
396         if (n < 0)
397                 return n;
398
399         for (i = 0; i < n; i++) {
400                 _cleanup_free_ char *gateway = NULL, *description = NULL;
401
402                 r = in_addr_to_string(local[i].family, &local[i].address, &gateway);
403                 if (r < 0)
404                         return r;
405
406                 r = get_gateway_description(rtnl, hwdb, local[i].ifindex, local[i].family, &local[i].address, &description);
407                 if (r < 0)
408                         log_debug_errno(r, "Could not get description of gateway: %m");
409
410                 if (description)
411                         printf("%*s%s (%s)\n",
412                                (int) strlen(prefix),
413                                i == 0 ? prefix : "",
414                                gateway, description);
415                 else
416                         printf("%*s%s\n",
417                                (int) strlen(prefix),
418                                i == 0 ? prefix : "",
419                                gateway);
420         }
421
422         return 0;
423 }
424
425 static int dump_addresses(
426                 sd_rtnl *rtnl,
427                 const char *prefix,
428                 int ifindex) {
429
430         _cleanup_free_ struct local_address *local = NULL;
431         int r, n, i;
432
433         n = local_addresses(rtnl, ifindex, AF_UNSPEC, &local);
434         if (n < 0)
435                 return n;
436
437         for (i = 0; i < n; i++) {
438                 _cleanup_free_ char *pretty = NULL;
439
440                 r = in_addr_to_string(local[i].family, &local[i].address, &pretty);
441                 if (r < 0)
442                         return r;
443
444                 printf("%*s%s\n",
445                        (int) strlen(prefix),
446                        i == 0 ? prefix : "",
447                        pretty);
448         }
449
450         return 0;
451 }
452
453 static void dump_list(const char *prefix, char **l) {
454         char **i;
455
456         STRV_FOREACH(i, l) {
457                 printf("%*s%s\n",
458                        (int) strlen(prefix),
459                        i == l ? prefix : "",
460                        *i);
461         }
462 }
463
464 static int link_status_one(
465                 sd_rtnl *rtnl,
466                 struct udev *udev,
467                 struct udev_hwdb *hwdb,
468                 const char *name) {
469
470         _cleanup_strv_free_ char **dns = NULL, **ntp = NULL, **domains = NULL;
471         _cleanup_free_ char *setup_state = NULL, *operational_state = NULL;
472         _cleanup_rtnl_message_unref_ sd_rtnl_message *req = NULL, *reply = NULL;
473         _cleanup_udev_device_unref_ struct udev_device *d = NULL;
474         char devid[2 + DECIMAL_STR_MAX(int)];
475         _cleanup_free_ char *t = NULL, *network = NULL;
476         const char *driver = NULL, *path = NULL, *vendor = NULL, *model = NULL, *link = NULL;
477         const char *on_color_operational, *off_color_operational,
478                    *on_color_setup, *off_color_setup;
479         struct ether_addr e;
480         unsigned iftype;
481         int r, ifindex;
482         bool have_mac;
483         uint32_t mtu;
484
485         assert(rtnl);
486         assert(udev);
487         assert(name);
488
489         if (safe_atoi(name, &ifindex) >= 0 && ifindex > 0)
490                 r = sd_rtnl_message_new_link(rtnl, &req, RTM_GETLINK, ifindex);
491         else {
492                 r = sd_rtnl_message_new_link(rtnl, &req, RTM_GETLINK, 0);
493                 if (r < 0)
494                         return rtnl_log_create_error(r);
495
496                 r = sd_rtnl_message_append_string(req, IFLA_IFNAME, name);
497         }
498
499         if (r < 0)
500                 return rtnl_log_create_error(r);
501
502         r = sd_rtnl_call(rtnl, req, 0, &reply);
503         if (r < 0)
504                 return log_error_errno(r, "Failed to query link: %m");
505
506         r = sd_rtnl_message_link_get_ifindex(reply, &ifindex);
507         if (r < 0)
508                 return rtnl_log_parse_error(r);
509
510         r = sd_rtnl_message_read_string(reply, IFLA_IFNAME, &name);
511         if (r < 0)
512                 return rtnl_log_parse_error(r);
513
514         r = sd_rtnl_message_link_get_type(reply, &iftype);
515         if (r < 0)
516                 return rtnl_log_parse_error(r);
517
518         have_mac = sd_rtnl_message_read_ether_addr(reply, IFLA_ADDRESS, &e) >= 0;
519
520         if (have_mac) {
521                 const uint8_t *p;
522                 bool all_zeroes = true;
523
524                 for (p = (uint8_t*) &e; p < (uint8_t*) &e + sizeof(e); p++)
525                         if (*p != 0) {
526                                 all_zeroes = false;
527                                 break;
528                         }
529
530                 if (all_zeroes)
531                         have_mac = false;
532         }
533
534         sd_rtnl_message_read_u32(reply, IFLA_MTU, &mtu);
535
536         sd_network_link_get_operational_state(ifindex, &operational_state);
537         operational_state_to_color(operational_state, &on_color_operational, &off_color_operational);
538
539         sd_network_link_get_setup_state(ifindex, &setup_state);
540         setup_state_to_color(setup_state, &on_color_setup, &off_color_setup);
541
542         sd_network_link_get_dns(ifindex, &dns);
543         sd_network_link_get_ntp(ifindex, &ntp);
544         sd_network_link_get_domains(ifindex, &domains);
545         r = sd_network_link_get_wildcard_domain(ifindex);
546         if (r > 0) {
547                 char *wildcard;
548
549                 wildcard = strdup("*");
550                 if (!wildcard)
551                         return log_oom();
552
553                 if (strv_consume(&domains, wildcard) < 0)
554                         return log_oom();
555         }
556
557         sprintf(devid, "n%i", ifindex);
558         d = udev_device_new_from_device_id(udev, devid);
559
560         link_get_type_string(iftype, d, &t);
561
562         if (d) {
563                 link = udev_device_get_property_value(d, "ID_NET_LINK_FILE");
564                 driver = udev_device_get_property_value(d, "ID_NET_DRIVER");
565                 path = udev_device_get_property_value(d, "ID_PATH");
566
567                 vendor = udev_device_get_property_value(d, "ID_VENDOR_FROM_DATABASE");
568                 if (!vendor)
569                         vendor = udev_device_get_property_value(d, "ID_VENDOR");
570
571                 model = udev_device_get_property_value(d, "ID_MODEL_FROM_DATABASE");
572                 if (!model)
573                         model = udev_device_get_property_value(d, "ID_MODEL");
574         }
575
576         sd_network_link_get_network_file(ifindex, &network);
577
578         printf("%s%s%s %i: %s\n", on_color_operational, draw_special_char(DRAW_BLACK_CIRCLE), off_color_operational, ifindex, name);
579
580         printf("   Link File: %s\n"
581                "Network File: %s\n"
582                "        Type: %s\n"
583                "       State: %s%s%s (%s%s%s)\n",
584                strna(link),
585                strna(network),
586                strna(t),
587                on_color_operational, strna(operational_state), off_color_operational,
588                on_color_setup, strna(setup_state), off_color_setup);
589
590         if (path)
591                 printf("        Path: %s\n", path);
592         if (driver)
593                 printf("      Driver: %s\n", driver);
594         if (vendor)
595                 printf("      Vendor: %s\n", vendor);
596         if (model)
597                 printf("       Model: %s\n", model);
598
599         if (have_mac) {
600                 _cleanup_free_ char *description = NULL;
601                 char ea[ETHER_ADDR_TO_STRING_MAX];
602
603                 ieee_oui(hwdb, &e, &description);
604
605                 if (description)
606                         printf("  HW Address: %s (%s)\n", ether_addr_to_string(&e, ea), description);
607                 else
608                         printf("  HW Address: %s\n", ether_addr_to_string(&e, ea));
609         }
610
611         if (mtu > 0)
612                 printf("         MTU: %u\n", mtu);
613
614         dump_addresses(rtnl, "     Address: ", ifindex);
615         dump_gateways(rtnl, hwdb, "     Gateway: ", ifindex);
616
617         if (!strv_isempty(dns))
618                 dump_list("         DNS: ", dns);
619         if (!strv_isempty(domains))
620                 dump_list("      Domain: ", domains);
621         if (!strv_isempty(ntp))
622                 dump_list("         NTP: ", ntp);
623
624         return 0;
625 }
626
627 static int link_status(char **args, unsigned n) {
628         _cleanup_udev_hwdb_unref_ struct udev_hwdb *hwdb = NULL;
629         _cleanup_udev_unref_ struct udev *udev = NULL;
630         _cleanup_rtnl_unref_ sd_rtnl *rtnl = NULL;
631         char **name;
632         int r;
633
634         r = sd_rtnl_open(&rtnl, 0);
635         if (r < 0)
636                 return log_error_errno(r, "Failed to connect to netlink: %m");
637
638         udev = udev_new();
639         if (!udev)
640                 return log_error_errno(errno, "Failed to connect to udev: %m");
641
642         hwdb = udev_hwdb_new(udev);
643         if (!hwdb)
644                 log_debug_errno(errno, "Failed to open hardware database: %m");
645
646         if (n <= 1 && !arg_all) {
647                 _cleanup_free_ char *operational_state = NULL;
648                 _cleanup_strv_free_ char **dns = NULL, **ntp = NULL, **domains = NULL;
649                 _cleanup_free_ struct local_address *addresses = NULL;
650                 const char *on_color_operational, *off_color_operational;
651                 int i, c;
652
653                 sd_network_get_operational_state(&operational_state);
654                 operational_state_to_color(operational_state, &on_color_operational, &off_color_operational);
655
656                 printf("       State: %s%s%s\n", on_color_operational, strna(operational_state), off_color_operational);
657
658                 c = local_addresses(rtnl, 0, AF_UNSPEC, &addresses);
659                 for (i = 0; i < c; i++) {
660                         _cleanup_free_ char *pretty = NULL;
661
662                         r = in_addr_to_string(addresses[i].family, &addresses[i].address, &pretty);
663                         if (r < 0)
664                                 return log_oom();
665
666                         printf("%13s %s\n",
667                                i > 0 ? "" : "Address:", pretty);
668                 }
669
670                 dump_gateways(rtnl, hwdb, "     Gateway: ", 0);
671
672                 sd_network_get_dns(&dns);
673                 if (!strv_isempty(dns))
674                         dump_list("         DNS: ", dns);
675
676                 sd_network_get_domains(&domains);
677                 if (!strv_isempty(domains))
678                         dump_list("      Domain: ", domains);
679
680                 sd_network_get_ntp(&ntp);
681                 if (!strv_isempty(ntp))
682                         dump_list("         NTP: ", ntp);
683
684                 return 0;
685         }
686
687         pager_open_if_enabled();
688
689         if (arg_all) {
690                 _cleanup_rtnl_message_unref_ sd_rtnl_message *req = NULL, *reply = NULL;
691                 _cleanup_free_ LinkInfo *links = NULL;
692                 int c, i;
693
694                 r = sd_rtnl_message_new_link(rtnl, &req, RTM_GETLINK, 0);
695                 if (r < 0)
696                         return rtnl_log_create_error(r);
697
698                 r = sd_rtnl_message_request_dump(req, true);
699                 if (r < 0)
700                         return rtnl_log_create_error(r);
701
702                 r = sd_rtnl_call(rtnl, req, 0, &reply);
703                 if (r < 0)
704                         return log_error_errno(r, "Failed to enumerate links: %m");
705
706                 c = decode_and_sort_links(reply, &links);
707                 if (c < 0)
708                         return rtnl_log_parse_error(c);
709
710                 for (i = 0; i < c; i++) {
711                         if (i > 0)
712                                 fputc('\n', stdout);
713
714                         link_status_one(rtnl, udev, hwdb, links[i].name);
715                 }
716         } else {
717                 STRV_FOREACH(name, args + 1) {
718                         if (name != args+1)
719                                 fputc('\n', stdout);
720
721                         link_status_one(rtnl, udev, hwdb, *name);
722                 }
723         }
724
725         return 0;
726 }
727
728 static void help(void) {
729         printf("%s [OPTIONS...]\n\n"
730                "Query and control the networking subsystem.\n\n"
731                "  -h --help             Show this help\n"
732                "     --version          Show package version\n"
733                "     --no-pager         Do not pipe output into a pager\n"
734                "     --no-legend        Do not show the headers and footers\n"
735                "  -a --all              Show status for all links\n\n"
736                "Commands:\n"
737                "  list                  List links\n"
738                "  status LINK           Show link status\n"
739                , program_invocation_short_name);
740 }
741
742 static int parse_argv(int argc, char *argv[]) {
743
744         enum {
745                 ARG_VERSION = 0x100,
746                 ARG_NO_PAGER,
747                 ARG_NO_LEGEND,
748         };
749
750         static const struct option options[] = {
751                 { "help",      no_argument,       NULL, 'h'           },
752                 { "version",   no_argument,       NULL, ARG_VERSION   },
753                 { "no-pager",  no_argument,       NULL, ARG_NO_PAGER  },
754                 { "no-legend", no_argument,       NULL, ARG_NO_LEGEND },
755                 { "all",       no_argument,       NULL, 'a'           },
756                 {}
757         };
758
759         int c;
760
761         assert(argc >= 0);
762         assert(argv);
763
764         while ((c = getopt_long(argc, argv, "ha", options, NULL)) >= 0) {
765
766                 switch (c) {
767
768                 case 'h':
769                         help();
770                         return 0;
771
772                 case ARG_VERSION:
773                         puts(PACKAGE_STRING);
774                         puts(SYSTEMD_FEATURES);
775                         return 0;
776
777                 case ARG_NO_PAGER:
778                         arg_no_pager = true;
779                         break;
780
781                 case ARG_NO_LEGEND:
782                         arg_legend = false;
783                         break;
784
785                 case 'a':
786                         arg_all = true;
787                         break;
788
789                 case '?':
790                         return -EINVAL;
791
792                 default:
793                         assert_not_reached("Unhandled option");
794                 }
795         }
796
797         return 1;
798 }
799
800 static int networkctl_main(int argc, char *argv[]) {
801
802         static const struct {
803                 const char* verb;
804                 const enum {
805                         MORE,
806                         LESS,
807                         EQUAL
808                 } argc_cmp;
809                 const int argc;
810                 int (* const dispatch)(char **args, unsigned n);
811         } verbs[] = {
812                 { "list",   LESS, 1, list_links  },
813                 { "status", MORE, 1, link_status },
814         };
815
816         int left;
817         unsigned i;
818
819         assert(argc >= 0);
820         assert(argv);
821
822         left = argc - optind;
823
824         if (left <= 0)
825                 /* Special rule: no arguments means "list" */
826                 i = 0;
827         else {
828                 if (streq(argv[optind], "help")) {
829                         help();
830                         return 0;
831                 }
832
833                 for (i = 0; i < ELEMENTSOF(verbs); i++)
834                         if (streq(argv[optind], verbs[i].verb))
835                                 break;
836
837                 if (i >= ELEMENTSOF(verbs)) {
838                         log_error("Unknown operation %s", argv[optind]);
839                         return -EINVAL;
840                 }
841         }
842
843         switch (verbs[i].argc_cmp) {
844
845         case EQUAL:
846                 if (left != verbs[i].argc) {
847                         log_error("Invalid number of arguments.");
848                         return -EINVAL;
849                 }
850
851                 break;
852
853         case MORE:
854                 if (left < verbs[i].argc) {
855                         log_error("Too few arguments.");
856                         return -EINVAL;
857                 }
858
859                 break;
860
861         case LESS:
862                 if (left > verbs[i].argc) {
863                         log_error("Too many arguments.");
864                         return -EINVAL;
865                 }
866
867                 break;
868
869         default:
870                 assert_not_reached("Unknown comparison operator.");
871         }
872
873         return verbs[i].dispatch(argv + optind, left);
874 }
875
876 int main(int argc, char* argv[]) {
877         int r;
878
879         log_parse_environment();
880         log_open();
881
882         r = parse_argv(argc, argv);
883         if (r <= 0)
884                 goto finish;
885
886         r = networkctl_main(argc, argv);
887
888 finish:
889         pager_close();
890
891         return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS;
892 }