#include #include #include #include #include #include #include #include #include #include #include #include #include #define ZEV_DEVICE "/devices/pseudo/zev@0:ctrl" #if !defined(offsetof) #define offsetof(s, m) ((size_t)(&(((s *)0)->m))) #endif static char *zev_device = ZEV_DEVICE; static int do_flush = 0; static char *zev_op_name[] = { "ERROR", "MARK", "ZFS_MOUNT", "ZFS_UMOUNT", "ZVOL_WRITE", "ZVOL_TRUNCATE", "ZNODE_CLOSE_AFTER_UPDATE", "ZNODE_CREATE", "ZNODE_MKDIR", "ZNODE_MAKE_XATTR_DIR", "ZNODE_REMOVE", "ZNODE_RMDIR", "ZNODE_LINK", "ZNODE_SYMLINK", "ZNODE_RENAME", "ZNODE_WRITE", "ZNODE_TRUNCATE", "ZNODE_SETATTR", "ZNODE_ACL", NULL }; #define MD_STATISTICS 1 #define MD_POLL_EVENTS 2 #define MD_CHECKSUMS 3 #define MD_DEBUG_INFO 4 #define MD_LIST_QUEUES 5 #define MD_SET_GLOBAL_MAX_QUEUE_LEN 6 #define MD_SET_MAX_QUEUE_LEN 7 #define MD_SET_POLL_WAKEUP_QUEUE_LEN 8 #define MD_MUTE_POOL 9 #define MD_UNMUTE_POOL 10 #define MD_MARK 11 #define MD_ADD_QUEUE 12 #define MD_ADD_BLOCKING_QUEUE 13 #define MD_REMOVE_QUEUE 14 #define MD_QUEUE_BLOCKING 15 #define MD_QUEUE_NONBLOCKING 16 #define MD_QUEUE_PROPERTIES 17 #define MD_ZEVSTAT 18 #define MD_ZEV_REPORT 19 #define MD_DUMP_SPOOL 20 #define MD_GET_ZEV_VERSION 21 static int verbose = 0; static int grep_friendly = 0; #define MAX_GUID 10000 static int num_guid_filter = 0; static uint64_t guid_filter[MAX_GUID]; static void zpf(char *fmt, ...) { va_list ap; va_start(ap, fmt); vprintf(fmt, ap); va_end(ap); if (grep_friendly) { printf(" "); } else { printf("\n"); } } static void znl(void) { if (grep_friendly) printf("\n"); } static void sig2hex_direct(const uint8_t *sig, char *hex) { int i; for (i = 0; i < SHA1_DIGEST_LENGTH; ++i) { sprintf(hex + 2 * i, "%02x", sig[i]); } hex[SHA1_DIGEST_LENGTH * 2] = '\0'; } static int zev_statistics(int fd) { zev_statistics_t zs; if (ioctl(fd, ZEV_IOC_GET_GLOBAL_STATISTICS, &zs)) { perror("getting statistics data failed"); return (EXIT_FAILURE); } printf("ZEV module state:\n"); printf(" queue length in bytes : %lu\n", zs.zev_queue_len); printf(" queue length limit : %lu\n", zs.zev_max_queue_len); printf(" bytes read from device : %lu\n", zs.zev_bytes_read); printf(" module internal errors : %lu\n\n", zs.zev_cnt_errors); printf(" discarded events : %lu\n", zs.zev_cnt_discarded_events); printf(" discarded bytes : %lu\n\n", zs.zev_bytes_discarded); printf("ZFS event statistics:\n"); printf(" total ZFS events : %lu\n", zs.zev_cnt_total_events); printf(" ZFS mount : %lu\n", zs.zev_cnt_zfs_mount); printf(" ZFS umount : %lu\n", zs.zev_cnt_zfs_umount); printf(" ZVOL write : %lu\n", zs.zev_cnt_zvol_write); printf(" ZVOL truncate : %lu\n", zs.zev_cnt_zvol_truncate); printf(" ZNODE close after update: %lu\n", zs.zev_cnt_znode_close_after_update); printf(" ZNODE create : %lu\n", zs.zev_cnt_znode_create); printf(" ZNODE remove : %lu\n", zs.zev_cnt_znode_remove); printf(" ZNODE link : %lu\n", zs.zev_cnt_znode_link); printf(" ZNODE symlink : %lu\n", zs.zev_cnt_znode_symlink); printf(" ZNODE rename : %lu\n", zs.zev_cnt_znode_rename); printf(" ZNODE write : %lu\n", zs.zev_cnt_znode_write); printf(" ZNODE truncate : %lu\n", zs.zev_cnt_znode_truncate); printf(" ZNODE setattr : %lu\n", zs.zev_cnt_znode_setattr); printf(" ZNODE acl : %lu\n", zs.zev_cnt_znode_acl); return EXIT_SUCCESS; } static void zev_print_inode_info(char *name, zev_inode_info_t *info) { zpf(" %s.inode: %llu", name, info->ino); zpf(" %s.gen: %llu", name, info->gen); zpf(" %s.mtime: %llu", name, info->mtime); zpf(" %s.ctime: %llu", name, info->ctime); zpf(" %s.size: %llu", name, info->size); zpf(" %s.mode: %llo", name, info->mode); zpf(" %s.links: %llu", name, info->links); zpf(" %s.type: %lu", name, info->type); zpf(" %s.flags: %lu", name, info->flags); } static void zev_print_mark_payload(zev_mark_t *rec) { int i; int j; uint8_t *p; char c; zpf(" payload:"); p = (uint8_t *)ZEV_PAYLOAD(rec); for (i=0; ipayload_len; i+=16) { printf(" "); for (j=i; jpayload_len && jpayload_len && j= ' ' && p[j] <= '~') c = p[j]; printf("%c", c); if (j == i + 7) printf(" "); } printf("\n"); } } static void zev_print_error(char *buf) { zev_error_t *rec = (zev_error_t *)buf; time_t op_time = rec->op_time; char *ct = ctime(&op_time); ct[24] = '\0'; if (verbose) { zpf("%s %s", ct, zev_op_name[rec->op - ZEV_OP_MIN]); zpf(" guid: %llu", rec->guid); zpf(" failed.op: %s", zev_op_name[rec->failed_op - ZEV_OP_MIN]); zpf(" message: %s", ZEV_ERRSTR(rec)); znl(); } else { printf("%s %s: failed_op=%s msg=%s\n", ct, zev_op_name[rec->op - ZEV_OP_MIN], zev_op_name[rec->failed_op - ZEV_OP_MIN], ZEV_ERRSTR(rec)); } } static void zev_print_mark(char *buf) { zev_mark_t *rec = (zev_mark_t *)buf; time_t op_time = rec->op_time; char *ct = ctime(&op_time); ct[24] = '\0'; if (verbose) { zpf("%s %s", ct, zev_op_name[rec->op - ZEV_OP_MIN]); zpf(" guid: %llu", rec->guid); zpf(" mark.id: %llu", rec->mark_id); zpf(" payload.len: %llu", rec->payload_len); if (rec->payload_len) zev_print_mark_payload(rec); znl(); } else { printf("%s %s: guid=%llu mark_id=%lld payload_len=%ld " "payload=\"%.*s\"\n", ct, zev_op_name[rec->op - ZEV_OP_MIN], rec->guid, rec->mark_id, rec->payload_len, rec->payload_len, (char *)(rec + 1)); } } static void zev_print_zfs_mount(char *buf) { zev_zfs_mount_t *rec = (zev_zfs_mount_t *)buf; time_t op_time = rec->op_time; char *ct = ctime(&op_time); ct[24] = '\0'; if (verbose) { zpf("%s %s", ct, zev_op_name[rec->op - ZEV_OP_MIN]); zpf(" guid: %llu", rec->guid); zpf(" txg: %llu", rec->txg); zpf(" dataset: %s", ZEV_DATASET(rec)); zpf(" mountpoint: %s", ZEV_MOUNTPOINT(rec)); zpf(" remount: %s", rec->remount ? "true" : "false"); zev_print_inode_info("root", &rec->root); znl(); } else { printf("%s %s: guid=%llu remount=%s dataset='%s' " "mountpoint='%s'\n", ct, zev_op_name[rec->op - ZEV_OP_MIN], rec->guid, rec->remount ? "true" : "false", ZEV_DATASET(rec), ZEV_MOUNTPOINT(rec)); } } static void zev_print_zfs_umount(char *buf) { zev_zfs_umount_t *rec = (zev_zfs_umount_t *)buf; time_t op_time = rec->op_time; char *ct = ctime(&op_time); ct[24] = '\0'; if (verbose) { zpf("%s %s", ct, zev_op_name[rec->op - ZEV_OP_MIN]); zpf(" guid: %llu", rec->guid); zpf(" txg: %llu", rec->txg); zev_print_inode_info("covered", &rec->covered); znl(); } else { printf("%s %s: guid=%llu\n", ct, zev_op_name[rec->op - ZEV_OP_MIN], rec->guid); } } static void zev_print_zvol_truncate(char *buf) { zev_zvol_truncate_t *rec = (zev_zvol_truncate_t *)buf; time_t op_time = rec->op_time; char *ct = ctime(&op_time); ct[24] = '\0'; if (verbose) { zpf("%s %s", ct, zev_op_name[rec->op - ZEV_OP_MIN]); zpf(" guid: %llu", rec->guid); zpf(" txg: %llu", rec->txg); zpf(" offset: %llu", rec->offset); zpf(" length: %llu", rec->length); znl(); } else { printf("%s %s: guid=%llu offset=%llu length=%llu\n", ct, zev_op_name[rec->op - ZEV_OP_MIN], rec->guid, rec->offset, rec->length); } } static void zev_print_zvol_write(char *buf) { zev_print_zvol_truncate(buf); } static void zev_print_znode_close_after_update(char *buf) { zev_znode_close_after_update_t *rec = (zev_znode_close_after_update_t *)buf; time_t op_time = rec->op_time; char *ct = ctime(&op_time); ct[24] = '\0'; if (verbose) { zpf("%s %s", ct, zev_op_name[rec->op - ZEV_OP_MIN]); zpf(" guid: %llu", rec->guid); zev_print_inode_info("file", &rec->file); znl(); } else { printf("%s %s: guid=%llu file=%llu.%llu\n", ct, zev_op_name[rec->op - ZEV_OP_MIN], rec->guid, rec->file.ino, rec->file.gen); } } static void zev_print_znode_create(char *buf) { zev_znode_create_t *rec = (zev_znode_create_t *)buf; time_t op_time = rec->op_time; char *ct = ctime(&op_time); ct[24] = '\0'; zev_sig_t *sig; char sigval[(SHA1_DIGEST_LENGTH * 2) + 1]; if (verbose) { zpf("%s %s", ct, zev_op_name[rec->op - ZEV_OP_MIN]); zpf(" guid: %llu", rec->guid); zpf(" txg: %llu", rec->txg); zpf(" name: '%s'", ZEV_NAME(rec)); sig = &rec->signature; sig2hex_direct(sig->value, sigval); zpf(" sig: level %d, offset %llu, value %s", sig->level, sig->block_offset, sigval); zev_print_inode_info("file", &rec->file); zev_print_inode_info("parent", &rec->parent); znl(); } else { printf("%s %s: guid=%llu parent=%llu.%llu file=%llu.%llu " "file.mtime=%llu, parent.mtime=%llu, name='%s'\n", ct, zev_op_name[rec->op - ZEV_OP_MIN], rec->guid, rec->parent.ino, rec->parent.gen, rec->file.ino, rec->file.gen, rec->file.mtime, rec->parent.mtime, ZEV_NAME(rec)); } } static void zev_print_znode_mkdir(char *buf) { zev_print_znode_create(buf); } static void zev_print_znode_make_xattr_dir(char *buf) { zev_print_znode_create(buf); } static void zev_print_znode_remove(char *buf) { zev_znode_remove_t *rec = (zev_znode_remove_t *)buf; time_t op_time = rec->op_time; char *ct = ctime(&op_time); ct[24] = '\0'; if (verbose) { zpf("%s %s", ct, zev_op_name[rec->op - ZEV_OP_MIN]); zpf(" guid: %llu", rec->guid); zpf(" txg: %llu", rec->txg); zpf(" file.name: '%s'", ZEV_NAME(rec)); zev_print_inode_info("file", &rec->file); zev_print_inode_info("parent", &rec->parent); znl(); } else { printf("%s %s: guid=%llu parent=%llu.%llu " "file.mtime=%llu name='%s'\n", ct, zev_op_name[rec->op - ZEV_OP_MIN], rec->guid, rec->parent.ino, rec->parent.gen, rec->file.mtime, ZEV_NAME(rec)); } } static void zev_print_znode_rmdir(char *buf) { zev_print_znode_remove(buf); } static void zev_print_znode_link(char *buf) { zev_znode_link_t *rec = (zev_znode_link_t *)buf; time_t op_time = rec->op_time; char *ct = ctime(&op_time); ct[24] = '\0'; if (verbose) { zpf("%s %s", ct, zev_op_name[rec->op - ZEV_OP_MIN]); zpf(" guid: %llu", rec->guid); zpf(" txg: %llu", rec->txg); zpf(" link.name: '%s'", ZEV_NAME(rec)); zev_print_inode_info("file", &rec->file); zev_print_inode_info("parent", &rec->parent); znl(); } else { printf("%s %s: parent=%llu.%llu file=%llu.%llu " "file.ctime=%llu parent.ctime=%llu name='%s'\n", ct, zev_op_name[rec->op - ZEV_OP_MIN], rec->parent.ino, rec->parent.gen, rec->file.ino, rec->file.gen, rec->file.ctime, rec->parent.ctime, ZEV_NAME(rec)); } } static void zev_print_znode_symlink(char *buf) { zev_znode_symlink_t *rec = (zev_znode_symlink_t *)buf; time_t op_time = rec->op_time; char *ct = ctime(&op_time); ct[24] = '\0'; zev_sig_t *sig; char sigval[(SHA1_DIGEST_LENGTH * 2) + 1]; if (verbose) { zpf("%s %s", ct, zev_op_name[rec->op - ZEV_OP_MIN]); zpf(" guid: %llu", rec->guid); zpf(" txg: %llu", rec->txg); zpf(" symlink.name: '%s'", ZEV_NAME(rec)); zpf(" symlink.link: '%s'", ZEV_LINK(rec)); sig = &rec->signature; sig2hex_direct(sig->value, sigval); zpf(" sig: level %d, offset %llu, value %s", sig->level, sig->block_offset, sigval); zev_print_inode_info("file", &rec->file); zev_print_inode_info("parent", &rec->parent); znl(); } else { printf("%s %s: parent=%llu.%llu file=%llu.%llu " "name='%s' link='%s'\n", ct, zev_op_name[rec->op - ZEV_OP_MIN], rec->parent.ino, rec->parent.gen, rec->file.ino, rec->file.gen, ZEV_NAME(rec), ZEV_LINK(rec)); } } static void zev_print_znode_rename(char *buf) { zev_znode_rename_t *rec = (zev_znode_rename_t *)buf; time_t op_time = rec->op_time; char *ct = ctime(&op_time); ct[24] = '\0'; if (verbose) { zpf("%s %s", ct, zev_op_name[rec->op - ZEV_OP_MIN]); zpf(" guid: %llu", rec->guid); zpf(" txg: %llu", rec->txg); zpf(" file.srcname: '%s'", ZEV_SRCNAME(rec)); zpf(" file.dstname: '%s'", ZEV_DSTNAME(rec)); zev_print_inode_info("file", &rec->file); if (rec->clobbered_file.ino) zev_print_inode_info("clobbered_file", &rec->clobbered_file); zev_print_inode_info("srcdir", &rec->srcdir); zev_print_inode_info("dstdir", &rec->dstdir); znl(); } else { printf("%s %s: srcdir=%llu.%llu dstdir=%llu.%llu " "file=%llu.%llu file.mtime=%llu, file.ctime=%llu, " "srcdir.mtime=%llu, srcdir.ctime=%llu, " "dstdir.mtime=%llu, dstdir.ctime=%llu, " "srcname='%s' dstname='%s'\n", ct, zev_op_name[rec->op - ZEV_OP_MIN], rec->srcdir.ino, rec->srcdir.gen, rec->dstdir.ino, rec->dstdir.gen, rec->file.ino, rec->file.gen, rec->file.mtime, rec->file.ctime, rec->srcdir.mtime, rec->srcdir.ctime, rec->dstdir.mtime, rec->dstdir.ctime, ZEV_SRCNAME(rec), ZEV_DSTNAME(rec)); } } static void zev_print_znode_write(char *buf) { zev_znode_write_t *rec = (zev_znode_write_t *)buf; time_t op_time = rec->op_time; char *ct = ctime(&op_time); ct[24] = '\0'; zev_sig_t *sig; char sigval[(SHA1_DIGEST_LENGTH * 2) + 1]; int i; if (verbose) { zpf("%s %s", ct, zev_op_name[rec->op - ZEV_OP_MIN]); zpf(" guid: %llu", rec->guid); zpf(" txg: %llu", rec->txg); zpf(" offset: %llu", rec->offset); zpf(" length: %llu", rec->length); zev_print_inode_info("file", &rec->file); znl(); for (i=0; isignature_cnt; i++) { sig = (zev_sig_t *)ZEV_SIGNATURES(rec); sig += i; sig2hex_direct(sig->value, sigval); zpf(" sig: level %d, offset %llu, value %s", sig->level, sig->block_offset, sigval); } } else { printf("%s %s: file=%llu.%llu offset=%llu length=%llu\n", ct, zev_op_name[rec->op - ZEV_OP_MIN], rec->file.ino, rec->file.gen, rec->offset, rec->length); } } static void zev_print_znode_truncate(char *buf) { zev_print_znode_write(buf); } static void zev_print_znode_setattr(char *buf) { zev_znode_setattr_t *rec = (zev_znode_setattr_t *)buf; time_t op_time = rec->op_time; char *ct = ctime(&op_time); ct[24] = '\0'; if (verbose) { zpf("%s %s", ct, zev_op_name[rec->op - ZEV_OP_MIN]); zpf(" guid: %llu", rec->guid); zpf(" txg: %llu", rec->txg); zev_print_inode_info("file", &rec->file); znl(); } else { printf("%s %s: file=%llu.%llu mtime=%llu\n", ct, zev_op_name[rec->op - ZEV_OP_MIN], rec->file.ino, rec->file.gen, rec->file.mtime); } } static void zev_print_znode_acl(char *buf) { zev_print_znode_setattr(buf); } static void zev_print_event(char *buf, int len) { int record_len; int op; record_len = *(uint32_t *)buf; if (record_len != len) { fprintf(stderr, "record length mismatch: got %d, expected %d\n", record_len, len); exit(1); } op = *((uint32_t *)buf + 1); if (op < ZEV_OP_MIN || op > ZEV_OP_MAX) { fprintf(stderr, "unknown op code: %d\n", op); exit(1); } if (num_guid_filter) { uint64_t guid = *((uint64_t *)buf + 2); int i; for (i = 0; i < num_guid_filter; ++i) if (guid_filter[i] == guid) break; if (i == num_guid_filter) /* no match, filtered */ return; } switch (op) { case ZEV_OP_ERROR: zev_print_error(buf); break; case ZEV_OP_MARK: zev_print_mark(buf); break; case ZEV_OP_ZFS_MOUNT: zev_print_zfs_mount(buf); break; case ZEV_OP_ZFS_UMOUNT: zev_print_zfs_umount(buf); break; case ZEV_OP_ZVOL_TRUNCATE: zev_print_zvol_truncate(buf); break; case ZEV_OP_ZVOL_WRITE: zev_print_zvol_write(buf); break; case ZEV_OP_ZNODE_CLOSE_AFTER_UPDATE: zev_print_znode_close_after_update(buf); break; case ZEV_OP_ZNODE_CREATE: zev_print_znode_create(buf); break; case ZEV_OP_ZNODE_MKDIR: zev_print_znode_mkdir(buf); break; case ZEV_OP_ZNODE_MAKE_XATTR_DIR: zev_print_znode_make_xattr_dir(buf); break; case ZEV_OP_ZNODE_REMOVE: zev_print_znode_remove(buf); break; case ZEV_OP_ZNODE_RMDIR: zev_print_znode_rmdir(buf); break; case ZEV_OP_ZNODE_LINK: zev_print_znode_link(buf); break; case ZEV_OP_ZNODE_SYMLINK: zev_print_znode_symlink(buf); break; case ZEV_OP_ZNODE_RENAME: zev_print_znode_rename(buf); break; case ZEV_OP_ZNODE_WRITE: zev_print_znode_write(buf); break; case ZEV_OP_ZNODE_TRUNCATE: zev_print_znode_truncate(buf); break; case ZEV_OP_ZNODE_SETATTR: zev_print_znode_setattr(buf); break; case ZEV_OP_ZNODE_ACL: zev_print_znode_acl(buf); break; default: fprintf(stderr, "unhandled op code: %d\n", op); exit(1); } if (do_flush) fflush(stdout); } static int zev_poll_events(int fd, int create_tmp_queue) { struct pollfd pfd[1]; int ret; char buf[4096]; zev_event_t *ev; int off = 0; int q_fd; if (create_tmp_queue) { snprintf(buf, sizeof(buf), "/devices/pseudo/zev@0:%s", ZEV_TMPQUEUE_DEVICE_NAME); q_fd = open(buf, O_RDONLY); if (q_fd < 0) { perror("opening queue device failed"); return (EXIT_FAILURE); } } else { q_fd = fd; } while (1) { pfd[0].fd = q_fd; pfd[0].events = POLLIN; ret = poll(pfd, 1, 1000); if (ret < 0) { perror("poll failed"); close(q_fd); return(EXIT_FAILURE); } if (!(pfd[0].revents & POLLIN)) continue; /* data available */ ret = read(q_fd, buf, sizeof(buf)); if (ret < 0) { perror("read failed"); close(q_fd); return(EXIT_FAILURE); } if (ret == 0) continue; while (ret > off) { ev = (zev_event_t *)(buf + off); zev_print_event(buf + off, ev->header.record_len); off += ev->header.record_len; } off = 0; } if (create_tmp_queue) close(q_fd); return EXIT_SUCCESS; } static int zev_dump_spool(int fd) { int len; char buf[4096]; int off = 0; while (1) { len = read(fd, buf + off, sizeof(buf) - off); if (len == -1) { fprintf(stderr, "reading from spool failed: %s\n", strerror(errno)); return EXIT_FAILURE; } if (len == 0) break; len += off; off = 0; while (len > off + sizeof(uint32_t)) { uint32_t evlen; char *mp; zev_event_t *ev; ev = (zev_event_t *)(buf + off); evlen = ev->header.record_len; if (len < off + evlen + 1) break; mp = buf + off + evlen; if (!memchr(mp, 0, len - off - evlen)) break; zev_print_event(buf + off, ev->header.record_len); off += ev->header.record_len + strlen(mp) + 1; } memmove(buf, buf + off, len - off); off = len - off; } return EXIT_SUCCESS; } static void usage(char *progname) { fprintf(stderr, "usage: %s [-d ] [options]\n", progname); fprintf(stderr, "\n"); fprintf(stderr, " Status information:\n"); fprintf(stderr, " -s show zev statistics\n"); fprintf(stderr, " -p poll for ZFS events\n"); fprintf(stderr, " -f dump events from spool\n"); fprintf(stderr, " -D print zev module debug " "information\n"); fprintf(stderr, " -T zevstat mode\n"); fprintf(stderr, " -R zevreport mode\n"); fprintf(stderr, "\n"); fprintf(stderr, " Tune zev module settings:\n"); fprintf(stderr, " -Q set maximum event queue " "length\n"); fprintf(stderr, " -m mute pool, no events for " "this pool\n"); fprintf(stderr, " -M unmute pool\n"); fprintf(stderr, "\n"); fprintf(stderr, " Queue management:\n"); fprintf(stderr, " -l list queues\n"); fprintf(stderr, " -a add non-blocking queue\n"); fprintf(stderr, " -A add blocking queue\n"); fprintf(stderr, " -r remove queue\n"); fprintf(stderr, " -b make queue non-blocking " "(default)\n"); fprintf(stderr, " -B make queue block when full\n"); fprintf(stderr, " -P display queue properties\n"); fprintf(stderr, " -L set maximum event queue " "length\n"); fprintf(stderr, " -t set queue length poll " "throttle\n"); fprintf(stderr, "\n"); fprintf(stderr, " Other options:\n"); fprintf(stderr, " -d non-default device file. " "('%s')\n", ZEV_DEVICE); fprintf(stderr, " -q use device file for this " "queue name\n"); fprintf(stderr, " -k : queue mark event\n"); fprintf(stderr, " -c list file's content " "checksums\n"); fprintf(stderr, " -v verbose: additional output " "for some operations\n"); fprintf(stderr, " -g grep-friendly event output, " "one event per line\n"); fprintf(stderr, " -G filter event output by guid, " "can be given more than once\n"); fprintf(stderr, " -V query zev module version\n"); fprintf(stderr, " -F flush output after each line\n"); exit (EXIT_FAILURE); } static void zevstat_usage(char *progname) { fprintf(stderr, "usage: %s [-v] [count]\n", progname); fprintf(stderr, " -v verbose, show counters for all event types\n"); exit (EXIT_FAILURE); } static void zevreport_usage(char *progname) { fprintf(stderr, "usage: %s \n", progname); exit (EXIT_FAILURE); } static int zev_add_queue(int fd, char *arg, int blocking) { zev_ioctl_add_queue_t aq; int namelen; namelen = strlen(arg); if (namelen > ZEV_MAX_QUEUE_NAME_LEN) { fprintf(stderr, "queue name too long: %s\n", arg); return (EXIT_FAILURE); } aq.zev_namelen = namelen; strcpy(aq.zev_name, arg); aq.zev_flags = ZEV_FL_PERSISTENT | ZEV_FL_INITIALLY_EMPTY; if (blocking) { aq.zev_flags |= ZEV_FL_BLOCK_WHILE_QUEUE_FULL; aq.zev_max_queue_len = ZEV_MAX_QUEUE_LEN; } else { aq.zev_max_queue_len = (1024 * 1024); } if (ioctl(fd, ZEV_IOC_ADD_QUEUE, &aq)) { perror("adding queue failed"); return (EXIT_FAILURE); } return (0); } static int zev_remove_queue(int fd, char *arg) { zev_ioctl_remove_queue_t aq; int namelen; namelen = strlen(arg); if (namelen > ZEV_MAX_QUEUE_NAME_LEN) { fprintf(stderr, "queue name too long: %s\n", arg); return (EXIT_FAILURE); } aq.zev_queue_name.zev_namelen = namelen; strcpy(aq.zev_queue_name.zev_name, arg); if (ioctl(fd, ZEV_IOC_REMOVE_QUEUE, &aq)) { perror("removing queue failed"); return (EXIT_FAILURE); } return (0); } static int zev_set_global_max_queue_len(int fd, char *arg) { uint64_t maxqueuelen; if (!arg) { fprintf(stderr, "missing queue length parameter\n"); return (EXIT_FAILURE); } errno = 0; maxqueuelen = strtol(arg, (char **)NULL, 10); if (errno) { fprintf(stderr, "invalid queue length parameter: %s\n", arg); return (EXIT_FAILURE); } if (ioctl(fd, ZEV_IOC_SET_MAX_QUEUE_LEN, &maxqueuelen)) { perror("setting max queue length failed"); return (EXIT_FAILURE); } return (0); } static int zev_mute_unmute_impl(int fd, char *poolname, int mute) { zev_ioctl_poolarg_t pa; int len; int op = mute ? ZEV_IOC_MUTE_POOL : ZEV_IOC_UNMUTE_POOL; len = strlen(poolname); if (len <= 0 || len >= sizeof(pa.zev_poolname)) { fprintf(stderr, "invalid poolname: %s\n", poolname); return (EXIT_FAILURE); } strcpy(pa.zev_poolname, poolname); pa.zev_poolname_len = len; if (ioctl(fd, op, &pa)) { perror("muting pool data failed"); return (EXIT_FAILURE); } return (0); } int zev_mute_pool(int fd, char *poolname) { return zev_mute_unmute_impl(fd, poolname, 1); } int zev_unmute_pool(int fd, char *poolname) { return zev_mute_unmute_impl(fd, poolname, 0); } static int zev_debug_info(int fd) { zev_ioctl_debug_info_t di; if (ioctl(fd, ZEV_IOC_GET_DEBUG_INFO, &di)) { perror("getting zev debug info failed"); return (EXIT_FAILURE); } printf("memory allocated: %llu bytes\n", di.zev_memory_allocated); printf("checksum cache size: %llu\n", di.zev_chksum_cache_size); printf("checksum cache hits: %llu\n", di.zev_chksum_cache_hits); printf("checksum cache misses: %llu\n", di.zev_chksum_cache_misses); return 0; } static int zev_mark(int fd, char *arg) { zev_ioctl_mark_t *mark; uint64_t guid; int len; char *p; p = strchr(arg, ':'); if (!p) { fprintf(stderr, "expected value is :, " "e.g. '123:hello'\n"); exit (EXIT_FAILURE); } *p = '\n'; p++; errno = 0; guid = strtoll(arg, (char **)NULL, 10); if (errno) { fprintf(stderr, "guid must be a number.\n"); exit (EXIT_FAILURE); } len = strlen(p); mark = malloc(sizeof(*mark) + len + 1); if (!mark) { fprintf(stderr, "can't allocate mark structure: %s\n", strerror(errno)); exit (EXIT_FAILURE); } mark->zev_guid = guid; mark->zev_mark_id = 0; mark->zev_payload_len = len; strcpy(ZEV_PAYLOAD(mark), p); if (ioctl(fd, ZEV_IOC_MARK, mark)) { perror("queueing mark failed"); return (EXIT_FAILURE); } printf("mark id: %lu\n", mark->zev_mark_id); return (0); } static int zev_queue_blocking(int fd, char *arg, int block) { zev_ioctl_get_queue_properties_t gqp; gqp.zev_queue_name.zev_namelen = strlen(arg); if (gqp.zev_queue_name.zev_namelen > ZEV_MAX_QUEUE_NAME_LEN) { fprintf(stderr, "queue name too long.\n"); return EXIT_FAILURE; } strcpy(gqp.zev_queue_name.zev_name, arg); if (ioctl(fd, ZEV_IOC_GET_QUEUE_PROPERTIES, &gqp)) { perror("getting queue properties failed"); return (EXIT_FAILURE); } if (block) { gqp.zev_flags |= ZEV_FL_BLOCK_WHILE_QUEUE_FULL; } else { gqp.zev_flags &= ~ZEV_FL_BLOCK_WHILE_QUEUE_FULL; } if (ioctl(fd, ZEV_IOC_SET_QUEUE_PROPERTIES, &gqp)) { perror("setting queue properties failed"); return (EXIT_FAILURE); } return (0); } static int zev_set_max_queue_len(int fd, char *arg, char *len) { zev_ioctl_get_queue_properties_t gqp; if (!len) { fprintf(stderr, "queue size parameter missing.\n"); return EXIT_FAILURE; } gqp.zev_queue_name.zev_namelen = strlen(arg); if (gqp.zev_queue_name.zev_namelen > ZEV_MAX_QUEUE_NAME_LEN) { fprintf(stderr, "queue name too long.\n"); return EXIT_FAILURE; } strcpy(gqp.zev_queue_name.zev_name, arg); if (ioctl(fd, ZEV_IOC_GET_QUEUE_PROPERTIES, &gqp)) { perror("getting queue properties failed"); return (EXIT_FAILURE); } gqp.zev_max_queue_len = atol(len); if (gqp.zev_max_queue_len == 0 && strcmp("0", len)) { fprintf(stderr, "queue size parameter garbled.\n"); return (EXIT_FAILURE); } if (gqp.zev_max_queue_len > ZEV_MAX_QUEUE_LEN) { fprintf(stderr, "queue size parameter out of bounds.\n"); return (EXIT_FAILURE); } if (ioctl(fd, ZEV_IOC_SET_QUEUE_PROPERTIES, &gqp)) { perror("setting queue properties failed"); return (EXIT_FAILURE); } return (0); } static int zev_set_poll_wakeup_queue_len(int fd, char *arg, char *len) { zev_ioctl_get_queue_properties_t gqp; if (!len) { fprintf(stderr, "poll throttle parameter missing.\n"); return EXIT_FAILURE; } gqp.zev_queue_name.zev_namelen = strlen(arg); if (gqp.zev_queue_name.zev_namelen > ZEV_MAX_QUEUE_NAME_LEN) { fprintf(stderr, "queue name too long.\n"); return EXIT_FAILURE; } strcpy(gqp.zev_queue_name.zev_name, arg); if (ioctl(fd, ZEV_IOC_GET_QUEUE_PROPERTIES, &gqp)) { perror("getting queue properties failed"); return (EXIT_FAILURE); } gqp.zev_poll_wakeup_threshold = atol(len); if (gqp.zev_poll_wakeup_threshold == 0 && strcmp("0", len)) { fprintf(stderr, "poll throttle parameter garbled.\n"); return (EXIT_FAILURE); } if (gqp.zev_poll_wakeup_threshold > ZEV_MAX_POLL_WAKEUP_QUEUE_LEN) { fprintf(stderr, "poll throttle parameter out of bounds.\n"); return (EXIT_FAILURE); } if (ioctl(fd, ZEV_IOC_SET_QUEUE_PROPERTIES, &gqp)) { perror("setting queue properties failed"); return (EXIT_FAILURE); } return (0); } static int zev_queue_properties(int fd, char *arg) { zev_ioctl_get_queue_properties_t gqp; gqp.zev_queue_name.zev_namelen = strlen(arg); if (gqp.zev_queue_name.zev_namelen > ZEV_MAX_QUEUE_NAME_LEN) { fprintf(stderr, "queue name too long.\n"); return EXIT_FAILURE; } strcpy(gqp.zev_queue_name.zev_name, arg); if (ioctl(fd, ZEV_IOC_GET_QUEUE_PROPERTIES, &gqp)) { perror("getting queue properties failed"); return (EXIT_FAILURE); } printf("queue : %s\n", arg); printf("max size : %" PRIu64 "\n", gqp.zev_max_queue_len); printf("poll throttle: %" PRIu64 "\n", gqp.zev_poll_wakeup_threshold); printf("persistent : %s\n", gqp.zev_flags & ZEV_FL_PERSISTENT ? "yes" : "no"); printf("blocking : %s\n", gqp.zev_flags & ZEV_FL_BLOCK_WHILE_QUEUE_FULL ? "yes" : "no"); return (0); } static int zev_list_queues(int fd) { zev_ioctl_get_queue_properties_t gqp; zev_ioctl_get_queue_list_t gql; zev_ioctl_get_queue_statistics_t gs; uint64_t i; char name[ZEV_MAX_QUEUE_NAME_LEN+1]; zev_statistics_t zs; if (ioctl(fd, ZEV_IOC_GET_GLOBAL_STATISTICS, &zs)) { perror("getting statistics data failed"); return (EXIT_FAILURE); } if (ioctl(fd, ZEV_IOC_GET_QUEUE_LIST, &gql)) { perror("getting queue list failed"); return (EXIT_FAILURE); } printf("Name Size " "Size%% Max Size Per Block\n"); for (i=0; izev_fd = fd; gs->zev_bufsize = buf_size; off = 0; data = 0; while (1) { errno = 0; data = llseek(fd, off, SEEK_DATA); if (data < 0) { if (errno == ENXIO) /* no more data */ break; perror("llseek failed"); goto err; } data = P2ALIGN(data, ZEV_L1_SIZE); off = data + ZEV_L1_SIZE; gs->zev_offset = data; gs->zev_len = ZEV_L1_SIZE; if (ioctl(dev_fd, ZEV_IOC_GET_FILE_SIGNATURES, gs)) { perror("ioctl to get signatures failed"); goto err; } for (i=0; izev_signature_cnt; i++) { sig = (zev_sig_t *)ZEV_SIGNATURES(gs); sig += i; sig2hex_direct(sig->value, sigval); printf("level %d, offset %llu, value %s\n", sig->level, sig->block_offset, sigval); } } free(buf); close(fd); return 0; err: free(buf); close(fd); return (EXIT_FAILURE); } typedef struct zevstat { uint64_t ns_start; uint64_t events[ZEV_OP_MIN + ZEV_OP_MAX]; uint64_t guids; uint64_t total_events; uint64_t total_guids; avl_tree_t guids_interval; avl_tree_t guids_runtime; } zevstat_t; typedef struct zev_guidtrack_t { uint64_t guid; avl_node_t avl_interval; avl_node_t avl_runtime; } zev_guidtrack_t; zevstat_t zevstat; static void zev_eventstat(char *buf, int len) { zev_header_t *rec = (zev_header_t *)buf; zev_guidtrack_t *gt; zev_guidtrack_t *gt_int; zev_guidtrack_t to_find; avl_index_t where; zevstat.total_events++; zevstat.events[rec->op]++; to_find.guid = rec->guid; gt = avl_find(&zevstat.guids_runtime, &to_find, &where); if (!gt) { gt = malloc(sizeof(*gt)); if (!gt) { perror("can't get guid tracking record"); exit (EXIT_FAILURE); } gt->guid = rec->guid; avl_insert(&zevstat.guids_runtime, gt, where); } gt_int = avl_find(&zevstat.guids_interval, &to_find, &where); if (!gt_int) avl_insert(&zevstat.guids_interval, gt, where); } static void zev_eventstat_interval(FILE *out) { uint64_t events; int i; zev_guidtrack_t *gt; events = 0; for (i = ZEV_OP_MIN; i <= ZEV_OP_MAX; i++) { events += zevstat.events[i]; } if (verbose) { fprintf(out, "%u %6llu %6llu %6llu %6llu ", time(NULL), events, zevstat.total_events, avl_numnodes(&zevstat.guids_interval), avl_numnodes(&zevstat.guids_runtime)); for (i = ZEV_OP_MIN; i <= ZEV_OP_MAX; i++) fprintf(out, "%6llu ", zevstat.events[i]); fprintf(out, "\n"); } else { fprintf(out, "%u %6llu %6llu %6llu %6llu\n", time(NULL), events, zevstat.total_events, avl_numnodes(&zevstat.guids_interval), avl_numnodes(&zevstat.guids_runtime)); } memset(&zevstat.events, 0, sizeof(zevstat.events)); zevstat.guids = 0; while (gt = avl_first(&zevstat.guids_interval)) avl_remove(&zevstat.guids_interval, gt); fflush(out); } static int zev_evcompar(const void *a, const void *b) { const zev_guidtrack_t *ga = a; const zev_guidtrack_t *gb = b; if (ga->guid > gb->guid) return 1; if (ga->guid < gb->guid) return -1; return 0; } static int zev_zevstat(int fd, char *s_interval, char *s_count, char *outfile) { uint64_t interval = 1000; uint64_t ms; uint64_t t_until; uint64_t t_now; int cnt = -1; struct pollfd pfd[1]; int ret; char buf[4096]; zev_event_t *ev; int off = 0; zev_ioctl_add_queue_t aq; int q_fd; zev_guidtrack_t *gt; FILE *out = stdout; struct stat st; char filename[MAXPATHLEN]; int retry; if (outfile) { retry = 0; strncpy(filename, outfile, sizeof(filename)); while (stat(filename, &st) == 0) { /* file exists */ snprintf(filename, sizeof(filename), "%s.%d", outfile, retry); retry++; } out = fopen(filename, "wb+"); if (!out) { perror("opening output file failed"); return (EXIT_FAILURE); } } memset(&zevstat, 0, sizeof(zevstat)); avl_create(&zevstat.guids_runtime, zev_evcompar, sizeof(zev_guidtrack_t), offsetof(zev_guidtrack_t, avl_runtime)); avl_create(&zevstat.guids_interval, zev_evcompar, sizeof(zev_guidtrack_t), offsetof(zev_guidtrack_t, avl_interval)); if (s_interval) { interval = atol(s_interval); if (interval == 0) { fprintf(stderr, "invalid interval.\n"); return (EXIT_FAILURE); } interval *= 1000; } if (s_count) { cnt = atol(s_count); if (interval == 0) { fprintf(stderr, "invalid count.\n"); return (EXIT_FAILURE); } } aq.zev_max_queue_len = 1024 * 1024; aq.zev_flags = ZEV_FL_INITIALLY_EMPTY; snprintf(aq.zev_name, ZEV_MAX_QUEUE_NAME_LEN, "zevstat.%ld.%ld", time(NULL), getpid()); aq.zev_namelen = strlen(aq.zev_name); if (ioctl(fd, ZEV_IOC_ADD_QUEUE, &aq)) { perror("adding temporary queue failed"); return (EXIT_FAILURE); } snprintf(buf, sizeof(buf), "/devices/pseudo/zev@0:%s", aq.zev_name); q_fd = open(buf, O_RDONLY); if (q_fd < 0) { perror("opening queue device failed"); return (EXIT_FAILURE); } pfd[0].fd = q_fd; pfd[0].events = POLLIN; /* drain queue */ while ((ret = poll(pfd, 1, 0)) > 0) { if (read(q_fd, buf, sizeof(buf)) < 0) { perror("read failed"); close(q_fd); return(EXIT_FAILURE); } } if (ret < 0) { perror("poll failed"); close(q_fd); return(EXIT_FAILURE); } fprintf(out, "timestamp events tevents guids tguids"); if (verbose) { fprintf(out, " error mark mount umount zvol_w "); fprintf(out, "zvol_t close create mkdir mxattr "); fprintf(out, "remove rmdir link symlnk rename "); fprintf(out, "write trunc setatt acl"); } fprintf(out, "\n"); while (cnt) { t_until = gethrtime() + (interval * 1000000); ms = interval; do { ret = poll(pfd, 1, ms); t_now = gethrtime(); if (t_now < t_until) { ms = t_until - t_now; ms /= 1000000ull; } if (ret < 0) { perror("poll failed"); close(q_fd); return(EXIT_FAILURE); } if (!(pfd[0].revents & POLLIN)) continue; /* data available */ ret = read(q_fd, buf, sizeof(buf)); if (ret < 0) { perror("read failed"); close(q_fd); return(EXIT_FAILURE); } if (ret == 0) continue; while (ret > off) { ev = (zev_event_t *)(buf + off); zev_eventstat(buf + off, ev->header.record_len); off += ev->header.record_len; } off = 0; } while ((t_now) < t_until && (ms > 0)); zev_eventstat_interval(out); if (cnt > 0) cnt--; } close(q_fd); if (outfile) fclose(out); while (gt = avl_first(&zevstat.guids_interval)) avl_remove(&zevstat.guids_interval, gt); while (gt = avl_first(&zevstat.guids_runtime)) { avl_remove(&zevstat.guids_runtime, gt); free(gt); } return EXIT_SUCCESS; } static int zev_report(int fd, char *basename) { char filename[MAXPATHLEN]; char count[10]; time_t now; time_t midnight; struct tm tm; int minutes; int ret; verbose++; while (1) { now = time(NULL); localtime_r(&now, &tm); snprintf(filename, sizeof(filename), "%s.%04d-%02d-%02d", basename, tm.tm_year + 1900, tm.tm_mon + 1, tm.tm_mday); tm.tm_sec = 0; tm.tm_min = 0; tm.tm_hour = 0; tm.tm_mday++; /* works for Jan 32nd, Feb 30th, etc. */ midnight = mktime(&tm); if (now % 60) sleep(60 - (now % 60)); minutes = (midnight - time(NULL)) / 60; snprintf(count, sizeof(count), "%d", minutes); ret = zev_zevstat(fd, "60", count, filename); if (ret) return EXIT_FAILURE; } return EXIT_SUCCESS; /* never reached */ } static int zev_get_zev_version(int fd) { zev_ioctl_get_zev_version vi; if (ioctl(fd, ZEV_IOC_GET_ZEV_VERSION, &vi)) { perror("getting zev cersion info failed"); return (EXIT_FAILURE); } printf("zev major version: %llu\n", vi.zev_major_version); printf("zev minor version: %llu\n", vi.zev_minor_version); return 0; } static void zev_sigint(int sig) { fflush(stdout); } int main(int argc, char **argv) { int fd; int c; extern char *optarg; int create_tmp_queue = 1; char buf[MAXPATHLEN]; int mode = 0; char *arg = NULL; char *arg2 = NULL; char *p; sigset(SIGINT, zev_sigint); /* open device */ fd = open(zev_device, O_RDONLY); if (fd < 0) { perror("opening zev device failed"); return EXIT_FAILURE; } p = strrchr(argv[0], '/'); if (!p) { p = argv[0]; } else { p++; } if (!strcmp(p, "zevstat")) { mode = MD_ZEVSTAT; if (argc < 2) zevstat_usage(argv[0]); if (!strcmp(argv[1], "-v")) { if (argc < 3) zevstat_usage(argv[0]); verbose++; arg = argv[2]; arg2 = argv[3]; } else { arg = argv[1]; arg2 = argv[2]; } return zev_zevstat(fd, arg, arg2, NULL); } else if(!strcmp(p, "zevreport")) { mode = MD_ZEV_REPORT; if (argc != 2) zevreport_usage(argv[0]); return zev_report(fd, argv[1]); } while ((c = getopt(argc, argv, "a:A:b:B:c:d:Df:FgG:hk:lL:m:M:pP:q:Q:r:R:st:T:vV?")) != -1) { switch(c) { case 'g': grep_friendly++; verbose++; break; case 'v': verbose++; break; case 's': mode = MD_STATISTICS; break; case 'p': mode = MD_POLL_EVENTS; break; case 'c': mode = MD_CHECKSUMS; arg = optarg; break; case 'D': mode = MD_DEBUG_INFO; break; case 'd': close(fd); zev_device = optarg; fd = open(zev_device, O_RDONLY); if (fd < 0) { perror("opening zev device failed"); return EXIT_FAILURE; } create_tmp_queue = 0; break; case 'q': snprintf(buf, sizeof(buf), "/devices/pseudo/zev@0:%s", optarg); close(fd); zev_device = buf; fd = open(zev_device, O_RDONLY); if (fd < 0) { perror("opening zev device failed"); return EXIT_FAILURE; } create_tmp_queue = 0; break; case 'f': fd = open(optarg, O_RDONLY); if (fd < 0) { perror("opening spool file failed"); return EXIT_FAILURE; } mode = MD_DUMP_SPOOL; break; case 'l': mode = MD_LIST_QUEUES; break; case 'Q': mode = MD_SET_GLOBAL_MAX_QUEUE_LEN; arg = optarg; break; case 'L': mode = MD_SET_MAX_QUEUE_LEN; arg = optarg; arg2 = argv[optind]; break; case 'T': mode = MD_ZEVSTAT; arg = optarg; arg2 = argv[optind]; break; case 'R': mode = MD_ZEV_REPORT; arg = optarg; break; case 't': mode = MD_SET_POLL_WAKEUP_QUEUE_LEN; arg = optarg; arg2 = argv[optind]; break; case 'm': mode = MD_MUTE_POOL; arg = optarg; break; case 'M': mode = MD_UNMUTE_POOL; arg = optarg; break; case 'k': mode = MD_MARK; arg = optarg; break; case 'a': mode = MD_ADD_QUEUE; arg = optarg; break; case 'A': mode = MD_ADD_BLOCKING_QUEUE; arg = optarg; break; case 'r': mode = MD_REMOVE_QUEUE; arg = optarg; break; case 'b': mode = MD_QUEUE_BLOCKING; arg = optarg; break; case 'B': mode = MD_QUEUE_NONBLOCKING; arg = optarg; break; case 'P': mode = MD_QUEUE_PROPERTIES; arg = optarg; break; case 'G': if (num_guid_filter == MAX_GUID) { fprintf(stderr, "too many guids given, max is %d\n", MAX_GUID); exit(1); } guid_filter[num_guid_filter++] = atoll(optarg); break; case 'V': mode = MD_GET_ZEV_VERSION; break; case 'F': do_flush = 1; break; case 'h': case '?': default: usage(argv[0]); } } switch (mode) { case MD_STATISTICS: return zev_statistics(fd); case MD_POLL_EVENTS: return zev_poll_events(fd, create_tmp_queue); case MD_DUMP_SPOOL: return zev_dump_spool(fd); case MD_CHECKSUMS: return zev_checksum(fd, arg); case MD_DEBUG_INFO: return zev_debug_info(fd); case MD_LIST_QUEUES: return zev_list_queues(fd); case MD_SET_GLOBAL_MAX_QUEUE_LEN: return zev_set_global_max_queue_len(fd, arg); case MD_SET_MAX_QUEUE_LEN: return zev_set_max_queue_len(fd, arg, arg2); case MD_SET_POLL_WAKEUP_QUEUE_LEN: return zev_set_poll_wakeup_queue_len(fd, arg, arg2); case MD_ZEVSTAT: return zev_zevstat(fd, arg, arg2, NULL); case MD_ZEV_REPORT: return zev_report(fd, arg); case MD_MUTE_POOL: return zev_mute_pool(fd, arg); case MD_UNMUTE_POOL: return zev_unmute_pool(fd, arg); case MD_MARK: return zev_mark(fd, arg); case MD_ADD_QUEUE: return zev_add_queue(fd, arg, 0); case MD_ADD_BLOCKING_QUEUE: return zev_add_queue(fd, arg, 1); case MD_REMOVE_QUEUE: return zev_remove_queue(fd, arg); case MD_QUEUE_BLOCKING: return zev_queue_blocking(fd, arg, 0); case MD_QUEUE_NONBLOCKING: return zev_queue_blocking(fd, arg, 1); case MD_QUEUE_PROPERTIES: return zev_queue_properties(fd, arg); case MD_GET_ZEV_VERSION: return zev_get_zev_version(fd); default: close(fd); usage(argv[0]); return EXIT_FAILURE; }; }