diff options
author | Brian Behlendorf <[email protected]> | 2008-12-11 11:08:09 -0800 |
---|---|---|
committer | Brian Behlendorf <[email protected]> | 2008-12-11 11:08:09 -0800 |
commit | 172bb4bd5e4afef721dd4d2972d8680d983f144b (patch) | |
tree | 18ab1e97e5e409150066c529b5a981ecf600ef80 /cmd/zinject | |
parent | 9e8b1e836caa454586797f771a7ad1817ebae315 (diff) |
Move the world out of /zfs/ and seperate out module build tree
Diffstat (limited to 'cmd/zinject')
-rw-r--r-- | cmd/zinject/translate.c | 474 | ||||
-rw-r--r-- | cmd/zinject/zinject.c | 768 | ||||
-rw-r--r-- | cmd/zinject/zinject.h | 71 |
3 files changed, 1313 insertions, 0 deletions
diff --git a/cmd/zinject/translate.c b/cmd/zinject/translate.c new file mode 100644 index 000000000..c85e024b6 --- /dev/null +++ b/cmd/zinject/translate.c @@ -0,0 +1,474 @@ +/* + * CDDL HEADER START + * + * The contents of this file are subject to the terms of the + * Common Development and Distribution License (the "License"). + * You may not use this file except in compliance with the License. + * + * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE + * or http://www.opensolaris.org/os/licensing. + * See the License for the specific language governing permissions + * and limitations under the License. + * + * When distributing Covered Code, include this CDDL HEADER in each + * file and include the License file at usr/src/OPENSOLARIS.LICENSE. + * If applicable, add the following below this CDDL HEADER, with the + * fields enclosed by brackets "[]" replaced with your own identifying + * information: Portions Copyright [yyyy] [name of copyright owner] + * + * CDDL HEADER END + */ +/* + * Copyright 2008 Sun Microsystems, Inc. All rights reserved. + * Use is subject to license terms. + */ + +#include <libzfs.h> + +#undef verify /* both libzfs.h and zfs_context.h want to define this */ + +#include <sys/zfs_context.h> + +#include <errno.h> +#include <fcntl.h> +#include <stdarg.h> +#include <stddef.h> +#include <stdio.h> +#include <stdlib.h> +#include <strings.h> +#include <sys/file.h> +#include <sys/mntent.h> +#include <sys/mnttab.h> +#include <sys/param.h> +#include <sys/stat.h> + +#include <sys/dmu.h> +#include <sys/dmu_objset.h> +#include <sys/dnode.h> +#include <sys/vdev_impl.h> + +#include <sys/mkdev.h> + +#include "zinject.h" + +extern void kernel_init(int); +extern void kernel_fini(void); + +static int debug; + +static void +ziprintf(const char *fmt, ...) +{ + va_list ap; + + if (!debug) + return; + + va_start(ap, fmt); + (void) vprintf(fmt, ap); + va_end(ap); +} + +/* + * Given a full path to a file, translate into a dataset name and a relative + * path within the dataset. 'dataset' must be at least MAXNAMELEN characters, + * and 'relpath' must be at least MAXPATHLEN characters. We also pass a stat64 + * buffer, which we need later to get the object ID. + */ +static int +parse_pathname(const char *fullpath, char *dataset, char *relpath, + struct stat64 *statbuf) +{ + struct extmnttab mp; + FILE *fp; + int match; + const char *rel; + + if (fullpath[0] != '/') { + (void) fprintf(stderr, "invalid object '%s': must be full " + "path\n", fullpath); + usage(); + return (-1); + } + + if (strlen(fullpath) >= MAXPATHLEN) { + (void) fprintf(stderr, "invalid object; pathname too long\n"); + return (-1); + } + + if (stat64(fullpath, statbuf) != 0) { + (void) fprintf(stderr, "cannot open '%s': %s\n", + fullpath, strerror(errno)); + return (-1); + } + + if ((fp = fopen(MNTTAB, "r")) == NULL) { + (void) fprintf(stderr, "cannot open /etc/mnttab\n"); + return (-1); + } + + match = 0; + while (getextmntent(fp, &mp, sizeof (mp)) == 0) { + if (makedev(mp.mnt_major, mp.mnt_minor) == statbuf->st_dev) { + match = 1; + break; + } + } + + if (!match) { + (void) fprintf(stderr, "cannot find mountpoint for '%s'\n", + fullpath); + return (-1); + } + + if (strcmp(mp.mnt_fstype, MNTTYPE_ZFS) != 0) { + (void) fprintf(stderr, "invalid path '%s': not a ZFS " + "filesystem\n", fullpath); + return (-1); + } + + if (strncmp(fullpath, mp.mnt_mountp, strlen(mp.mnt_mountp)) != 0) { + (void) fprintf(stderr, "invalid path '%s': mountpoint " + "doesn't match path\n", fullpath); + return (-1); + } + + (void) strcpy(dataset, mp.mnt_special); + + rel = fullpath + strlen(mp.mnt_mountp); + if (rel[0] == '/') + rel++; + (void) strcpy(relpath, rel); + + return (0); +} + +/* + * Convert from a (dataset, path) pair into a (objset, object) pair. Note that + * we grab the object number from the inode number, since looking this up via + * libzpool is a real pain. + */ +/* ARGSUSED */ +static int +object_from_path(const char *dataset, const char *path, struct stat64 *statbuf, + zinject_record_t *record) +{ + objset_t *os; + int err; + + /* + * Before doing any libzpool operations, call sync() to ensure that the + * on-disk state is consistent with the in-core state. + */ + sync(); + + if ((err = dmu_objset_open(dataset, DMU_OST_ZFS, + DS_MODE_USER | DS_MODE_READONLY, &os)) != 0) { + (void) fprintf(stderr, "cannot open dataset '%s': %s\n", + dataset, strerror(err)); + return (-1); + } + + record->zi_objset = dmu_objset_id(os); + record->zi_object = statbuf->st_ino; + + dmu_objset_close(os); + + return (0); +} + +/* + * Calculate the real range based on the type, level, and range given. + */ +static int +calculate_range(const char *dataset, err_type_t type, int level, char *range, + zinject_record_t *record) +{ + objset_t *os = NULL; + dnode_t *dn = NULL; + int err; + int ret = -1; + + /* + * Determine the numeric range from the string. + */ + if (range == NULL) { + /* + * If range is unspecified, set the range to [0,-1], which + * indicates that the whole object should be treated as an + * error. + */ + record->zi_start = 0; + record->zi_end = -1ULL; + } else { + char *end; + + /* XXX add support for suffixes */ + record->zi_start = strtoull(range, &end, 10); + + + if (*end == '\0') + record->zi_end = record->zi_start + 1; + else if (*end == ',') + record->zi_end = strtoull(end + 1, &end, 10); + + if (*end != '\0') { + (void) fprintf(stderr, "invalid range '%s': must be " + "a numeric range of the form 'start[,end]'\n", + range); + goto out; + } + } + + switch (type) { + case TYPE_DATA: + break; + + case TYPE_DNODE: + /* + * If this is a request to inject faults into the dnode, then we + * must translate the current (objset,object) pair into an + * offset within the metadnode for the objset. Specifying any + * kind of range with type 'dnode' is illegal. + */ + if (range != NULL) { + (void) fprintf(stderr, "range cannot be specified when " + "type is 'dnode'\n"); + goto out; + } + + record->zi_start = record->zi_object * sizeof (dnode_phys_t); + record->zi_end = record->zi_start + sizeof (dnode_phys_t); + record->zi_object = 0; + break; + } + + /* + * Get the dnode associated with object, so we can calculate the block + * size. + */ + if ((err = dmu_objset_open(dataset, DMU_OST_ANY, + DS_MODE_USER | DS_MODE_READONLY, &os)) != 0) { + (void) fprintf(stderr, "cannot open dataset '%s': %s\n", + dataset, strerror(err)); + goto out; + } + + if (record->zi_object == 0) { + dn = os->os->os_meta_dnode; + } else { + err = dnode_hold(os->os, record->zi_object, FTAG, &dn); + if (err != 0) { + (void) fprintf(stderr, "failed to hold dnode " + "for object %llu\n", + (u_longlong_t)record->zi_object); + goto out; + } + } + + + ziprintf("data shift: %d\n", (int)dn->dn_datablkshift); + ziprintf(" ind shift: %d\n", (int)dn->dn_indblkshift); + + /* + * Translate range into block IDs. + */ + if (record->zi_start != 0 || record->zi_end != -1ULL) { + record->zi_start >>= dn->dn_datablkshift; + record->zi_end >>= dn->dn_datablkshift; + } + + /* + * Check level, and then translate level 0 blkids into ranges + * appropriate for level of indirection. + */ + record->zi_level = level; + if (level > 0) { + ziprintf("level 0 blkid range: [%llu, %llu]\n", + record->zi_start, record->zi_end); + + if (level >= dn->dn_nlevels) { + (void) fprintf(stderr, "level %d exceeds max level " + "of object (%d)\n", level, dn->dn_nlevels - 1); + goto out; + } + + if (record->zi_start != 0 || record->zi_end != 0) { + int shift = dn->dn_indblkshift - SPA_BLKPTRSHIFT; + + for (; level > 0; level--) { + record->zi_start >>= shift; + record->zi_end >>= shift; + } + } + } + + ret = 0; +out: + if (dn) { + if (dn != os->os->os_meta_dnode) + dnode_rele(dn, FTAG); + } + if (os) + dmu_objset_close(os); + + return (ret); +} + +int +translate_record(err_type_t type, const char *object, const char *range, + int level, zinject_record_t *record, char *poolname, char *dataset) +{ + char path[MAXPATHLEN]; + char *slash; + struct stat64 statbuf; + int ret = -1; + + kernel_init(FREAD); + + debug = (getenv("ZINJECT_DEBUG") != NULL); + + ziprintf("translating: %s\n", object); + + if (MOS_TYPE(type)) { + /* + * MOS objects are treated specially. + */ + switch (type) { + case TYPE_MOS: + record->zi_type = 0; + break; + case TYPE_MOSDIR: + record->zi_type = DMU_OT_OBJECT_DIRECTORY; + break; + case TYPE_METASLAB: + record->zi_type = DMU_OT_OBJECT_ARRAY; + break; + case TYPE_CONFIG: + record->zi_type = DMU_OT_PACKED_NVLIST; + break; + case TYPE_BPLIST: + record->zi_type = DMU_OT_BPLIST; + break; + case TYPE_SPACEMAP: + record->zi_type = DMU_OT_SPACE_MAP; + break; + case TYPE_ERRLOG: + record->zi_type = DMU_OT_ERROR_LOG; + break; + } + + dataset[0] = '\0'; + (void) strcpy(poolname, object); + return (0); + } + + /* + * Convert a full path into a (dataset, file) pair. + */ + if (parse_pathname(object, dataset, path, &statbuf) != 0) + goto err; + + ziprintf(" dataset: %s\n", dataset); + ziprintf(" path: %s\n", path); + + /* + * Convert (dataset, file) into (objset, object) + */ + if (object_from_path(dataset, path, &statbuf, record) != 0) + goto err; + + ziprintf("raw objset: %llu\n", record->zi_objset); + ziprintf("raw object: %llu\n", record->zi_object); + + /* + * For the given object, calculate the real (type, level, range) + */ + if (calculate_range(dataset, type, level, (char *)range, record) != 0) + goto err; + + ziprintf(" objset: %llu\n", record->zi_objset); + ziprintf(" object: %llu\n", record->zi_object); + if (record->zi_start == 0 && + record->zi_end == -1ULL) + ziprintf(" range: all\n"); + else + ziprintf(" range: [%llu, %llu]\n", record->zi_start, + record->zi_end); + + /* + * Copy the pool name + */ + (void) strcpy(poolname, dataset); + if ((slash = strchr(poolname, '/')) != NULL) + *slash = '\0'; + + ret = 0; + +err: + kernel_fini(); + return (ret); +} + +int +translate_raw(const char *str, zinject_record_t *record) +{ + /* + * A raw bookmark of the form objset:object:level:blkid, where each + * number is a hexidecimal value. + */ + if (sscanf(str, "%llx:%llx:%x:%llx", (u_longlong_t *)&record->zi_objset, + (u_longlong_t *)&record->zi_object, &record->zi_level, + (u_longlong_t *)&record->zi_start) != 4) { + (void) fprintf(stderr, "bad raw spec '%s': must be of the form " + "'objset:object:level:blkid'\n", str); + return (-1); + } + + record->zi_end = record->zi_start; + + return (0); +} + +int +translate_device(const char *pool, const char *device, err_type_t label_type, + zinject_record_t *record) +{ + char *end; + zpool_handle_t *zhp; + nvlist_t *tgt; + boolean_t isspare, iscache; + + /* + * Given a device name or GUID, create an appropriate injection record + * with zi_guid set. + */ + if ((zhp = zpool_open(g_zfs, pool)) == NULL) + return (-1); + + record->zi_guid = strtoull(device, &end, 16); + if (record->zi_guid == 0 || *end != '\0') { + tgt = zpool_find_vdev(zhp, device, &isspare, &iscache, NULL); + + if (tgt == NULL) { + (void) fprintf(stderr, "cannot find device '%s' in " + "pool '%s'\n", device, pool); + return (-1); + } + + verify(nvlist_lookup_uint64(tgt, ZPOOL_CONFIG_GUID, + &record->zi_guid) == 0); + } + + switch (label_type) { + case TYPE_LABEL_UBERBLOCK: + record->zi_start = offsetof(vdev_label_t, vl_uberblock[0]); + record->zi_end = record->zi_start + VDEV_UBERBLOCK_RING - 1; + break; + case TYPE_LABEL_NVLIST: + record->zi_start = offsetof(vdev_label_t, vl_vdev_phys); + record->zi_end = record->zi_start + VDEV_PHYS_SIZE - 1; + break; + } + return (0); +} diff --git a/cmd/zinject/zinject.c b/cmd/zinject/zinject.c new file mode 100644 index 000000000..02dc18b9c --- /dev/null +++ b/cmd/zinject/zinject.c @@ -0,0 +1,768 @@ +/* + * CDDL HEADER START + * + * The contents of this file are subject to the terms of the + * Common Development and Distribution License (the "License"). + * You may not use this file except in compliance with the License. + * + * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE + * or http://www.opensolaris.org/os/licensing. + * See the License for the specific language governing permissions + * and limitations under the License. + * + * When distributing Covered Code, include this CDDL HEADER in each + * file and include the License file at usr/src/OPENSOLARIS.LICENSE. + * If applicable, add the following below this CDDL HEADER, with the + * fields enclosed by brackets "[]" replaced with your own identifying + * information: Portions Copyright [yyyy] [name of copyright owner] + * + * CDDL HEADER END + */ +/* + * Copyright 2008 Sun Microsystems, Inc. All rights reserved. + * Use is subject to license terms. + */ + +#pragma ident "%Z%%M% %I% %E% SMI" + +/* + * ZFS Fault Injector + * + * This userland component takes a set of options and uses libzpool to translate + * from a user-visible object type and name to an internal representation. + * There are two basic types of faults: device faults and data faults. + * + * + * DEVICE FAULTS + * + * Errors can be injected into a particular vdev using the '-d' option. This + * option takes a path or vdev GUID to uniquely identify the device within a + * pool. There are two types of errors that can be injected, EIO and ENXIO, + * that can be controlled through the '-e' option. The default is ENXIO. For + * EIO failures, any attempt to read data from the device will return EIO, but + * subsequent attempt to reopen the device will succeed. For ENXIO failures, + * any attempt to read from the device will return EIO, but any attempt to + * reopen the device will also return ENXIO. + * For label faults, the -L option must be specified. This allows faults + * to be injected into either the nvlist or uberblock region of all the labels + * for the specified device. + * + * This form of the command looks like: + * + * zinject -d device [-e errno] [-L <uber | nvlist>] pool + * + * + * DATA FAULTS + * + * We begin with a tuple of the form: + * + * <type,level,range,object> + * + * type A string describing the type of data to target. Each type + * implicitly describes how to interpret 'object'. Currently, + * the following values are supported: + * + * data User data for a file + * dnode Dnode for a file or directory + * + * The following MOS objects are special. Instead of injecting + * errors on a particular object or blkid, we inject errors across + * all objects of the given type. + * + * mos Any data in the MOS + * mosdir object directory + * config pool configuration + * bplist blkptr list + * spacemap spacemap + * metaslab metaslab + * errlog persistent error log + * + * level Object level. Defaults to '0', not applicable to all types. If + * a range is given, this corresponds to the indirect block + * corresponding to the specific range. + * + * range A numerical range [start,end) within the object. Defaults to + * the full size of the file. + * + * object A string describing the logical location of the object. For + * files and directories (currently the only supported types), + * this is the path of the object on disk. + * + * This is translated, via libzpool, into the following internal representation: + * + * <type,objset,object,level,range> + * + * These types should be self-explanatory. This tuple is then passed to the + * kernel via a special ioctl() to initiate fault injection for the given + * object. Note that 'type' is not strictly necessary for fault injection, but + * is used when translating existing faults into a human-readable string. + * + * + * The command itself takes one of the forms: + * + * zinject + * zinject <-a | -u pool> + * zinject -c <id|all> + * zinject [-q] <-t type> [-f freq] [-u] [-a] [-m] [-e errno] [-l level] + * [-r range] <object> + * zinject [-f freq] [-a] [-m] [-u] -b objset:object:level:start:end pool + * + * With no arguments, the command prints all currently registered injection + * handlers, with their numeric identifiers. + * + * The '-c' option will clear the given handler, or all handlers if 'all' is + * specified. + * + * The '-e' option takes a string describing the errno to simulate. This must + * be either 'io' or 'checksum'. In most cases this will result in the same + * behavior, but RAID-Z will produce a different set of ereports for this + * situation. + * + * The '-a', '-u', and '-m' flags toggle internal flush behavior. If '-a' is + * specified, then the ARC cache is flushed appropriately. If '-u' is + * specified, then the underlying SPA is unloaded. Either of these flags can be + * specified independently of any other handlers. The '-m' flag automatically + * does an unmount and remount of the underlying dataset to aid in flushing the + * cache. + * + * The '-f' flag controls the frequency of errors injected, expressed as a + * integer percentage between 1 and 100. The default is 100. + * + * The this form is responsible for actually injecting the handler into the + * framework. It takes the arguments described above, translates them to the + * internal tuple using libzpool, and then issues an ioctl() to register the + * handler. + * + * The final form can target a specific bookmark, regardless of whether a + * human-readable interface has been designed. It allows developers to specify + * a particular block by number. + */ + +#include <errno.h> +#include <fcntl.h> +#include <stdio.h> +#include <stdlib.h> +#include <strings.h> +#include <unistd.h> + +#include <sys/fs/zfs.h> +#include <sys/mount.h> + +#include <libzfs.h> + +#undef verify /* both libzfs.h and zfs_context.h want to define this */ + +#include "zinject.h" + +libzfs_handle_t *g_zfs; +int zfs_fd; + +#define ECKSUM EBADE + +static const char *errtable[TYPE_INVAL] = { + "data", + "dnode", + "mos", + "mosdir", + "metaslab", + "config", + "bplist", + "spacemap", + "errlog", + "uber", + "nvlist" +}; + +static err_type_t +name_to_type(const char *arg) +{ + int i; + for (i = 0; i < TYPE_INVAL; i++) + if (strcmp(errtable[i], arg) == 0) + return (i); + + return (TYPE_INVAL); +} + +static const char * +type_to_name(uint64_t type) +{ + switch (type) { + case DMU_OT_OBJECT_DIRECTORY: + return ("mosdir"); + case DMU_OT_OBJECT_ARRAY: + return ("metaslab"); + case DMU_OT_PACKED_NVLIST: + return ("config"); + case DMU_OT_BPLIST: + return ("bplist"); + case DMU_OT_SPACE_MAP: + return ("spacemap"); + case DMU_OT_ERROR_LOG: + return ("errlog"); + default: + return ("-"); + } +} + + +/* + * Print usage message. + */ +void +usage(void) +{ + (void) printf( + "usage:\n" + "\n" + "\tzinject\n" + "\n" + "\t\tList all active injection records.\n" + "\n" + "\tzinject -c <id|all>\n" + "\n" + "\t\tClear the particular record (if given a numeric ID), or\n" + "\t\tall records if 'all' is specificed.\n" + "\n" + "\tzinject -d device [-e errno] [-L <nvlist|uber>] pool\n" + "\t\tInject a fault into a particular device or the device's\n" + "\t\tlabel. Label injection can either be 'nvlist' or 'uber'.\n" + "\t\t'errno' can either be 'nxio' (the default) or 'io'.\n" + "\n" + "\tzinject -b objset:object:level:blkid pool\n" + "\n" + "\t\tInject an error into pool 'pool' with the numeric bookmark\n" + "\t\tspecified by the remaining tuple. Each number is in\n" + "\t\thexidecimal, and only one block can be specified.\n" + "\n" + "\tzinject [-q] <-t type> [-e errno] [-l level] [-r range]\n" + "\t [-a] [-m] [-u] [-f freq] <object>\n" + "\n" + "\t\tInject an error into the object specified by the '-t' option\n" + "\t\tand the object descriptor. The 'object' parameter is\n" + "\t\tinterperted depending on the '-t' option.\n" + "\n" + "\t\t-q\tQuiet mode. Only print out the handler number added.\n" + "\t\t-e\tInject a specific error. Must be either 'io' or\n" + "\t\t\t'checksum'. Default is 'io'.\n" + "\t\t-l\tInject error at a particular block level. Default is " + "0.\n" + "\t\t-m\tAutomatically remount underlying filesystem.\n" + "\t\t-r\tInject error over a particular logical range of an\n" + "\t\t\tobject. Will be translated to the appropriate blkid\n" + "\t\t\trange according to the object's properties.\n" + "\t\t-a\tFlush the ARC cache. Can be specified without any\n" + "\t\t\tassociated object.\n" + "\t\t-u\tUnload the associated pool. Can be specified with only\n" + "\t\t\ta pool object.\n" + "\t\t-f\tOnly inject errors a fraction of the time. Expressed as\n" + "\t\t\ta percentage between 1 and 100.\n" + "\n" + "\t-t data\t\tInject an error into the plain file contents of a\n" + "\t\t\tfile. The object must be specified as a complete path\n" + "\t\t\tto a file on a ZFS filesystem.\n" + "\n" + "\t-t dnode\tInject an error into the metadnode in the block\n" + "\t\t\tcorresponding to the dnode for a file or directory. The\n" + "\t\t\t'-r' option is incompatible with this mode. The object\n" + "\t\t\tis specified as a complete path to a file or directory\n" + "\t\t\ton a ZFS filesystem.\n" + "\n" + "\t-t <mos>\tInject errors into the MOS for objects of the given\n" + "\t\t\ttype. Valid types are: mos, mosdir, config, bplist,\n" + "\t\t\tspacemap, metaslab, errlog. The only valid <object> is\n" + "\t\t\tthe poolname.\n"); +} + +static int +iter_handlers(int (*func)(int, const char *, zinject_record_t *, void *), + void *data) +{ + zfs_cmd_t zc; + int ret; + + zc.zc_guid = 0; + + while (ioctl(zfs_fd, ZFS_IOC_INJECT_LIST_NEXT, &zc) == 0) + if ((ret = func((int)zc.zc_guid, zc.zc_name, + &zc.zc_inject_record, data)) != 0) + return (ret); + + return (0); +} + +static int +print_data_handler(int id, const char *pool, zinject_record_t *record, + void *data) +{ + int *count = data; + + if (record->zi_guid != 0) + return (0); + + if (*count == 0) { + (void) printf("%3s %-15s %-6s %-6s %-8s %3s %-15s\n", + "ID", "POOL", "OBJSET", "OBJECT", "TYPE", "LVL", "RANGE"); + (void) printf("--- --------------- ------ " + "------ -------- --- ---------------\n"); + } + + *count += 1; + + (void) printf("%3d %-15s %-6llu %-6llu %-8s %3d ", id, pool, + (u_longlong_t)record->zi_objset, (u_longlong_t)record->zi_object, + type_to_name(record->zi_type), record->zi_level); + + if (record->zi_start == 0 && + record->zi_end == -1ULL) + (void) printf("all\n"); + else + (void) printf("[%llu, %llu]\n", (u_longlong_t)record->zi_start, + (u_longlong_t)record->zi_end); + + return (0); +} + +static int +print_device_handler(int id, const char *pool, zinject_record_t *record, + void *data) +{ + int *count = data; + + if (record->zi_guid == 0) + return (0); + + if (*count == 0) { + (void) printf("%3s %-15s %s\n", "ID", "POOL", "GUID"); + (void) printf("--- --------------- ----------------\n"); + } + + *count += 1; + + (void) printf("%3d %-15s %llx\n", id, pool, + (u_longlong_t)record->zi_guid); + + return (0); +} + +/* + * Print all registered error handlers. Returns the number of handlers + * registered. + */ +static int +print_all_handlers(void) +{ + int count = 0; + + (void) iter_handlers(print_device_handler, &count); + (void) printf("\n"); + count = 0; + (void) iter_handlers(print_data_handler, &count); + + return (count); +} + +/* ARGSUSED */ +static int +cancel_one_handler(int id, const char *pool, zinject_record_t *record, + void *data) +{ + zfs_cmd_t zc; + + zc.zc_guid = (uint64_t)id; + + if (ioctl(zfs_fd, ZFS_IOC_CLEAR_FAULT, &zc) != 0) { + (void) fprintf(stderr, "failed to remove handler %d: %s\n", + id, strerror(errno)); + return (1); + } + + return (0); +} + +/* + * Remove all fault injection handlers. + */ +static int +cancel_all_handlers(void) +{ + int ret = iter_handlers(cancel_one_handler, NULL); + + (void) printf("removed all registered handlers\n"); + + return (ret); +} + +/* + * Remove a specific fault injection handler. + */ +static int +cancel_handler(int id) +{ + zfs_cmd_t zc; + + zc.zc_guid = (uint64_t)id; + + if (ioctl(zfs_fd, ZFS_IOC_CLEAR_FAULT, &zc) != 0) { + (void) fprintf(stderr, "failed to remove handler %d: %s\n", + id, strerror(errno)); + return (1); + } + + (void) printf("removed handler %d\n", id); + + return (0); +} + +/* + * Register a new fault injection handler. + */ +static int +register_handler(const char *pool, int flags, zinject_record_t *record, + int quiet) +{ + zfs_cmd_t zc; + + (void) strcpy(zc.zc_name, pool); + zc.zc_inject_record = *record; + zc.zc_guid = flags; + + if (ioctl(zfs_fd, ZFS_IOC_INJECT_FAULT, &zc) != 0) { + (void) fprintf(stderr, "failed to add handler: %s\n", + strerror(errno)); + return (1); + } + + if (flags & ZINJECT_NULL) + return (0); + + if (quiet) { + (void) printf("%llu\n", (u_longlong_t)zc.zc_guid); + } else { + (void) printf("Added handler %llu with the following " + "properties:\n", (u_longlong_t)zc.zc_guid); + (void) printf(" pool: %s\n", pool); + if (record->zi_guid) { + (void) printf(" vdev: %llx\n", + (u_longlong_t)record->zi_guid); + } else { + (void) printf("objset: %llu\n", + (u_longlong_t)record->zi_objset); + (void) printf("object: %llu\n", + (u_longlong_t)record->zi_object); + (void) printf(" type: %llu\n", + (u_longlong_t)record->zi_type); + (void) printf(" level: %d\n", record->zi_level); + if (record->zi_start == 0 && + record->zi_end == -1ULL) + (void) printf(" range: all\n"); + else + (void) printf(" range: [%llu, %llu)\n", + (u_longlong_t)record->zi_start, + (u_longlong_t)record->zi_end); + } + } + + return (0); +} + +int +main(int argc, char **argv) +{ + int c; + char *range = NULL; + char *cancel = NULL; + char *end; + char *raw = NULL; + char *device = NULL; + int level = 0; + int quiet = 0; + int error = 0; + int domount = 0; + err_type_t type = TYPE_INVAL; + err_type_t label = TYPE_INVAL; + zinject_record_t record = { 0 }; + char pool[MAXNAMELEN]; + char dataset[MAXNAMELEN]; + zfs_handle_t *zhp; + int ret; + int flags = 0; + + if ((g_zfs = libzfs_init()) == NULL) { + (void) fprintf(stderr, "internal error: failed to " + "initialize ZFS library\n"); + return (1); + } + + libzfs_print_on_error(g_zfs, B_TRUE); + + if ((zfs_fd = open(ZFS_DEV, O_RDWR)) < 0) { + (void) fprintf(stderr, "failed to open ZFS device\n"); + return (1); + } + + if (argc == 1) { + /* + * No arguments. Print the available handlers. If there are no + * available handlers, direct the user to '-h' for help + * information. + */ + if (print_all_handlers() == 0) { + (void) printf("No handlers registered.\n"); + (void) printf("Run 'zinject -h' for usage " + "information.\n"); + } + + return (0); + } + + while ((c = getopt(argc, argv, ":ab:d:f:qhc:t:l:mr:e:uL:")) != -1) { + switch (c) { + case 'a': + flags |= ZINJECT_FLUSH_ARC; + break; + case 'b': + raw = optarg; + break; + case 'c': + cancel = optarg; + break; + case 'd': + device = optarg; + break; + case 'e': + if (strcasecmp(optarg, "io") == 0) { + error = EIO; + } else if (strcasecmp(optarg, "checksum") == 0) { + error = ECKSUM; + } else if (strcasecmp(optarg, "nxio") == 0) { + error = ENXIO; + } else { + (void) fprintf(stderr, "invalid error type " + "'%s': must be 'io', 'checksum' or " + "'nxio'\n", optarg); + usage(); + return (1); + } + break; + case 'f': + record.zi_freq = atoi(optarg); + if (record.zi_freq < 1 || record.zi_freq > 100) { + (void) fprintf(stderr, "frequency range must " + "be in the range (0, 100]\n"); + return (1); + } + break; + case 'h': + usage(); + return (0); + case 'l': + level = (int)strtol(optarg, &end, 10); + if (*end != '\0') { + (void) fprintf(stderr, "invalid level '%s': " + "must be an integer\n", optarg); + usage(); + return (1); + } + break; + case 'm': + domount = 1; + break; + case 'q': + quiet = 1; + break; + case 'r': + range = optarg; + break; + case 't': + if ((type = name_to_type(optarg)) == TYPE_INVAL && + !MOS_TYPE(type)) { + (void) fprintf(stderr, "invalid type '%s'\n", + optarg); + usage(); + return (1); + } + break; + case 'u': + flags |= ZINJECT_UNLOAD_SPA; + break; + case 'L': + if ((label = name_to_type(optarg)) == TYPE_INVAL && + !LABEL_TYPE(type)) { + (void) fprintf(stderr, "invalid label type " + "'%s'\n", optarg); + usage(); + return (1); + } + break; + case ':': + (void) fprintf(stderr, "option -%c requires an " + "operand\n", optopt); + usage(); + return (1); + case '?': + (void) fprintf(stderr, "invalid option '%c'\n", + optopt); + usage(); + return (2); + } + } + + argc -= optind; + argv += optind; + + if (cancel != NULL) { + /* + * '-c' is invalid with any other options. + */ + if (raw != NULL || range != NULL || type != TYPE_INVAL || + level != 0) { + (void) fprintf(stderr, "cancel (-c) incompatible with " + "any other options\n"); + usage(); + return (2); + } + if (argc != 0) { + (void) fprintf(stderr, "extraneous argument to '-c'\n"); + usage(); + return (2); + } + + if (strcmp(cancel, "all") == 0) { + return (cancel_all_handlers()); + } else { + int id = (int)strtol(cancel, &end, 10); + if (*end != '\0') { + (void) fprintf(stderr, "invalid handle id '%s':" + " must be an integer or 'all'\n", cancel); + usage(); + return (1); + } + return (cancel_handler(id)); + } + } + + if (device != NULL) { + /* + * Device (-d) injection uses a completely different mechanism + * for doing injection, so handle it separately here. + */ + if (raw != NULL || range != NULL || type != TYPE_INVAL || + level != 0) { + (void) fprintf(stderr, "device (-d) incompatible with " + "data error injection\n"); + usage(); + return (2); + } + + if (argc != 1) { + (void) fprintf(stderr, "device (-d) injection requires " + "a single pool name\n"); + usage(); + return (2); + } + + (void) strcpy(pool, argv[0]); + dataset[0] = '\0'; + + if (error == ECKSUM) { + (void) fprintf(stderr, "device error type must be " + "'io' or 'nxio'\n"); + return (1); + } + + if (translate_device(pool, device, label, &record) != 0) + return (1); + if (!error) + error = ENXIO; + } else if (raw != NULL) { + if (range != NULL || type != TYPE_INVAL || level != 0) { + (void) fprintf(stderr, "raw (-b) format with " + "any other options\n"); + usage(); + return (2); + } + + if (argc != 1) { + (void) fprintf(stderr, "raw (-b) format expects a " + "single pool name\n"); + usage(); + return (2); + } + + (void) strcpy(pool, argv[0]); + dataset[0] = '\0'; + + if (error == ENXIO) { + (void) fprintf(stderr, "data error type must be " + "'checksum' or 'io'\n"); + return (1); + } + + if (translate_raw(raw, &record) != 0) + return (1); + if (!error) + error = EIO; + } else if (type == TYPE_INVAL) { + if (flags == 0) { + (void) fprintf(stderr, "at least one of '-b', '-d', " + "'-t', '-a', or '-u' must be specified\n"); + usage(); + return (2); + } + + if (argc == 1 && (flags & ZINJECT_UNLOAD_SPA)) { + (void) strcpy(pool, argv[0]); + dataset[0] = '\0'; + } else if (argc != 0) { + (void) fprintf(stderr, "extraneous argument for " + "'-f'\n"); + usage(); + return (2); + } + + flags |= ZINJECT_NULL; + } else { + if (argc != 1) { + (void) fprintf(stderr, "missing object\n"); + usage(); + return (2); + } + + if (error == ENXIO) { + (void) fprintf(stderr, "data error type must be " + "'checksum' or 'io'\n"); + return (1); + } + + if (translate_record(type, argv[0], range, level, &record, pool, + dataset) != 0) + return (1); + if (!error) + error = EIO; + } + + /* + * If this is pool-wide metadata, unmount everything. The ioctl() will + * unload the pool, so that we trigger spa-wide reopen of metadata next + * time we access the pool. + */ + if (dataset[0] != '\0' && domount) { + if ((zhp = zfs_open(g_zfs, dataset, ZFS_TYPE_DATASET)) == NULL) + return (1); + + if (zfs_unmount(zhp, NULL, 0) != 0) + return (1); + } + + record.zi_error = error; + + ret = register_handler(pool, flags, &record, quiet); + + if (dataset[0] != '\0' && domount) + ret = (zfs_mount(zhp, NULL, 0) != 0); + + libzfs_fini(g_zfs); + + return (ret); +} diff --git a/cmd/zinject/zinject.h b/cmd/zinject/zinject.h new file mode 100644 index 000000000..adc3efe80 --- /dev/null +++ b/cmd/zinject/zinject.h @@ -0,0 +1,71 @@ +/* + * CDDL HEADER START + * + * The contents of this file are subject to the terms of the + * Common Development and Distribution License (the "License"). + * You may not use this file except in compliance with the License. + * + * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE + * or http://www.opensolaris.org/os/licensing. + * See the License for the specific language governing permissions + * and limitations under the License. + * + * When distributing Covered Code, include this CDDL HEADER in each + * file and include the License file at usr/src/OPENSOLARIS.LICENSE. + * If applicable, add the following below this CDDL HEADER, with the + * fields enclosed by brackets "[]" replaced with your own identifying + * information: Portions Copyright [yyyy] [name of copyright owner] + * + * CDDL HEADER END + */ +/* + * Copyright 2008 Sun Microsystems, Inc. All rights reserved. + * Use is subject to license terms. + */ + +#ifndef _ZINJECT_H +#define _ZINJECT_H + +#pragma ident "%Z%%M% %I% %E% SMI" + +#include <sys/zfs_ioctl.h> + +#ifdef __cplusplus +extern "C" { +#endif + +typedef enum { + TYPE_DATA, /* plain file contents */ + TYPE_DNODE, /* metadnode contents */ + TYPE_MOS, /* all MOS data */ + TYPE_MOSDIR, /* MOS object directory */ + TYPE_METASLAB, /* metaslab objects */ + TYPE_CONFIG, /* MOS config */ + TYPE_BPLIST, /* block pointer list */ + TYPE_SPACEMAP, /* space map objects */ + TYPE_ERRLOG, /* persistent error log */ + TYPE_LABEL_UBERBLOCK, /* label specific uberblock */ + TYPE_LABEL_NVLIST, /* label specific nvlist */ + TYPE_INVAL +} err_type_t; + +#define MOS_TYPE(t) \ + ((t) >= TYPE_MOS && (t) < TYPE_LABEL_UBERBLOCK) + +#define LABEL_TYPE(t) \ + ((t) >= TYPE_LABEL_UBERBLOCK && (t) < TYPE_INVAL) + +int translate_record(err_type_t type, const char *object, const char *range, + int level, zinject_record_t *record, char *poolname, char *dataset); +int translate_raw(const char *raw, zinject_record_t *record); +int translate_device(const char *pool, const char *device, + err_type_t label_type, zinject_record_t *record); +void usage(void); + +extern libzfs_handle_t *g_zfs; + +#ifdef __cplusplus +} +#endif + +#endif /* _ZINJECT_H */ |