| /* |
| * firewire-phy-command.c - send PHY packets |
| * |
| * Copyright 2011 Clemens Ladisch <clemens@ladisch.de> |
| * |
| * licensed under the terms of the GNU General Public License, version 2 |
| */ |
| |
| #define _GNU_SOURCE |
| #include <stdio.h> |
| #include <stdlib.h> |
| #include <stdbool.h> |
| #include <stdint.h> |
| #include <string.h> |
| #include <ctype.h> |
| #include <dirent.h> |
| #include <errno.h> |
| #include <getopt.h> |
| #include <fcntl.h> |
| #include <poll.h> |
| #include <unistd.h> |
| #include <sys/ioctl.h> |
| #include <linux/firewire-cdev.h> |
| #include <linux/firewire-constants.h> |
| |
| #include "config.h" |
| |
| #ifndef FW_CDEV_IOC_SEND_PHY_PACKET |
| #error kernel headers too old |
| #endif |
| |
| #define ARRAY_SIZE(a) (sizeof(a) / sizeof *(a)) |
| |
| #define ptr_to_u64(p) ((uintptr_t)(p)) |
| |
| typedef __u8 u8; |
| typedef __u32 u32; |
| typedef __u64 u64; |
| |
| struct node { |
| struct node *next; |
| char *name; |
| int fd; |
| u32 card; |
| u32 id; |
| u32 generation; |
| bool is_local; |
| }; |
| |
| static struct node *nodes; |
| static struct node *local_node; |
| static u32 param_node_id; |
| static u32 ping_time; |
| static u32 self_ids[3]; |
| |
| static void help(void) |
| { |
| fputs("Usage: firewire-phy-command [options] command [parameters]\n" |
| "Commands:\n" |
| " config [root <node>] [gapcount <value>]\n" |
| " ping <node>\n" |
| " read <node> [<page> <port>] <register>\n" |
| " nop|disable|suspend|clear|enable|resume <node> <port>\n" |
| " resume\n" |
| " linkon <node>\n" |
| " reset\n" |
| "Options:\n" |
| " -b, --bus=node bus to send packet on\n" |
| " -h, --help show this message and exit\n" |
| " -V, --version show version number and exit\n" |
| "\n" |
| "Report bugs to <" PACKAGE_BUGREPORT ">.\n" |
| PACKAGE_NAME " home page: <" PACKAGE_URL ">.\n", |
| stderr); |
| } |
| |
| static int fw_filter(const struct dirent *dirent) |
| { |
| unsigned int i; |
| |
| if (dirent->d_name[0] != 'f' || |
| dirent->d_name[1] != 'w') |
| return false; |
| i = 2; |
| do { |
| if (!isdigit(dirent->d_name[i])) |
| return false; |
| } while (dirent->d_name[++i]); |
| return true; |
| } |
| |
| static void open_all_nodes(void) |
| { |
| struct dirent **ents; |
| int count, i; |
| struct node *node; |
| struct fw_cdev_get_info get_info; |
| struct fw_cdev_event_bus_reset bus_reset; |
| bool eacces = false; |
| |
| count = scandir("/dev", &ents, fw_filter, versionsort); |
| if (count < 0) { |
| perror("cannot read /dev"); |
| exit(EXIT_FAILURE); |
| } |
| |
| for (i = count - 1; i >= 0; --i) { |
| node = malloc(sizeof(*node)); |
| if (!node) { |
| fputs("out of memory\n", stderr); |
| exit(EXIT_FAILURE); |
| } |
| |
| if (asprintf(&node->name, "/dev/%s", ents[i]->d_name) < 0) { |
| perror("asprintf failed"); |
| exit(EXIT_FAILURE); |
| } |
| node->fd = open(node->name, O_RDWR); |
| if (node->fd == -1) { |
| if (errno == EACCES) |
| eacces = true; |
| free(node->name); |
| free(node); |
| continue; |
| } |
| |
| get_info.version = 4; |
| get_info.rom_length = 0; |
| get_info.rom = 0; |
| get_info.bus_reset = ptr_to_u64(&bus_reset); |
| get_info.bus_reset_closure = 0; |
| if (ioctl(node->fd, FW_CDEV_IOC_GET_INFO, &get_info) < 0) { |
| close(node->fd); |
| free(node->name); |
| free(node); |
| continue; |
| } |
| node->card = get_info.card; |
| node->id = bus_reset.node_id; |
| node->generation = bus_reset.generation; |
| node->is_local = bus_reset.node_id == bus_reset.local_node_id; |
| |
| node->next = nodes; |
| nodes = node; |
| free(ents[i]); |
| } |
| free(ents); |
| |
| if (!nodes) { |
| if (eacces) { |
| errno = EACCES; |
| perror("/dev/fw*"); |
| } else { |
| fputs("no fw devices found\n", stderr); |
| } |
| exit(EXIT_FAILURE); |
| } |
| } |
| |
| static void close_all_nodes(void) |
| { |
| struct node *node, *next; |
| |
| for (node = nodes; node; node = next) { |
| close(node->fd); |
| free(node->name); |
| next = node->next; |
| free(node); |
| } |
| } |
| |
| static void find_local_node(const char *bus_name) |
| { |
| int bus_card; |
| char *endptr; |
| int fd; |
| struct fw_cdev_get_info get_info; |
| struct node *node; |
| |
| if (bus_name) { |
| bus_card = strtol(bus_name, &endptr, 0); |
| if (!*endptr) { |
| if (bus_card < 0) { |
| fputs("invalid bus number\n", stderr); |
| exit(EXIT_FAILURE); |
| } |
| } else { |
| bus_card = -1; |
| for (node = nodes; node; node = node->next) |
| if (!strcmp(node->name, bus_name)) { |
| bus_card = node->card; |
| break; |
| } |
| if (bus_card < 0) { |
| fd = open(bus_name, O_RDWR); |
| if (fd == -1) { |
| perror(bus_name); |
| exit(EXIT_FAILURE); |
| } |
| get_info.version = 4; |
| get_info.rom_length = 0; |
| get_info.rom = 0; |
| get_info.bus_reset = 0; |
| get_info.bus_reset_closure = 0; |
| if (ioctl(fd, FW_CDEV_IOC_GET_INFO, &get_info) < 0) { |
| fprintf(stderr, "%s: not a fw device\n", bus_name); |
| exit(EXIT_FAILURE); |
| } |
| close(fd); |
| bus_card = get_info.card; |
| } |
| } |
| } else { |
| bus_card = -1; |
| } |
| |
| for (node = nodes; node; node = node->next) { |
| if (node->is_local && |
| (bus_card == -1 || node->card == bus_card)) { |
| local_node = node; |
| return; |
| } |
| } |
| |
| if (bus_card == -1) |
| fputs("local node not found\n", stderr); |
| else |
| fprintf(stderr, "local node for card %d not found\n", bus_card); |
| exit(EXIT_FAILURE); |
| } |
| |
| static void find_param_node(const char *name) |
| { |
| int id; |
| char *endptr; |
| struct node *node; |
| int fd; |
| struct fw_cdev_get_info get_info; |
| struct fw_cdev_event_bus_reset bus_reset; |
| char card_str[16]; |
| |
| id = strtol(name, &endptr, 0); |
| if (!*endptr) { |
| if (id < 0 || id > 63) { |
| fputs("invalid node id\n", stderr); |
| exit(EXIT_FAILURE); |
| } |
| param_node_id = id; |
| return; |
| } |
| |
| for (node = nodes; node; node = node->next) |
| if (!strcmp(node->name, name)) { |
| param_node_id = node->id & 0x3f; |
| sprintf(card_str, "%u", node->card); |
| find_local_node(card_str); |
| return; |
| } |
| |
| fd = open(name, O_RDWR); |
| if (fd == -1) { |
| perror(name); |
| exit(EXIT_FAILURE); |
| } |
| get_info.version = 4; |
| get_info.rom_length = 0; |
| get_info.rom = 0; |
| get_info.bus_reset = ptr_to_u64(&bus_reset); |
| get_info.bus_reset_closure = 0; |
| if (ioctl(fd, FW_CDEV_IOC_GET_INFO, &get_info) < 0) { |
| fprintf(stderr, "%s: not a fw device\n", name); |
| exit(EXIT_FAILURE); |
| } |
| close(fd); |
| param_node_id = bus_reset.node_id & 0x3f; |
| sprintf(card_str, "%u", get_info.card); |
| find_local_node(card_str); |
| } |
| |
| static u32 _send_packet(u32 quadlet0, u32 quadlet1, u32 response_mask, u32 response_bits) |
| { |
| struct fw_cdev_receive_phy_packets receive_phy_packets; |
| struct fw_cdev_send_phy_packet send_phy_packet; |
| struct pollfd pollfd; |
| int poll_result; |
| ssize_t bytes; |
| union fw_cdev_event event; |
| bool wait_for_sent = true; |
| bool wait_for_response = response_mask != 0; |
| unsigned int self_id_index = 0; |
| u32 response = 0; |
| |
| if (wait_for_response) { |
| receive_phy_packets.closure = 0; |
| if (ioctl(local_node->fd, FW_CDEV_IOC_RECEIVE_PHY_PACKETS, &receive_phy_packets) < 0) { |
| perror("RECEIVE_PHY_PACKETS ioctl failed"); |
| exit(EXIT_FAILURE); |
| } |
| } |
| |
| send_phy_packet.closure = 0; |
| send_phy_packet.data[0] = quadlet0; |
| send_phy_packet.data[1] = quadlet1; |
| send_phy_packet.generation = local_node->generation; |
| if (ioctl(local_node->fd, FW_CDEV_IOC_SEND_PHY_PACKET, &send_phy_packet) < 0) { |
| perror("SEND_PHY_PACKET ioctl failed"); |
| exit(EXIT_FAILURE); |
| } |
| |
| pollfd.fd = local_node->fd; |
| pollfd.events = POLLIN; |
| while (wait_for_sent || wait_for_response) { |
| poll_result = poll(&pollfd, 1, 100); |
| if (poll_result < 0) { |
| perror("poll failed"); |
| exit(EXIT_FAILURE); |
| } |
| if (poll_result == 0) { |
| fputs("timeout\n", stderr); |
| exit(EXIT_FAILURE); |
| } |
| bytes = read(local_node->fd, &event, sizeof(event)); |
| if (bytes < sizeof(struct fw_cdev_event_common)) { |
| fputs("short read\n", stderr); |
| exit(EXIT_FAILURE); |
| } |
| switch (event.common.type) { |
| case FW_CDEV_EVENT_BUS_RESET: |
| fputs("bus reset\n", stderr); |
| exit(EXIT_FAILURE); |
| case FW_CDEV_EVENT_PHY_PACKET_SENT: |
| wait_for_sent = false; |
| if (event.phy_packet.length >= 4) |
| ping_time = event.phy_packet.data[0]; |
| break; |
| case FW_CDEV_EVENT_PHY_PACKET_RECEIVED: |
| if ((event.phy_packet.data[0] & response_mask) == response_bits) { |
| response = event.phy_packet.data[0]; |
| if ((response & 0xc0000000) == 0x80000000) { |
| self_ids[self_id_index++] = response; |
| if (self_id_index >= ARRAY_SIZE(self_ids) || |
| !(response & 1)) |
| wait_for_response = false; |
| } else { |
| wait_for_response = false; |
| } |
| } |
| break; |
| } |
| } |
| |
| return response; |
| } |
| |
| static u32 send_packet(u32 quadlet, u32 response_mask, u32 response_bits) |
| { |
| return _send_packet(quadlet, ~quadlet, response_mask, response_bits); |
| } |
| |
| static void command_config(char *args[]) |
| { |
| bool new_root = false; |
| int gap_count = -1; |
| char *endptr; |
| u32 packet; |
| |
| if (!args[0]) { |
| fputs("missing configuration parameter\n", stderr); |
| syntax_error: |
| help(); |
| exit(EXIT_FAILURE); |
| } |
| |
| do { |
| if (!strcmp(args[0], "root")) { |
| if (!args[1]) { |
| fputs("no root node specified\n", stderr); |
| goto syntax_error; |
| } |
| find_param_node(args[1]); |
| new_root = true; |
| } else if (!strcmp(args[0], "gapcount")) { |
| if (!args[1]) { |
| fputs("no gap count specified\n", stderr); |
| goto syntax_error; |
| } |
| gap_count = strtol(args[1], &endptr, 0); |
| if (*endptr) { |
| fputs("gap count is not a number\n", stderr); |
| exit(EXIT_FAILURE); |
| } else if (gap_count < 0 || gap_count > 63) { |
| fputs("gap count out of range\n", stderr); |
| exit(EXIT_FAILURE); |
| } |
| } else { |
| fprintf(stderr, "unknown configuration parameter `%s'\n", args[0]); |
| goto syntax_error; |
| } |
| args += 2; |
| } while (args[0]); |
| |
| packet = 0; |
| if (new_root) |
| packet |= (1 << 23) | (param_node_id << 24); |
| if (gap_count >= 0) |
| packet |= (1 << 22) | (gap_count << 16); |
| send_packet(packet, 0, 0); |
| } |
| |
| static void print_port(u32 self_id, unsigned int shift) |
| { |
| static const char *const port[] = { |
| [0] = "", |
| [1] = "-", |
| [2] = "p", |
| [3] = "c", |
| }; |
| |
| fputs(port[(self_id >> shift) & 3], stdout); |
| } |
| |
| static void command_ping(char *args[]) |
| { |
| static const char *const speed[] = { |
| [0] = "S100", |
| [1] = "S200", |
| [2] = "S400", |
| [3] = "beta", |
| }; |
| static const char *const power[] = { |
| [0] = "+0W", |
| [1] = "+15W", |
| [2] = "+30W", |
| [3] = "+45W", |
| [4] = "-3W", |
| [5] = " ?W", |
| [6] = "-3..-6W", |
| [7] = "-3..-10W", |
| }; |
| unsigned int i; |
| |
| if (!args[0]) { |
| fputs("missing destination node\n", stderr); |
| syntax_error: |
| help(); |
| exit(EXIT_FAILURE); |
| } |
| find_param_node(args[0]); |
| |
| if (args[1]) { |
| fprintf(stderr, "unexpected parameter `%s'\n", args[1]); |
| goto syntax_error; |
| } |
| |
| send_packet((0 << 18) | (param_node_id << 24), |
| 0xff000000, |
| (2 << 30) | (param_node_id << 24)); |
| printf("time: %u ticks (%llu ns)", |
| ping_time, (ping_time * 1000000uLL + 12288u) / 24576u); |
| printf(", selfID: phy %u %s gc=%u %s %s%s%s [", |
| (self_ids[0] >> 24) & 0x3f, |
| speed[(self_ids[0] >> 14) & 3], |
| (self_ids[0] >> 16) & 0x3f, |
| power[(self_ids[0] >> 8) & 7], |
| self_ids[0] & (1 << 22) ? "L" : "", |
| self_ids[0] & (1 << 11) ? "c" : "", |
| self_ids[0] & (1 << 1) ? "i" : ""); |
| print_port(self_ids[0], 6); |
| print_port(self_ids[0], 4); |
| print_port(self_ids[0], 2); |
| if (self_ids[0] & 1) { |
| for (i = 1; i < ARRAY_SIZE(self_ids); ++i) { |
| print_port(self_ids[i], 16); |
| print_port(self_ids[i], 14); |
| print_port(self_ids[i], 12); |
| print_port(self_ids[i], 10); |
| print_port(self_ids[i], 8); |
| print_port(self_ids[i], 6); |
| print_port(self_ids[i], 4); |
| print_port(self_ids[i], 2); |
| if (!(self_ids[i] & 1)) |
| break; |
| } |
| } |
| puts("]"); |
| } |
| |
| static void command_read(char *args[]) |
| { |
| unsigned int page, port, reg; |
| char *endptr; |
| u32 packet; |
| u32 response; |
| |
| if (!args[0]) { |
| fputs("missing destination node\n", stderr); |
| syntax_error: |
| help(); |
| exit(EXIT_FAILURE); |
| } |
| find_param_node(args[0]); |
| |
| if (!args[1]) { |
| fputs("missing register number\n", stderr); |
| goto syntax_error; |
| } |
| if (args[2]) { |
| if (!args[3]) { |
| fputs("missing register number\n", stderr); |
| goto syntax_error; |
| } |
| |
| page = strtol(args[1], &endptr, 0); |
| if (*endptr) { |
| fputs("invalid page number\n", stderr); |
| exit(EXIT_FAILURE); |
| } |
| if (page > 7) { |
| fputs("page number out of range\n", stderr); |
| exit(EXIT_FAILURE); |
| } |
| |
| port = strtol(args[2], &endptr, 0); |
| if (*endptr) { |
| fputs("invalid port number\n", stderr); |
| exit(EXIT_FAILURE); |
| } |
| if (port > 15) { |
| fputs("port number out of range\n", stderr); |
| exit(EXIT_FAILURE); |
| } |
| |
| reg = strtol(args[3], &endptr, 0); |
| if (*endptr) { |
| fputs("invalid register number\n", stderr); |
| exit(EXIT_FAILURE); |
| } |
| if (reg < 8 || reg > 15) { |
| fputs("register number out of range\n", stderr); |
| exit(EXIT_FAILURE); |
| } |
| |
| if (args[4]) { |
| fprintf(stderr, "unexpected parameter `%s'\n", args[4]); |
| goto syntax_error; |
| } |
| } else { |
| page = 0; |
| port = 0; |
| |
| reg = strtol(args[1], &endptr, 0); |
| if (*endptr) { |
| fputs("invalid register number\n", stderr); |
| exit(EXIT_FAILURE); |
| } |
| if (reg > 7) { |
| fputs("register number out of range\n", stderr); |
| exit(EXIT_FAILURE); |
| } |
| |
| if (args[2]) { |
| fprintf(stderr, "unexpected parameter `%s'\n", args[2]); |
| goto syntax_error; |
| } |
| } |
| |
| packet = reg < 8 ? 1 << 18 : 5 << 18; |
| packet |= page << 15; |
| packet |= port << 11; |
| packet |= (reg & 7) << 8; |
| packet |= param_node_id << 24; |
| response = send_packet(packet, 0xffffff00, packet | (2 << 18)); |
| printf("value: 0x%02x\n", response & 0xff); |
| } |
| |
| static void command_remote_cmd(char *args[], u32 cmd) |
| { |
| unsigned int port; |
| char *endptr; |
| u32 response; |
| |
| if (!args[0]) { |
| fputs("missing destination node\n", stderr); |
| syntax_error: |
| help(); |
| exit(EXIT_FAILURE); |
| } |
| find_param_node(args[0]); |
| |
| if (!args[1]) { |
| fputs("missing port number\n", stderr); |
| goto syntax_error; |
| } |
| port = strtol(args[1], &endptr, 0); |
| if (*endptr) { |
| fputs("invalid port number\n", stderr); |
| exit(EXIT_FAILURE); |
| } |
| if (port > 15) { |
| fputs("port number out of range\n", stderr); |
| exit(EXIT_FAILURE); |
| } |
| |
| if (args[2]) { |
| fprintf(stderr, "unexpected parameter `%s'\n", args[2]); |
| goto syntax_error; |
| } |
| |
| response = send_packet((0x8 << 18) | cmd | (port << 11) | (param_node_id << 24), |
| 0xff3ff807, |
| (0xa << 18) | cmd | (port << 11) | (param_node_id << 24)); |
| if (!(response & (1 << 3))) |
| puts("command rejected"); |
| else if (!(response & 0x1f0)) |
| fputs("port status: ok\n", stdout); |
| else |
| printf("port status:%s%s%s%s%s\n", |
| response & (1 << 4) ? " disabled" : "", |
| response & (1 << 5) ? " bias" : "", |
| response & (1 << 6) ? " connected" : "", |
| response & (1 << 7) ? " fault" : "", |
| response & (1 << 8) ? " standby_fault" : ""); |
| } |
| |
| static void command_nop(char *args[]) |
| { |
| command_remote_cmd(args, 0); |
| } |
| |
| static void command_disable(char *args[]) |
| { |
| command_remote_cmd(args, 1); |
| } |
| |
| static void command_suspend(char *args[]) |
| { |
| command_remote_cmd(args, 2); |
| } |
| |
| static void command_clear(char *args[]) |
| { |
| command_remote_cmd(args, 4); |
| } |
| |
| static void command_enable(char *args[]) |
| { |
| command_remote_cmd(args, 5); |
| } |
| |
| static void command_resume(char *args[]) |
| { |
| if (args[0]) |
| command_remote_cmd(args, 6); |
| else |
| send_packet((0xf << 18) | (local_node->id << 24), 0, 0); |
| } |
| |
| static void command_standby(char *args[]) |
| { |
| command_remote_cmd(args, 7 | (1 << 15)); |
| } |
| |
| static void command_restore(char *args[]) |
| { |
| command_remote_cmd(args, 7 | (2 << 15)); |
| } |
| |
| static void command_linkon(char *args[]) |
| { |
| if (!args[0]) { |
| fputs("missing destination node\n", stderr); |
| syntax_error: |
| help(); |
| exit(EXIT_FAILURE); |
| } |
| find_param_node(args[0]); |
| |
| if (args[1]) { |
| fprintf(stderr, "unexpected parameter `%s'\n", args[1]); |
| goto syntax_error; |
| } |
| |
| send_packet((1 << 30) | (param_node_id << 24), 0, 0); |
| } |
| |
| static void command_versaphy(char *args[]) |
| { |
| u32 q0, q1; |
| char *endptr; |
| |
| if (!args[0] || !args[1]) { |
| fputs("missing data\n", stderr); |
| syntax_error: |
| help(); |
| exit(EXIT_FAILURE); |
| } |
| |
| q0 = strtol(args[0], &endptr, 16); |
| if (*endptr) { |
| fputs("invalid data quadlet\n", stderr); |
| exit(EXIT_FAILURE); |
| } |
| q1 = strtol(args[1], &endptr, 16); |
| if (*endptr) { |
| fputs("invalid data quadlet\n", stderr); |
| exit(EXIT_FAILURE); |
| } |
| if ((q0 & 0xc0000000) != 0xc0000000) { |
| fputs("not a VersaPHY packet\n", stderr); |
| exit(EXIT_FAILURE); |
| } |
| |
| if (args[2]) { |
| fprintf(stderr, "unexpected parameter `%s'\n", args[2]); |
| goto syntax_error; |
| } |
| |
| _send_packet(q0, q1, 0, 0); |
| } |
| |
| static void command_reset(char *args[]) |
| { |
| struct fw_cdev_initiate_bus_reset initiate_bus_reset; |
| |
| if (args[0]) { |
| fprintf(stderr, "unexpected parameter `%s'\n", args[0]); |
| help(); |
| exit(EXIT_FAILURE); |
| } |
| |
| initiate_bus_reset.type = FW_CDEV_SHORT_RESET; |
| if (ioctl(local_node->fd, FW_CDEV_IOC_INITIATE_BUS_RESET, &initiate_bus_reset) < 0) { |
| perror("INITIATE_BUS_RESET ioctl failed"); |
| exit(EXIT_FAILURE); |
| } |
| } |
| |
| int main(int argc, char *argv[]) |
| { |
| static const char short_options[] = "b:hV"; |
| static const struct option long_options[] = { |
| { "bus", 1, NULL, 'b' }, |
| { "help", 0, NULL, 'h' }, |
| { "version", 0, NULL, 'V' }, |
| {} |
| }; |
| static const struct { |
| const char *name; |
| void (*fn)(char *args[]); |
| } commands[] = { |
| { "config", command_config }, |
| { "ping", command_ping }, |
| { "read", command_read }, |
| { "nop", command_nop }, |
| { "disable", command_disable }, |
| { "suspend", command_suspend }, |
| { "clear", command_clear }, |
| { "enable", command_enable }, |
| { "resume", command_resume }, |
| { "standby", command_standby }, |
| { "restore", command_restore }, |
| { "linkon", command_linkon }, |
| { "link-on", command_linkon }, |
| { "link_on", command_linkon }, |
| { "versaphy", command_versaphy }, |
| { "reset", command_reset }, |
| }; |
| const char *bus_name = NULL; |
| unsigned int i; |
| int c; |
| |
| while ((c = getopt_long(argc, argv, short_options, long_options, NULL)) != -1) { |
| switch (c) { |
| case 'b': |
| bus_name = optarg; |
| break; |
| case 'h': |
| help(); |
| return 0; |
| case 'V': |
| puts("firewire-phy-command version " PACKAGE_VERSION); |
| return 0; |
| default: |
| syntax_error: |
| help(); |
| return 1; |
| } |
| } |
| |
| if (optind >= argc) { |
| fputs("missing command\n", stderr); |
| goto syntax_error; |
| } |
| for (i = 0; i < ARRAY_SIZE(commands); ++i) |
| if (!strcmp(commands[i].name, argv[optind])) { |
| open_all_nodes(); |
| find_local_node(bus_name); |
| commands[i].fn(argv + optind + 1); |
| close_all_nodes(); |
| return 0; |
| } |
| |
| fprintf(stderr, "unknown command `%s'\n", argv[optind]); |
| return 1; |
| } |