Skip to content

Commit

Permalink
Add zfs_prepare_disk script for disk firmware install
Browse files Browse the repository at this point in the history
Have libzfs call a special `zfs_prepare_disk` script before a disk is
included into the pool.  The user can edit this script to add things
like a disk firmware update or a disk health check.  Use of the script
is totally optional. See the zfs_prepare_disk manpage for full details.

Signed-off-by: Tony Hutter <[email protected]>
  • Loading branch information
tonyhutter committed Sep 6, 2023
1 parent cad00d5 commit 88c3f0d
Show file tree
Hide file tree
Showing 10 changed files with 308 additions and 37 deletions.
6 changes: 4 additions & 2 deletions cmd/zed/agents/zfs_mod.c
Original file line number Diff line number Diff line change
Expand Up @@ -404,8 +404,10 @@ zfs_process_add(zpool_handle_t *zhp, nvlist_t *vdev, boolean_t labeled)
* If this is a request to label a whole disk, then attempt to
* write out the label.
*/
if (zpool_label_disk(g_zfshdl, zhp, leafname) != 0) {
zed_log_msg(LOG_INFO, " zpool_label_disk: could not "
if (zpool_prepare_and_label_disk(g_zfshdl, zhp, leafname,
vdev, "autoreplace") != 0) {
zed_log_msg(LOG_INFO,
" zpool_prepare_and_label_disk: could not "
"label '%s' (%s)", leafname,
libzfs_error_description(g_zfshdl));

Expand Down
35 changes: 7 additions & 28 deletions cmd/zpool/zpool_iter.c
Original file line number Diff line number Diff line change
Expand Up @@ -443,37 +443,22 @@ vdev_run_cmd(vdev_cmd_data_t *data, char *cmd)
{
int rc;
char *argv[2] = {cmd};
char *env[5] = {(char *)"PATH=/bin:/sbin:/usr/bin:/usr/sbin"};
char **env;
char **lines = NULL;
int lines_cnt = 0;
int i;

/* Setup our custom environment variables */
rc = asprintf(&env[1], "VDEV_PATH=%s",
data->path ? data->path : "");
if (rc == -1) {
env[1] = NULL;
env = zpool_vdev_script_alloc_env(data->pool, data->path, data->upath,
data->vdev_enc_sysfs_path, NULL, NULL);
if (env == NULL)
goto out;
}

rc = asprintf(&env[2], "VDEV_UPATH=%s",
data->upath ? data->upath : "");
if (rc == -1) {
env[2] = NULL;
goto out;
}

rc = asprintf(&env[3], "VDEV_ENC_SYSFS_PATH=%s",
data->vdev_enc_sysfs_path ?
data->vdev_enc_sysfs_path : "");
if (rc == -1) {
env[3] = NULL;
goto out;
}

/* Run the command */
rc = libzfs_run_process_get_stdout_nopath(cmd, argv, env, &lines,
&lines_cnt);

zpool_vdev_script_free_env(env);

if (rc != 0)
goto out;

Expand All @@ -485,10 +470,6 @@ vdev_run_cmd(vdev_cmd_data_t *data, char *cmd)
out:
if (lines != NULL)
libzfs_free_str_array(lines, lines_cnt);

/* Start with i = 1 since env[0] was statically allocated */
for (i = 1; i < ARRAY_SIZE(env); i++)
free(env[i]);
}

/*
Expand Down Expand Up @@ -547,7 +528,6 @@ vdev_run_cmd_thread(void *cb_cmd_data)
if (snprintf(fullpath, sizeof (fullpath),
"%s/%s", dir, cmd) == -1)
continue;

if (access(fullpath, X_OK) == 0) {
vdev_run_cmd(data, fullpath);
break;
Expand Down Expand Up @@ -673,7 +653,6 @@ all_pools_for_each_vdev_run(int argc, char **argv, char *cmd,
vdev_cmd_data_list_t *vcdl;
vcdl = safe_malloc(sizeof (vdev_cmd_data_list_t));
vcdl->cmd = cmd;

vcdl->vdev_names = vdev_names;
vcdl->vdev_names_count = vdev_names_count;
vcdl->cb_name_flags = cb_name_flags;
Expand Down
4 changes: 4 additions & 0 deletions cmd/zpool/zpool_util.h
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,10 @@ vdev_cmd_data_list_t *all_pools_for_each_vdev_run(int argc, char **argv,

void free_vdev_cmd_data_list(vdev_cmd_data_list_t *vcdl);

void free_vdev_cmd_data(vdev_cmd_data_t *data);

int vdev_run_cmd_simple(char *path, char *cmd);

int check_device(const char *path, boolean_t force,
boolean_t isspare, boolean_t iswholedisk);
boolean_t check_sector_size_database(char *path, int *sector_size);
Expand Down
19 changes: 12 additions & 7 deletions cmd/zpool/zpool_vdev.c
Original file line number Diff line number Diff line change
Expand Up @@ -947,7 +947,7 @@ zero_label(const char *path)
* need to get the devid after we label the disk.
*/
static int
make_disks(zpool_handle_t *zhp, nvlist_t *nv)
make_disks(zpool_handle_t *zhp, nvlist_t *nv, boolean_t replacing)
{
nvlist_t **child;
uint_t c, children;
Expand Down Expand Up @@ -1043,8 +1043,13 @@ make_disks(zpool_handle_t *zhp, nvlist_t *nv)
/*
* When labeling a pool the raw device node name
* is provided as it appears under /dev/.
*
* Note that 'zhp' will be NULL when we're creating a
* pool.
*/
if (zpool_label_disk(g_zfs, zhp, devnode) == -1)
if (zpool_prepare_and_label_disk(g_zfs, zhp, devnode, nv,

Check failure on line 1050 in cmd/zpool/zpool_vdev.c

View workflow job for this annotation

GitHub Actions / checkstyle

line > 80 characters
zhp == NULL ? "create" :
replacing ? "replace" : "add") == -1)
return (-1);

/*
Expand Down Expand Up @@ -1082,19 +1087,19 @@ make_disks(zpool_handle_t *zhp, nvlist_t *nv)
}

for (c = 0; c < children; c++)
if ((ret = make_disks(zhp, child[c])) != 0)
if ((ret = make_disks(zhp, child[c], replacing)) != 0)
return (ret);

if (nvlist_lookup_nvlist_array(nv, ZPOOL_CONFIG_SPARES,
&child, &children) == 0)
for (c = 0; c < children; c++)
if ((ret = make_disks(zhp, child[c])) != 0)
if ((ret = make_disks(zhp, child[c], replacing)) != 0)
return (ret);

if (nvlist_lookup_nvlist_array(nv, ZPOOL_CONFIG_L2CACHE,
&child, &children) == 0)
for (c = 0; c < children; c++)
if ((ret = make_disks(zhp, child[c])) != 0)
if ((ret = make_disks(zhp, child[c], replacing)) != 0)
return (ret);

return (0);
Expand Down Expand Up @@ -1752,7 +1757,7 @@ split_mirror_vdev(zpool_handle_t *zhp, char *newname, nvlist_t *props,
return (NULL);
}

if (!flags.dryrun && make_disks(zhp, newroot) != 0) {
if (!flags.dryrun && make_disks(zhp, newroot, B_FALSE) != 0) {
nvlist_free(newroot);
return (NULL);
}
Expand Down Expand Up @@ -1873,7 +1878,7 @@ make_root_vdev(zpool_handle_t *zhp, nvlist_t *props, int force, int check_rep,
/*
* Run through the vdev specification and label any whole disks found.
*/
if (!dryrun && make_disks(zhp, newroot) != 0) {
if (!dryrun && make_disks(zhp, newroot, replacing) != 0) {
nvlist_free(newroot);
return (NULL);
}
Expand Down
6 changes: 6 additions & 0 deletions include/libzfs.h
Original file line number Diff line number Diff line change
Expand Up @@ -325,6 +325,12 @@ _LIBZFS_H nvlist_t *zpool_find_vdev_by_physpath(zpool_handle_t *, const char *,
boolean_t *, boolean_t *, boolean_t *);
_LIBZFS_H int zpool_label_disk(libzfs_handle_t *, zpool_handle_t *,
const char *);
_LIBZFS_H int zpool_prepare_and_label_disk(libzfs_handle_t *, zpool_handle_t *,
const char *, nvlist_t *vdev_nv, const char *prepare_str);
_LIBZFS_H char ** zpool_vdev_script_alloc_env(const char *pool_name,
const char *vdev_path, const char *vdev_upath,
const char *vdev_enc_sysfs_path, const char *opt_key, const char *opt_val);
_LIBZFS_H void zpool_vdev_script_free_env(char **env);
_LIBZFS_H uint64_t zpool_vdev_path_to_guid(zpool_handle_t *zhp,
const char *path);

Expand Down
184 changes: 184 additions & 0 deletions lib/libzfs/os/linux/libzfs_pool_os.c
Original file line number Diff line number Diff line change
Expand Up @@ -338,3 +338,187 @@ zpool_label_disk(libzfs_handle_t *hdl, zpool_handle_t *zhp, const char *name)
}
return (0);
}

/* PATH + 5 env vars + a NULL entry = 7 */
#define ZPOOL_VDEV_SCRIPT_ENV_COUNT 7

Check failure on line 343 in lib/libzfs/os/linux/libzfs_pool_os.c

View workflow job for this annotation

GitHub Actions / checkstyle

#define followed by space instead of tab

/*
* There's a few places where ZFS will call external scripts (like the script
* in zpool.d/ and `zfs_prepare_disk`). These scripts are called from a
* with a reduced $PATH, and some vdev specific environment vars set. This
* function will allocate an populate the the enviorment variable array that
* is passed to these scripts. The user must free the arrays with
* zpool_vdev_script_boilerplate_free() when they are done.
*
* The following env vars will be set (but value could be blank):
*
* POOL_NAME
* VDEV_PATH
* VDEV_UPATH
* VDEV_ENC_SYSFS_PATH
*
* In addition, you can set an optional environment variable named 'opt_key'
* to 'opt_val' if you want.
*
* Returns allocated env[] array on success, NULL otherwise.
*/
char **
zpool_vdev_script_alloc_env(const char *pool_name,
const char *vdev_path, const char *vdev_upath,
const char *vdev_enc_sysfs_path, const char *opt_key, const char *opt_val)
{
char **env = NULL;
int rc;

env = calloc(ZPOOL_VDEV_SCRIPT_ENV_COUNT, sizeof(*env));

Check failure on line 373 in lib/libzfs/os/linux/libzfs_pool_os.c

View workflow job for this annotation

GitHub Actions / checkstyle

missing space between keyword and paren
if (!env)
return NULL;

Check failure on line 375 in lib/libzfs/os/linux/libzfs_pool_os.c

View workflow job for this annotation

GitHub Actions / checkstyle

unparenthesized return expression

env[0] = strdup("PATH=/bin:/sbin:/usr/bin:/usr/sbin");
if (!env[0])
goto error;

/* Setup our custom environment variables */
rc = asprintf(&env[1], "POOL_NAME=%s", pool_name ? pool_name : "");
if (rc == -1) {
env[1] = NULL;
goto error;
}

rc = asprintf(&env[2], "VDEV_PATH=%s", vdev_path ? vdev_path : "");
if (rc == -1) {
env[2] = NULL;
goto error;
}

rc = asprintf(&env[3], "VDEV_UPATH=%s", vdev_upath ? vdev_upath : "");
if (rc == -1) {
env[3] = NULL;
goto error;;

Check failure on line 397 in lib/libzfs/os/linux/libzfs_pool_os.c

View workflow job for this annotation

GitHub Actions / checkstyle

comma or semicolon followed by non-blank
}

rc = asprintf(&env[4], "VDEV_ENC_SYSFS_PATH=%s",
vdev_enc_sysfs_path ? vdev_enc_sysfs_path : "");
if (rc == -1) {
env[4] = NULL;
goto error;
}

if (opt_key != NULL) {
rc = asprintf(&env[5], "%s=%s", opt_key, opt_val ? opt_val : "");

Check failure on line 408 in lib/libzfs/os/linux/libzfs_pool_os.c

View workflow job for this annotation

GitHub Actions / checkstyle

line > 80 characters
if (rc == -1) {
env[5] = NULL;
goto error;
}
}

return env;

Check failure on line 415 in lib/libzfs/os/linux/libzfs_pool_os.c

View workflow job for this annotation

GitHub Actions / checkstyle

unparenthesized return expression

error:
for (int i = 0; i < ZPOOL_VDEV_SCRIPT_ENV_COUNT; i++)
free(env[i]);

free(env);

return NULL;

Check failure on line 423 in lib/libzfs/os/linux/libzfs_pool_os.c

View workflow job for this annotation

GitHub Actions / checkstyle

unparenthesized return expression
}

/*
* Free the env[] array that was allocated by zpool_vdev_script_alloc_env().
*/
void zpool_vdev_script_free_env(char **env)

Check failure on line 429 in lib/libzfs/os/linux/libzfs_pool_os.c

View workflow job for this annotation

GitHub Actions / checkstyle

return type of function not on separate line
{
for (int i = 0; i < ZPOOL_VDEV_SCRIPT_ENV_COUNT; i++)
free(env[i]);

free(env);
}

/*
* Prepare a disk by (optionally) running a program before labeling the disk.
* This can be useful for installing disk firmware or doing some pre-flight
* checks on the disk before it becomes part of the pool. The program run is
* located at LIBEXECDIR/zfs/zfs_prepare_disk (defaults to
* /usr/libexec/zfs/zfs_prepare_disk).
*
* Return 0 on success, non-zero on failure.
*/
static int
zpool_prepare_disk(const char *pool_name, nvlist_t *vdev_nv, const char *prepare_str)

Check failure on line 447 in lib/libzfs/os/linux/libzfs_pool_os.c

View workflow job for this annotation

GitHub Actions / checkstyle

line > 80 characters
{
const char *script_path = LIBEXECDIR "/zfs_prepare_disk";
int rc = 0;

/* Path to script and a NULL entry */
char *argv[2] = {(char *)script_path};
char **env = NULL;
const char *path = NULL, *enc_sysfs_path = NULL;
char *upath;

if (access(script_path, X_OK) != 0) {
/* No script, nothing to do */
return 0;
}

(void) nvlist_lookup_string(vdev_nv, ZPOOL_CONFIG_PATH, &path);
(void) nvlist_lookup_string(vdev_nv, ZPOOL_CONFIG_VDEV_ENC_SYSFS_PATH,
&enc_sysfs_path);

upath = zfs_get_underlying_path(path);

env = zpool_vdev_script_alloc_env(pool_name, path, upath,
enc_sysfs_path, "VDEV_PREPARE", prepare_str);

if (env == NULL) {
free(upath);
return ENOMEM;
}

rc = libzfs_run_process_get_stdout(script_path, argv, env, NULL, 0);
free(upath);

zpool_vdev_script_free_env(env);

return rc;
}

/*
* Optionally run a script and then label a disk. The script can be used to
* prepare a disk for inclusion into the pool. For example, it might update
* the disk's firmware or check it's health.
*
* The 'name' provided is the short name, stripped of any leading
* /dev path, and is passed to zpool_label_disk. vdev_nv is the nvlist for
* the vdev. prepare_str is a string that gets passed as the VDEV_PREPARE
* env variable to the script.
*
* The following env vars are passed to the script:
*
* POOL_NAME: The pool name (blank during zpool create)
* VDEV_PREPARE: Reason why the disk is being prepared for inclusion:
* "create", "add", "replace", or "autoreplace"
* VDEV_PATH: Path to the disk
* VDEV_UPATH: One of the 'underlying paths' to the disk. This is
* useful for DM devices.
* VDEV_ENC_SYSFS_PATH: Path to the disk's enclosure sysfs path, if available.
*
* Note, some of these values can be blank.
*
* Return 0 on success, non-zero otherwise.
*/
int
zpool_prepare_and_label_disk(libzfs_handle_t *hdl, zpool_handle_t *zhp, const char *name,
nvlist_t *vdev_nv, const char *prepare_str)
{
int rc;
char vdev_path[MAXPATHLEN];
(void) snprintf(vdev_path, sizeof (vdev_path), "%s/%s", DISK_ROOT, name);

/* zhp will be NULL when creating a pool */
rc = zpool_prepare_disk(zhp ? zpool_get_name(zhp) : NULL, vdev_nv, prepare_str);
if (rc != 0)
return rc;

zpool_label_disk(hdl, zhp, name);
return 0;
}
1 change: 1 addition & 0 deletions man/Makefile.am
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@ dist_man_MANS = \
%D%/man8/zfs-userspace.8 \
%D%/man8/zfs-wait.8 \
%D%/man8/zfs_ids_to_path.8 \
%D%/man8/zfs_prepare_disk.8 \
%D%/man8/zgenhostid.8 \
%D%/man8/zinject.8 \
%D%/man8/zpool.8 \
Expand Down
Loading

0 comments on commit 88c3f0d

Please sign in to comment.