/*
* 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 (c) 2011, 2012, Oracle and/or its affiliates. All rights reserved.
*/
/*
* Handle initialization of the suspend subsystem. This includes setting
* platform default values for the suspend-enable property and creating
* the cpr file.
*/
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>
#include <strings.h>
#include <libgen.h>
#include <sys/cpr.h>
#include <errno.h>
#include <sys/mkdev.h>
#include <sys/vtoc.h>
#include <sys/efi_partition.h>
#include <libnvpair.h>
#include <libuutil.h>
#include <libzfs.h>
#include <instzones_api.h>
#include <libpower.h>
#include <libpower_impl.h>
#ifdef sparc
#include <sys/openpromio.h>
#endif
int get_cpr_config_path(char **cpr_conf);
#ifdef sparc
static int utop(char *fs_name, char *prom_name);
#endif
static libzfs_handle_t *g_zfs = NULL;
typedef struct dir_data {
char *dir;
char *ds;
} dir_data_t;
#define ZFS_CLOSE(_zhp) \
if (_zhp) { \
zfs_close(_zhp); \
_zhp = NULL; \
}
#define PM_POOL_SEP "/ROOT"
static int be_get_ds_from_dir_callback(zfs_handle_t *zhp, void *data);
static char *be_get_ds_from_dir(char *dir);
/*
* Initialize any suspend facilities for this machine if needed or configured.
*/
pm_error_t
pm_init_suspend()
{
pm_error_t err;
boolean_t enabled = B_FALSE;
nvlist_t *proplist = NULL, *prop = NULL;
nvpair_t *nvp;
/*
* Retrieve all of the SMF properties to see if suspend-enabled is
* already configured in SMF. If it is, then the service has been
* started before and no re-setting of the default is required.
*/
errno = 0;
err = pm_smf_listprop(&proplist, PM_SVC_POWER);
if (err != PM_SUCCESS) {
/*
* An error occurred reading from SMF. Pass the error up
* to the caller to process.
*/
return (err);
}
if ((errno = nvlist_lookup_nvlist(proplist, PM_PROP_SUSPEND_ENABLE,
&prop)) != 0 || prop == NULL) {
/*
* The suspend-enable property is not yet initialized.
* Since this implies that suspend is not enabled,
* there is nothing left to do here, so return failure.
*/
nvlist_free(proplist);
return (PM_ERROR_PROPERTY_NOT_FOUND);
}
/* Recurse to find the value of the property */
if ((errno = nvlist_lookup_nvpair(prop, PM_AUTHORITY_SMF_STR,
&nvp)) == 0) {
errno = nvpair_value_boolean_value(nvp, &enabled);
}
/* Clean up */
nvlist_free(prop);
nvlist_free(proplist);
/* Things to run if suspend is enabled */
if (enabled && (pm_get_suspendenable() == B_TRUE)) {
/*
* Update the cprconfig file or create if it does not
* already exist.
*/
if (update_cprconfig() != PM_SUCCESS) {
if (errno == ENOTSUP)
return (PM_ERROR_NOT_SUPPORTED);
else
return (PM_ERROR_SYSTEM);
}
}
/*
* If we get here, always return success, as we have done
* what is needed to "initialize" suspend.
*/
return (PM_SUCCESS);
}
/*
* returns B_TRUE if the slice is good (e.g. does not start at block
* zero, or a string describing the error if it doesn't
*/
static boolean_t
is_good_slice(char *sfile, char **err)
{
int fd, rc;
struct vtoc vtoc;
dk_gpt_t *gpt;
char rdskname[MAXPATHLEN];
char *x, *y;
*err = NULL;
/* convert from dsk to rdsk */
(void) strlcpy(rdskname, sfile, sizeof (rdskname));
x = strstr(rdskname, "dsk/");
y = strstr(sfile, "dsk/");
if (x != NULL) {
*x++ = 'r';
(void) strcpy(x, y);
}
if ((fd = open(rdskname, O_RDONLY)) == -1) {
*err = "could not open '%s'\n";
} else if ((rc = read_vtoc(fd, &vtoc)) >= 0) {
/*
* we got a slice number; now check the block
* number where the slice starts
*/
if (vtoc.v_part[rc].p_start < 2)
*err = "using '%s' would clobber the disk label\n";
(void) close(fd);
return (*err ? B_FALSE : B_TRUE);
} else if ((rc == VT_ENOTSUP) &&
(efi_alloc_and_read(fd, &gpt)) >= 0) {
/* EFI slices don't clobber the disk label */
free(gpt);
(void) close(fd);
return (B_TRUE);
} else
*err = "could not read partition table from '%s'\n";
return (B_FALSE);
}
/*
* This should go in a header file, but for now it is private, so leave
* it here.
*/
#define PM_PROP_STATEFILE "suspend-statefile"
/*
* These describe the parent pathname to block disk devices used
* for the statefile. As there is no API for acquiring them, we
* define them here so that they are easily identified should they
* need to be updated.
*/
#define PM_SPECFSPATH "/dev/dsk/"
#define PM_ZVOLPATH "/dev/zvol/dsk/"
#define PM_DUMPNAME "dump"
/*
* Get and verify the device for saving a statefile.
* If there is not a defined property, choose the ZFS dump device.
*/
static void
sfpath(struct cprconfig *cc) {
char sfile[MAXPATHLEN], diskname[MAXPATHLEN];
char pool_name[MAXPATHLEN];
char *p, *vname, *fp = NULL, *err_fmt = NULL;
size_t size;
struct stat stbuf;
nvlist_t *proplist = NULL, *prop = NULL;
nvpair_t *nvp;
zpool_handle_t *zpool_handle;
nvlist_t *config, *nvroot;
nvlist_t **child;
uint_t children;
libzfs_handle_t *lzfs;
/*
* First see if we have a property that specifies the
* statefile path. If any attempt to find it via SMF
* fails, fallback to the "default" path of the ZFS dump
* device.
*/
uu_dprintf(pm_log, UU_DPRINTF_DEBUG,
"%s get statefile path\n", __FUNCTION__);
if (pm_smf_listprop(&proplist, PM_SVC_POWER) == PM_SUCCESS) {
/* Get the property defining the statefile path */
if (nvlist_lookup_nvlist(proplist, PM_PROP_STATEFILE,
&prop) == 0 && prop != NULL) {
/*
* We have a property, so now extract the value
* from the property
*/
if (nvlist_lookup_nvpair(prop, PM_AUTHORITY_SMF_STR,
&nvp) == 0 && nvp != NULL) {
(void) nvpair_value_string(nvp, &fp);
(void) strlcpy(sfile, fp, MAXPATHLEN);
}
nvlist_free(prop);
}
nvlist_free(proplist);
}
/*
* Define the "default" statefile.
* Note that this suffers from the same problem as
* get_cpr_config_path(), in that it uses a copy of
* a private interface to get the pool name.
* Technically, we have free'd the stuff at fp, but that
* is OK, as we don't expect to use the data, but only know
* that we found data.
*/
if ((fp == NULL) && ((fp = be_get_ds_from_dir("/")) != NULL)) {
if ((p = strstr(fp, PM_POOL_SEP)) != NULL) {
(void) memset(pool_name, 0, MAXPATHLEN);
size = (size_t)(p - fp);
(void) strlcpy(pool_name, fp, size);
(void) snprintf(sfile, MAXPATHLEN,
"%s%s/%s", PM_ZVOLPATH, pool_name, PM_DUMPNAME);
} else {
/* Couldn't find pool, return. */
uu_dprintf(pm_log, UU_DPRINTF_FATAL,
"%s statefile requires zfs\n", __FUNCTION__);
return;
}
}
/* If we still cannot find a path, return. */
if (fp == NULL) {
uu_dprintf(pm_log, UU_DPRINTF_FATAL,
"%s statefile not specified\n", __FUNCTION__);
return;
}
/*
* Statefile must exist before it can be used here.
*/
if (stat(sfile, &stbuf) == -1) {
uu_dprintf(pm_log, UU_DPRINTF_FATAL,
"%s statefile path error on %s\n", __FUNCTION__, sfile);
return;
}
/*
* If not a block device, return.
*/
if (!S_ISBLK(stbuf.st_mode)) {
uu_dprintf(pm_log, UU_DPRINTF_FATAL,
"%s statefile not block device\n", __FUNCTION__);
return;
}
/*
* If not a good "slice", return.
*/
if (!is_good_slice(sfile, &err_fmt)) {
uu_dprintf(pm_log, UU_DPRINTF_FATAL,
"%s %s\n", __FUNCTION__, err_fmt);
return;
}
/*
* OK, we have a block device, and we know it is valid,
* so identify the prom device
* If the path is *not* a zvol, it will be treated as a
* SPECFS device.
*/
if (strncmp(sfile, PM_ZVOLPATH, sizeof (PM_ZVOLPATH) - 1) != 0) {
#ifdef sparc
if (utop(sfile, cc->cf_dev_prom)) {
uu_dprintf(pm_log, UU_DPRINTF_FATAL,
"%s could not convert to prom name: %s\n",
__FUNCTION__, sfile);
return;
}
#else
(void) strlcpy(cc->cf_dev_prom, sfile,
sizeof (cc->cf_dev_prom));
#endif
cc->cf_type = CFT_SPEC;
uu_dprintf(pm_log, UU_DPRINTF_DEBUG,
"%s statefile is specfs device %s\n", __FUNCTION__,
cc->cf_dev_prom);
} else {
/*
* Should be ZFS, get the underlying device.
* If anything fails, just return as we haven't yet
* filled in any config file entries.
* Note that we don't really care about the vdev on
* x86 platforms, but this code does give us the
* oppertunity to validate the zvol.
*/
fp = sfile;
fp += sizeof (PM_ZVOLPATH) - 1;
(void) strncpy(pool_name, fp, MAXPATHLEN);
if (p = strchr(pool_name, '/'))
*p = '\0';
if ((lzfs = libzfs_init()) == NULL) {
uu_dprintf(pm_log, UU_DPRINTF_FATAL,
"%s couldn't init zfs\n", __FUNCTION__);
return;
}
if ((zpool_handle = zpool_open(lzfs, pool_name)) == NULL) {
uu_dprintf(pm_log, UU_DPRINTF_FATAL,
"%s couldn't open zfs\n", __FUNCTION__);
libzfs_fini(lzfs);
return;
}
config = zpool_get_config(zpool_handle, NULL);
if (nvlist_lookup_nvlist(config, ZPOOL_CONFIG_VDEV_TREE,
&nvroot) != 0) {
uu_dprintf(pm_log, UU_DPRINTF_FATAL,
"%s couldn't identify pool %s\n", __FUNCTION__,
pool_name);
zpool_close(zpool_handle);
libzfs_fini(lzfs);
return;
}
verify(nvlist_lookup_nvlist_array(nvroot, ZPOOL_CONFIG_CHILDREN,
&child, &children) == 0);
if (children != 1) {
uu_dprintf(pm_log, UU_DPRINTF_FATAL,
"%s couldn't get pool children\n", __FUNCTION__);
zpool_close(zpool_handle);
libzfs_fini(lzfs);
return;
}
vname = zpool_vdev_name(lzfs, zpool_handle, child[0], B_FALSE,
B_FALSE);
if (vname == NULL) {
uu_dprintf(pm_log, UU_DPRINTF_FATAL,
"%s couldn't identify vdev\n", __FUNCTION__);
zpool_close(zpool_handle);
libzfs_fini(lzfs);
return;
}
(void) strcpy(diskname, PM_SPECFSPATH);
(void) strlcat(diskname, vname, sizeof (diskname));
#ifdef sparc
if (utop(diskname, cc->cf_dev_prom)) {
uu_dprintf(pm_log, UU_DPRINTF_FATAL,
"%s could not convert to prom name: %s\n",
__FUNCTION__, sfile);
return;
}
#else
(void) strlcpy(cc->cf_dev_prom, diskname,
sizeof (cc->cf_dev_prom));
#endif
(void) strncpy(cc->cf_fs, p + 1, sizeof (p + 1));
uu_dprintf(pm_log, UU_DPRINTF_DEBUG,
"%s statefile is zfs device: %s\n", __FUNCTION__,
cc->cf_dev_prom);
cc->cf_type = CFT_ZVOL;
free(vname);
zpool_close(zpool_handle);
libzfs_fini(lzfs);
}
/*
* By virtue of arriving here, we have identified and validated
* the statefile path. All that remains is to stuff it in the
* last two config file entries.
*/
(void) strlcpy(cc->cf_devfs, sfile, sizeof (cc->cf_devfs));
(void) strlcpy(cc->cf_path, sfile, sizeof (cc->cf_path));
uu_dprintf(pm_log, UU_DPRINTF_DEBUG,
"%s statefile path: %s\n", __FUNCTION__, sfile);
}
/*
* Update or create cpr_config file.
*/
pm_error_t
update_cprconfig()
{
struct cprconfig cc;
char cpr_conf_dir[MAXPATHLEN], cpr_conf_parent[MAXPATHLEN];
int fd, createdir = 0;
struct stat statbuf;
char *cpr_conf = NULL;
ssize_t nread;
(void) memset(&cc, 0, sizeof (cc));
/*
* A note on the use of dirname and the strings we use to get it.
* dirname(3c) usually manipulates the string that is passed in,
* such that the original string is not usable unless it was
* dup'd or copied before. The exception, is if a NULL is passed,
* and then we get get a new string that is ".". So we must
* always check the return value, and if we care about the original
* string, we must dup it before calling dirname.
*/
if (cpr_conf == NULL && get_cpr_config_path(&cpr_conf) != 0) {
uu_dprintf(pm_log, UU_DPRINTF_FATAL,
"%s cannot identify cpr_file\n", __FUNCTION__);
return (PM_ERROR_SYSTEM);
}
if (strlcpy(cpr_conf_dir, cpr_conf, MAXPATHLEN) >= MAXPATHLEN) {
uu_dprintf(pm_log, UU_DPRINTF_FATAL,
"%s buffer overflow error\n", __FUNCTION__);
return (PM_ERROR_SYSTEM);
}
if ((strcmp(dirname(cpr_conf_dir), ".") == 0) ||
(cpr_conf_dir[0] != '/')) {
uu_dprintf(pm_log, UU_DPRINTF_FATAL,
"%s cpr_config directory unknown or invalid\n",
__FUNCTION__);
return (PM_ERROR_SYSTEM);
}
/*
* If the confdir doesn't exist, it needs to be created,
* unless the parent doesn't exist, and then we bail.
*/
if (stat(cpr_conf_dir, &statbuf) == -1) {
/*
* cpr_config directory doesn't exist. If the parent does,
* set it up so that cpr_conf_dir can be created a little
* further down. Otherwise, return an error.
*/
if (strlcpy(cpr_conf_parent, cpr_conf_dir, MAXPATHLEN) >=
MAXPATHLEN) {
uu_dprintf(pm_log, UU_DPRINTF_FATAL,
"%s buffer overflow error\n", __FUNCTION__);
return (PM_ERROR_SYSTEM);
}
/*
* We already know that cpr_conf_dir properly started
* with '/', so the copy should as well, and we don't
* need to test for it. However, if the parent is
* just '/', this is an error.
*/
if ((strcmp(dirname(cpr_conf_parent), "/") == 0) ||
(stat(cpr_conf_parent, &statbuf) == -1)) {
uu_dprintf(pm_log, UU_DPRINTF_FATAL,
"%s cpr_config parent directory missing\n",
__FUNCTION__);
return (PM_ERROR_SYSTEM);
} else {
createdir = 1;
}
}
/*
* Create the cpr_config directory if we have determined it is missing
*/
if (createdir) {
uu_dprintf(pm_log, UU_DPRINTF_DEBUG, "%s creating: %s\n",
__FUNCTION__, cpr_conf_dir);
if (mkdir(cpr_conf_dir, 0755) == -1) {
uu_dprintf(pm_log, UU_DPRINTF_FATAL,
"%s conf directory %s missing\n",
__FUNCTION__, cpr_conf_dir);
return (PM_ERROR_SYSTEM);
}
}
/* Is there an existing cpr_config */
if (stat(cpr_conf, &statbuf) != -1) {
/* Use it to initially populate cprconfig struct. */
if ((statbuf.st_size >= sizeof (cc)) &&
(fd = open(cpr_conf, O_RDONLY)) != -1) {
nread = read(fd, &cc, sizeof (cc));
if (nread != (ssize_t)sizeof (cc)) {
uu_dprintf(pm_log, UU_DPRINTF_DEBUG,
"%s: %s is corrupted, regenerating.\n",
__FUNCTION__, cpr_conf);
(void) memset(&cc, 0, sizeof (cc));
}
}
(void) close(fd);
uu_dprintf(pm_log, UU_DPRINTF_DEBUG, "%s updating file: %s\n",
__FUNCTION__, cpr_conf);
} else {
uu_dprintf(pm_log, UU_DPRINTF_DEBUG, "%s creating file: %s\n",
__FUNCTION__, cpr_conf);
}
/* Always make sure the magic number is correct. */
cc.cf_magic = CPR_CONFIG_MAGIC;
/*
* Fetch the statefile path for this platform, and populate
* the cpr config struct.
*/
sfpath(&cc);
/*
* Effectively, we always want a fresh file, so create it if
* it doesn't exist, and truncate it if it does.
*/
if ((fd = open(cpr_conf, O_CREAT | O_TRUNC | O_WRONLY, 0644)) == -1) {
uu_dprintf(pm_log, UU_DPRINTF_FATAL,
"%s cannot open/create \"%s\", %s\\n",
__FUNCTION__, cpr_conf, strerror(errno));
return (PM_ERROR_SYSTEM);
}
if (write(fd, &cc, sizeof (cc)) != sizeof (cc)) {
uu_dprintf(pm_log, UU_DPRINTF_FATAL,
"%s error writing \"%s\", %s\\n",
__FUNCTION__, cpr_conf, strerror(errno));
(void) close(fd);
return (PM_ERROR_SYSTEM);
}
(void) close(fd);
return (PM_SUCCESS);
}
/*
* Find the path to the cpr_config file.
*/
int
get_cpr_config_path(char **cpr_conf)
{
int cpsize;
#ifdef sparc
/* On Sparc, this is easy, as it (currently) must be CPR_CONFIG */
if (*cpr_conf == NULL) {
*cpr_conf = strdup(CPR_CONFIG);
cpsize = strlen(CPR_CONFIG);
} else {
cpsize = snprintf(*cpr_conf, MAXPATHLEN, "%s", CPR_CONFIG);
}
#else
char pool[MAXPATHLEN], *fs, *sep;
size_t size;
/* On others, we need to find the pool the root is running. */
/* Clear and add the leading '/' to the pool pathname. */
(void) memset(pool, 0, MAXPATHLEN);
if (*cpr_conf == NULL)
*cpr_conf = malloc(MAXPATHLEN);
pool[0] = '/';
if ((fs = be_get_ds_from_dir("/")) == NULL)
return (-1);
if ((sep = strstr(fs, PM_POOL_SEP)) != NULL) {
/*
* We have identified the mounted pool, extract everything
* up to "/ROOT" from it.
*/
size = (size_t)(sep - fs);
(void) strncpy(pool + 1, fs, size);
}
cpsize = snprintf(*cpr_conf, MAXPATHLEN, "%s%s", pool, CPR_CONFIG);
#endif
if ((cpsize <= 0) || (cpsize > MAXPATHLEN))
return (-1);
else
return (0);
}
#ifdef sparc
/*
* Convert a Unix device to a prom device and save on success,
* log any ioctl/conversion error.
*/
static int
utop(char *fs_name, char *prom_name)
{
union obpbuf {
char buf[OBP_MAXPATHLEN + sizeof (uint_t)];
struct openpromio oppio;
};
union obpbuf oppbuf;
struct openpromio *opp;
char *promdev = "/dev/openprom";
int fd, upval;
if ((fd = open(promdev, O_RDONLY)) == -1) {
uu_dprintf(pm_log, UU_DPRINTF_FATAL,
"%s cannot open \"%s\", %s.\n",
__FUNCTION__, promdev, strerror(errno));
return (-1);
}
opp = &oppbuf.oppio;
opp->oprom_size = OBP_MAXPATHLEN;
(void) strlcpy(opp->oprom_array, fs_name, OBP_MAXPATHLEN);
upval = ioctl(fd, OPROMDEV2PROMNAME, opp);
(void) close(fd);
if (upval == 0) {
(void) strlcpy(prom_name, opp->oprom_array, OBP_MAXPATHLEN);
} else {
uu_dprintf(pm_log, UU_DPRINTF_FATAL,
"%s cannot convert \"%s\" to prom device\n",
__FUNCTION__, fs_name);
}
return (upval);
}
#endif
/*
* Function: be_get_ds_from_dir_callback
* Description: This is a callback function used to iterate all datasets
* to find the one that is currently mounted at the directory
* being searched for. If matched, the name of the dataset is
* returned in heap storage, so the caller is responsible for
* freeing it.
* Parameters:
* zhp - zfs_handle_t pointer to current dataset being processed.
* data - dir_data_t pointer providing name of directory being
* searched for.
* Returns:
* 1 - This dataset is mounted at directory being searched for.
* 0 - This dataset is not mounted at directory being searched for.
* Scope:
* Private
*/
static int
be_get_ds_from_dir_callback(zfs_handle_t *zhp, void *data)
{
dir_data_t *dd = data;
char *mp = NULL;
int zret = 0;
if (zfs_get_type(zhp) != ZFS_TYPE_FILESYSTEM) {
ZFS_CLOSE(zhp);
return (0);
}
if (zfs_is_mounted(zhp, &mp) && mp != NULL &&
strcmp(mp, dd->dir) == 0) {
if ((dd->ds = strdup(zfs_get_name(zhp))) == NULL) {
uu_dprintf(pm_log, UU_DPRINTF_FATAL,
"%s memory allocation failed\n", __FUNCTION__);
ZFS_CLOSE(zhp);
return (0);
}
ZFS_CLOSE(zhp);
return (1);
}
zret = zfs_iter_filesystems(zhp, be_get_ds_from_dir_callback, dd);
ZFS_CLOSE(zhp);
return (zret);
}
/*
* Function: be_get_ds_from_dir(char *dir)
* Description: Given a directory path, find the underlying dataset mounted
* at that directory path if there is one. The returned name
* is allocated in heap storage, so the caller is responsible
* for freeing it.
* Parameters:
* dir - char pointer of directory to find.
* Returns:
* NULL - if directory is not mounted from a dataset.
* name of dataset mounted at dir.
* Scope:
* Semi-private (library wide use only)
*
* This is what I need to find the root pool from a mounted root dir.
*/
static char *
be_get_ds_from_dir(char *dir)
{
dir_data_t dd = { 0 };
char resolved_dir[MAXPATHLEN];
if (g_zfs == NULL && (g_zfs = libzfs_init()) == NULL) {
return (NULL);
}
/* Make sure length of dir is within the max length */
if (dir == NULL || strlen(dir) >= MAXPATHLEN)
return (NULL);
/* Resolve dir in case its lofs mounted */
(void) strlcpy(resolved_dir, dir, sizeof (resolved_dir));
z_resolve_lofs(resolved_dir, sizeof (resolved_dir));
dd.dir = resolved_dir;
(void) zfs_iter_root(g_zfs, be_get_ds_from_dir_callback, &dd);
return (dd.ds);
}