]>
Commit | Line | Data |
---|---|---|
71bb428f | 1 | /* |
e64d5256 | 2 | * Copyright (C) 2017-2018 Netronome Systems, Inc. |
71bb428f JK |
3 | * |
4 | * This software is dual licensed under the GNU General License Version 2, | |
5 | * June 1991 as shown in the file COPYING in the top-level directory of this | |
6 | * source tree or the BSD 2-Clause License provided below. You have the | |
7 | * option to license this software under the complete terms of either license. | |
8 | * | |
9 | * The BSD 2-Clause License: | |
10 | * | |
11 | * Redistribution and use in source and binary forms, with or | |
12 | * without modification, are permitted provided that the following | |
13 | * conditions are met: | |
14 | * | |
15 | * 1. Redistributions of source code must retain the above | |
16 | * copyright notice, this list of conditions and the following | |
17 | * disclaimer. | |
18 | * | |
19 | * 2. Redistributions in binary form must reproduce the above | |
20 | * copyright notice, this list of conditions and the following | |
21 | * disclaimer in the documentation and/or other materials | |
22 | * provided with the distribution. | |
23 | * | |
24 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, | |
25 | * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF | |
26 | * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND | |
27 | * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS | |
28 | * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN | |
29 | * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN | |
30 | * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE | |
31 | * SOFTWARE. | |
32 | */ | |
33 | ||
e64d5256 | 34 | #include <ctype.h> |
71bb428f | 35 | #include <errno.h> |
e6593596 | 36 | #include <fcntl.h> |
4990f1f4 | 37 | #include <fts.h> |
71bb428f | 38 | #include <libgen.h> |
4990f1f4 | 39 | #include <mntent.h> |
71bb428f JK |
40 | #include <stdbool.h> |
41 | #include <stdio.h> | |
42 | #include <stdlib.h> | |
43 | #include <string.h> | |
44 | #include <unistd.h> | |
45 | #include <linux/limits.h> | |
46 | #include <linux/magic.h> | |
52262210 | 47 | #include <net/if.h> |
3fc27b71 | 48 | #include <sys/mount.h> |
52262210 | 49 | #include <sys/stat.h> |
71bb428f JK |
50 | #include <sys/types.h> |
51 | #include <sys/vfs.h> | |
52 | ||
53 | #include <bpf.h> | |
54 | ||
55 | #include "main.h" | |
56 | ||
20d5de51 JB |
57 | #ifndef BPF_FS_MAGIC |
58 | #define BPF_FS_MAGIC 0xcafe4a11 | |
59 | #endif | |
60 | ||
0b1c27db QM |
61 | void p_err(const char *fmt, ...) |
62 | { | |
63 | va_list ap; | |
64 | ||
65 | va_start(ap, fmt); | |
66 | if (json_output) { | |
67 | jsonw_start_object(json_wtr); | |
68 | jsonw_name(json_wtr, "error"); | |
69 | jsonw_vprintf_enquote(json_wtr, fmt, ap); | |
70 | jsonw_end_object(json_wtr); | |
71 | } else { | |
72 | fprintf(stderr, "Error: "); | |
73 | vfprintf(stderr, fmt, ap); | |
74 | fprintf(stderr, "\n"); | |
75 | } | |
76 | va_end(ap); | |
77 | } | |
78 | ||
79 | void p_info(const char *fmt, ...) | |
80 | { | |
81 | va_list ap; | |
82 | ||
83 | if (json_output) | |
84 | return; | |
85 | ||
86 | va_start(ap, fmt); | |
87 | vfprintf(stderr, fmt, ap); | |
88 | fprintf(stderr, "\n"); | |
89 | va_end(ap); | |
90 | } | |
91 | ||
71bb428f JK |
92 | static bool is_bpffs(char *path) |
93 | { | |
94 | struct statfs st_fs; | |
95 | ||
96 | if (statfs(path, &st_fs) < 0) | |
97 | return false; | |
98 | ||
99 | return (unsigned long)st_fs.f_type == BPF_FS_MAGIC; | |
100 | } | |
101 | ||
3fc27b71 QM |
102 | static int mnt_bpffs(const char *target, char *buff, size_t bufflen) |
103 | { | |
104 | bool bind_done = false; | |
105 | ||
106 | while (mount("", target, "none", MS_PRIVATE | MS_REC, NULL)) { | |
107 | if (errno != EINVAL || bind_done) { | |
108 | snprintf(buff, bufflen, | |
109 | "mount --make-private %s failed: %s", | |
110 | target, strerror(errno)); | |
111 | return -1; | |
112 | } | |
113 | ||
114 | if (mount(target, target, "none", MS_BIND, NULL)) { | |
115 | snprintf(buff, bufflen, | |
116 | "mount --bind %s %s failed: %s", | |
117 | target, target, strerror(errno)); | |
118 | return -1; | |
119 | } | |
120 | ||
121 | bind_done = true; | |
122 | } | |
123 | ||
124 | if (mount("bpf", target, "bpf", 0, "mode=0700")) { | |
125 | snprintf(buff, bufflen, "mount -t bpf bpf %s failed: %s", | |
126 | target, strerror(errno)); | |
127 | return -1; | |
128 | } | |
129 | ||
130 | return 0; | |
131 | } | |
132 | ||
f120919f | 133 | int open_obj_pinned(char *path, bool quiet) |
71bb428f | 134 | { |
71bb428f JK |
135 | int fd; |
136 | ||
137 | fd = bpf_obj_get(path); | |
138 | if (fd < 0) { | |
f120919f QM |
139 | if (!quiet) |
140 | p_err("bpf obj get (%s): %s", path, | |
141 | errno == EACCES && !is_bpffs(dirname(path)) ? | |
142 | "directory not in bpf file system (bpffs)" : | |
143 | strerror(errno)); | |
71bb428f JK |
144 | return -1; |
145 | } | |
146 | ||
18527196 PB |
147 | return fd; |
148 | } | |
149 | ||
150 | int open_obj_pinned_any(char *path, enum bpf_obj_type exp_type) | |
151 | { | |
152 | enum bpf_obj_type type; | |
153 | int fd; | |
154 | ||
f120919f | 155 | fd = open_obj_pinned(path, false); |
18527196 PB |
156 | if (fd < 0) |
157 | return -1; | |
158 | ||
71bb428f JK |
159 | type = get_fd_type(fd); |
160 | if (type < 0) { | |
161 | close(fd); | |
162 | return type; | |
163 | } | |
164 | if (type != exp_type) { | |
9a5ab8bf | 165 | p_err("incorrect object type: %s", get_fd_type_name(type)); |
71bb428f JK |
166 | close(fd); |
167 | return -1; | |
168 | } | |
169 | ||
170 | return fd; | |
171 | } | |
172 | ||
49a086c2 | 173 | int do_pin_fd(int fd, const char *name) |
71bb428f | 174 | { |
3fc27b71 | 175 | char err_str[ERR_MAX_LEN]; |
3fc27b71 QM |
176 | char *file; |
177 | char *dir; | |
49a086c2 RG |
178 | int err = 0; |
179 | ||
180 | err = bpf_obj_pin(fd, name); | |
181 | if (!err) | |
182 | goto out; | |
183 | ||
184 | file = malloc(strlen(name) + 1); | |
185 | strcpy(file, name); | |
186 | dir = dirname(file); | |
187 | ||
188 | if (errno != EPERM || is_bpffs(dir)) { | |
189 | p_err("can't pin the object (%s): %s", name, strerror(errno)); | |
190 | goto out_free; | |
191 | } | |
192 | ||
193 | /* Attempt to mount bpffs, then retry pinning. */ | |
194 | err = mnt_bpffs(dir, err_str, ERR_MAX_LEN); | |
195 | if (!err) { | |
196 | err = bpf_obj_pin(fd, name); | |
197 | if (err) | |
198 | p_err("can't pin the object (%s): %s", name, | |
199 | strerror(errno)); | |
200 | } else { | |
201 | err_str[ERR_MAX_LEN - 1] = '\0'; | |
202 | p_err("can't mount BPF file system to pin the object (%s): %s", | |
203 | name, err_str); | |
204 | } | |
205 | ||
206 | out_free: | |
207 | free(file); | |
208 | out: | |
209 | return err; | |
210 | } | |
211 | ||
212 | int do_pin_any(int argc, char **argv, int (*get_fd_by_id)(__u32)) | |
213 | { | |
214 | unsigned int id; | |
215 | char *endptr; | |
71bb428f JK |
216 | int err; |
217 | int fd; | |
218 | ||
759b94a0 TS |
219 | if (argc < 3) { |
220 | p_err("too few arguments, id ID and FILE path is required"); | |
221 | return -1; | |
222 | } else if (argc > 3) { | |
223 | p_err("too many arguments"); | |
224 | return -1; | |
225 | } | |
226 | ||
71bb428f | 227 | if (!is_prefix(*argv, "id")) { |
9a5ab8bf | 228 | p_err("expected 'id' got %s", *argv); |
71bb428f JK |
229 | return -1; |
230 | } | |
231 | NEXT_ARG(); | |
232 | ||
233 | id = strtoul(*argv, &endptr, 0); | |
234 | if (*endptr) { | |
9a5ab8bf | 235 | p_err("can't parse %s as ID", *argv); |
71bb428f JK |
236 | return -1; |
237 | } | |
238 | NEXT_ARG(); | |
239 | ||
71bb428f JK |
240 | fd = get_fd_by_id(id); |
241 | if (fd < 0) { | |
9a5ab8bf | 242 | p_err("can't get prog by id (%u): %s", id, strerror(errno)); |
71bb428f JK |
243 | return -1; |
244 | } | |
245 | ||
49a086c2 | 246 | err = do_pin_fd(fd, *argv); |
71bb428f | 247 | |
3fc27b71 QM |
248 | close(fd); |
249 | return err; | |
71bb428f JK |
250 | } |
251 | ||
252 | const char *get_fd_type_name(enum bpf_obj_type type) | |
253 | { | |
254 | static const char * const names[] = { | |
255 | [BPF_OBJ_UNKNOWN] = "unknown", | |
256 | [BPF_OBJ_PROG] = "prog", | |
257 | [BPF_OBJ_MAP] = "map", | |
258 | }; | |
259 | ||
260 | if (type < 0 || type >= ARRAY_SIZE(names) || !names[type]) | |
261 | return names[BPF_OBJ_UNKNOWN]; | |
262 | ||
263 | return names[type]; | |
264 | } | |
265 | ||
266 | int get_fd_type(int fd) | |
267 | { | |
268 | char path[PATH_MAX]; | |
269 | char buf[512]; | |
270 | ssize_t n; | |
271 | ||
272 | snprintf(path, sizeof(path), "/proc/%d/fd/%d", getpid(), fd); | |
273 | ||
274 | n = readlink(path, buf, sizeof(buf)); | |
275 | if (n < 0) { | |
9a5ab8bf | 276 | p_err("can't read link type: %s", strerror(errno)); |
71bb428f JK |
277 | return -1; |
278 | } | |
279 | if (n == sizeof(path)) { | |
9a5ab8bf | 280 | p_err("can't read link type: path too long!"); |
71bb428f JK |
281 | return -1; |
282 | } | |
283 | ||
284 | if (strstr(buf, "bpf-map")) | |
285 | return BPF_OBJ_MAP; | |
286 | else if (strstr(buf, "bpf-prog")) | |
287 | return BPF_OBJ_PROG; | |
288 | ||
289 | return BPF_OBJ_UNKNOWN; | |
290 | } | |
291 | ||
292 | char *get_fdinfo(int fd, const char *key) | |
293 | { | |
294 | char path[PATH_MAX]; | |
295 | char *line = NULL; | |
296 | size_t line_n = 0; | |
297 | ssize_t n; | |
298 | FILE *fdi; | |
299 | ||
300 | snprintf(path, sizeof(path), "/proc/%d/fdinfo/%d", getpid(), fd); | |
301 | ||
302 | fdi = fopen(path, "r"); | |
303 | if (!fdi) { | |
9a5ab8bf | 304 | p_err("can't open fdinfo: %s", strerror(errno)); |
71bb428f JK |
305 | return NULL; |
306 | } | |
307 | ||
53909030 | 308 | while ((n = getline(&line, &line_n, fdi)) > 0) { |
71bb428f JK |
309 | char *value; |
310 | int len; | |
311 | ||
312 | if (!strstr(line, key)) | |
313 | continue; | |
314 | ||
315 | fclose(fdi); | |
316 | ||
317 | value = strchr(line, '\t'); | |
318 | if (!value || !value[1]) { | |
9a5ab8bf | 319 | p_err("malformed fdinfo!?"); |
71bb428f JK |
320 | free(line); |
321 | return NULL; | |
322 | } | |
323 | value++; | |
324 | ||
325 | len = strlen(value); | |
326 | memmove(line, value, len); | |
327 | line[len - 1] = '\0'; | |
328 | ||
329 | return line; | |
330 | } | |
331 | ||
9a5ab8bf | 332 | p_err("key '%s' not found in fdinfo", key); |
71bb428f JK |
333 | free(line); |
334 | fclose(fdi); | |
335 | return NULL; | |
336 | } | |
f05e2c32 | 337 | |
f412eed9 JK |
338 | void print_data_json(uint8_t *data, size_t len) |
339 | { | |
340 | unsigned int i; | |
341 | ||
342 | jsonw_start_array(json_wtr); | |
343 | for (i = 0; i < len; i++) | |
344 | jsonw_printf(json_wtr, "%d", data[i]); | |
345 | jsonw_end_array(json_wtr); | |
346 | } | |
347 | ||
f05e2c32 QM |
348 | void print_hex_data_json(uint8_t *data, size_t len) |
349 | { | |
350 | unsigned int i; | |
351 | ||
352 | jsonw_start_array(json_wtr); | |
353 | for (i = 0; i < len; i++) | |
354 | jsonw_printf(json_wtr, "\"0x%02hhx\"", data[i]); | |
355 | jsonw_end_array(json_wtr); | |
356 | } | |
4990f1f4 PB |
357 | |
358 | int build_pinned_obj_table(struct pinned_obj_table *tab, | |
359 | enum bpf_obj_type type) | |
360 | { | |
361 | struct bpf_prog_info pinned_info = {}; | |
362 | struct pinned_obj *obj_node = NULL; | |
363 | __u32 len = sizeof(pinned_info); | |
364 | struct mntent *mntent = NULL; | |
365 | enum bpf_obj_type objtype; | |
366 | FILE *mntfile = NULL; | |
367 | FTSENT *ftse = NULL; | |
368 | FTS *fts = NULL; | |
369 | int fd, err; | |
370 | ||
371 | mntfile = setmntent("/proc/mounts", "r"); | |
372 | if (!mntfile) | |
373 | return -1; | |
374 | ||
375 | while ((mntent = getmntent(mntfile))) { | |
376 | char *path[] = { mntent->mnt_dir, NULL }; | |
377 | ||
378 | if (strncmp(mntent->mnt_type, "bpf", 3) != 0) | |
379 | continue; | |
380 | ||
381 | fts = fts_open(path, 0, NULL); | |
382 | if (!fts) | |
383 | continue; | |
384 | ||
385 | while ((ftse = fts_read(fts))) { | |
386 | if (!(ftse->fts_info & FTS_F)) | |
387 | continue; | |
f120919f | 388 | fd = open_obj_pinned(ftse->fts_path, true); |
4990f1f4 PB |
389 | if (fd < 0) |
390 | continue; | |
391 | ||
392 | objtype = get_fd_type(fd); | |
393 | if (objtype != type) { | |
394 | close(fd); | |
395 | continue; | |
396 | } | |
397 | memset(&pinned_info, 0, sizeof(pinned_info)); | |
398 | err = bpf_obj_get_info_by_fd(fd, &pinned_info, &len); | |
399 | if (err) { | |
400 | close(fd); | |
401 | continue; | |
402 | } | |
403 | ||
404 | obj_node = malloc(sizeof(*obj_node)); | |
405 | if (!obj_node) { | |
406 | close(fd); | |
407 | fts_close(fts); | |
408 | fclose(mntfile); | |
409 | return -1; | |
410 | } | |
411 | ||
412 | memset(obj_node, 0, sizeof(*obj_node)); | |
413 | obj_node->id = pinned_info.id; | |
414 | obj_node->path = strdup(ftse->fts_path); | |
415 | hash_add(tab->table, &obj_node->hash, obj_node->id); | |
416 | ||
417 | close(fd); | |
418 | } | |
419 | fts_close(fts); | |
420 | } | |
421 | fclose(mntfile); | |
422 | return 0; | |
423 | } | |
424 | ||
425 | void delete_pinned_obj_table(struct pinned_obj_table *tab) | |
426 | { | |
427 | struct pinned_obj *obj; | |
428 | struct hlist_node *tmp; | |
429 | unsigned int bkt; | |
430 | ||
431 | hash_for_each_safe(tab->table, bkt, tmp, obj, hash) { | |
432 | hash_del(&obj->hash); | |
433 | free(obj->path); | |
434 | free(obj); | |
435 | } | |
436 | } | |
52262210 | 437 | |
f412eed9 JK |
438 | unsigned int get_page_size(void) |
439 | { | |
440 | static int result; | |
441 | ||
442 | if (!result) | |
443 | result = getpagesize(); | |
444 | return result; | |
445 | } | |
446 | ||
e64d5256 JK |
447 | unsigned int get_possible_cpus(void) |
448 | { | |
449 | static unsigned int result; | |
450 | char buf[128]; | |
451 | long int n; | |
452 | char *ptr; | |
453 | int fd; | |
454 | ||
455 | if (result) | |
456 | return result; | |
457 | ||
458 | fd = open("/sys/devices/system/cpu/possible", O_RDONLY); | |
459 | if (fd < 0) { | |
460 | p_err("can't open sysfs possible cpus"); | |
461 | exit(-1); | |
462 | } | |
463 | ||
464 | n = read(fd, buf, sizeof(buf)); | |
465 | if (n < 2) { | |
466 | p_err("can't read sysfs possible cpus"); | |
467 | exit(-1); | |
468 | } | |
469 | close(fd); | |
470 | ||
471 | if (n == sizeof(buf)) { | |
472 | p_err("read sysfs possible cpus overflow"); | |
473 | exit(-1); | |
474 | } | |
475 | ||
476 | ptr = buf; | |
477 | n = 0; | |
478 | while (*ptr && *ptr != '\n') { | |
479 | unsigned int a, b; | |
480 | ||
481 | if (sscanf(ptr, "%u-%u", &a, &b) == 2) { | |
482 | n += b - a + 1; | |
483 | ||
484 | ptr = strchr(ptr, '-') + 1; | |
485 | } else if (sscanf(ptr, "%u", &a) == 1) { | |
486 | n++; | |
487 | } else { | |
488 | assert(0); | |
489 | } | |
490 | ||
491 | while (isdigit(*ptr)) | |
492 | ptr++; | |
493 | if (*ptr == ',') | |
494 | ptr++; | |
495 | } | |
496 | ||
497 | result = n; | |
498 | ||
499 | return result; | |
500 | } | |
501 | ||
52262210 JK |
502 | static char * |
503 | ifindex_to_name_ns(__u32 ifindex, __u32 ns_dev, __u32 ns_ino, char *buf) | |
504 | { | |
505 | struct stat st; | |
506 | int err; | |
507 | ||
508 | err = stat("/proc/self/ns/net", &st); | |
509 | if (err) { | |
510 | p_err("Can't stat /proc/self: %s", strerror(errno)); | |
511 | return NULL; | |
512 | } | |
513 | ||
514 | if (st.st_dev != ns_dev || st.st_ino != ns_ino) | |
515 | return NULL; | |
516 | ||
517 | return if_indextoname(ifindex, buf); | |
518 | } | |
519 | ||
e6593596 JW |
520 | static int read_sysfs_hex_int(char *path) |
521 | { | |
522 | char vendor_id_buf[8]; | |
523 | int len; | |
524 | int fd; | |
525 | ||
526 | fd = open(path, O_RDONLY); | |
527 | if (fd < 0) { | |
528 | p_err("Can't open %s: %s", path, strerror(errno)); | |
529 | return -1; | |
530 | } | |
531 | ||
532 | len = read(fd, vendor_id_buf, sizeof(vendor_id_buf)); | |
533 | close(fd); | |
534 | if (len < 0) { | |
535 | p_err("Can't read %s: %s", path, strerror(errno)); | |
536 | return -1; | |
537 | } | |
538 | if (len >= (int)sizeof(vendor_id_buf)) { | |
539 | p_err("Value in %s too long", path); | |
540 | return -1; | |
541 | } | |
542 | ||
543 | vendor_id_buf[len] = 0; | |
544 | ||
545 | return strtol(vendor_id_buf, NULL, 0); | |
546 | } | |
547 | ||
548 | static int read_sysfs_netdev_hex_int(char *devname, const char *entry_name) | |
549 | { | |
550 | char full_path[64]; | |
551 | ||
552 | snprintf(full_path, sizeof(full_path), "/sys/class/net/%s/device/%s", | |
553 | devname, entry_name); | |
554 | ||
555 | return read_sysfs_hex_int(full_path); | |
556 | } | |
557 | ||
3ddeac67 JK |
558 | const char * |
559 | ifindex_to_bfd_params(__u32 ifindex, __u64 ns_dev, __u64 ns_ino, | |
560 | const char **opt) | |
e6593596 JW |
561 | { |
562 | char devname[IF_NAMESIZE]; | |
563 | int vendor_id; | |
564 | int device_id; | |
565 | ||
566 | if (!ifindex_to_name_ns(ifindex, ns_dev, ns_ino, devname)) { | |
567 | p_err("Can't get net device name for ifindex %d: %s", ifindex, | |
568 | strerror(errno)); | |
569 | return NULL; | |
570 | } | |
571 | ||
572 | vendor_id = read_sysfs_netdev_hex_int(devname, "vendor"); | |
573 | if (vendor_id < 0) { | |
574 | p_err("Can't get device vendor id for %s", devname); | |
575 | return NULL; | |
576 | } | |
577 | ||
578 | switch (vendor_id) { | |
579 | case 0x19ee: | |
580 | device_id = read_sysfs_netdev_hex_int(devname, "device"); | |
581 | if (device_id != 0x4000 && | |
582 | device_id != 0x6000 && | |
583 | device_id != 0x6003) | |
584 | p_info("Unknown NFP device ID, assuming it is NFP-6xxx arch"); | |
3ddeac67 | 585 | *opt = "ctx4"; |
e6593596 JW |
586 | return "NFP-6xxx"; |
587 | default: | |
588 | p_err("Can't get bfd arch name for device vendor id 0x%04x", | |
589 | vendor_id); | |
590 | return NULL; | |
591 | } | |
592 | } | |
593 | ||
52262210 JK |
594 | void print_dev_plain(__u32 ifindex, __u64 ns_dev, __u64 ns_inode) |
595 | { | |
596 | char name[IF_NAMESIZE]; | |
597 | ||
598 | if (!ifindex) | |
599 | return; | |
600 | ||
601 | printf(" dev "); | |
602 | if (ifindex_to_name_ns(ifindex, ns_dev, ns_inode, name)) | |
603 | printf("%s", name); | |
604 | else | |
605 | printf("ifindex %u ns_dev %llu ns_ino %llu", | |
606 | ifindex, ns_dev, ns_inode); | |
607 | } | |
608 | ||
609 | void print_dev_json(__u32 ifindex, __u64 ns_dev, __u64 ns_inode) | |
610 | { | |
611 | char name[IF_NAMESIZE]; | |
612 | ||
613 | if (!ifindex) | |
614 | return; | |
615 | ||
616 | jsonw_name(json_wtr, "dev"); | |
617 | jsonw_start_object(json_wtr); | |
618 | jsonw_uint_field(json_wtr, "ifindex", ifindex); | |
619 | jsonw_uint_field(json_wtr, "ns_dev", ns_dev); | |
620 | jsonw_uint_field(json_wtr, "ns_inode", ns_inode); | |
621 | if (ifindex_to_name_ns(ifindex, ns_dev, ns_inode, name)) | |
622 | jsonw_string_field(json_wtr, "ifname", name); | |
623 | jsonw_end_object(json_wtr); | |
624 | } | |
0b592b5a JK |
625 | |
626 | int parse_u32_arg(int *argc, char ***argv, __u32 *val, const char *what) | |
627 | { | |
628 | char *endptr; | |
629 | ||
630 | NEXT_ARGP(); | |
631 | ||
632 | if (*val) { | |
633 | p_err("%s already specified", what); | |
634 | return -1; | |
635 | } | |
636 | ||
637 | *val = strtoul(**argv, &endptr, 0); | |
638 | if (*endptr) { | |
639 | p_err("can't parse %s as %s", **argv, what); | |
640 | return -1; | |
641 | } | |
642 | NEXT_ARGP(); | |
643 | ||
644 | return 0; | |
645 | } |