/*
* 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) 2012, Oracle and/or its affiliates. All rights reserved.
*/
#include <sys/stat.h>
#include <sys/paths.h>
#include <sys/lofi.h>
#include <string.h>
#include <strings.h>
#include <stdio.h>
#include <libscf.h>
#include <assert.h>
#include <libgen.h>
#include <errno.h>
#include <fcntl.h>
#include <unistd.h>
#include <libdevinfo.h>
#include <sys/wait.h>
#include <sys/mkdev.h>
#include <regex.h>
#include "suri_impl.h"
#include "suri_strings.h"
regex_t suri_re_no_slice_devname;
/*
* Get the state of a service. If we get the state, return ESURI_OK, and return
* ESURI_ERR otherwise.
*/
static suri_err_t
suri_getsvcstate(struct suri_handle *sh, int *status, char *svcname)
{
char *s;
assert(status != NULL);
assert(svcname != NULL);
if ((s = smf_get_state(svcname)) == NULL) {
suri_err_set_desc(sh, "%s: \"%s\"",
SURIGTEXT("Could not get status of service"), svcname);
return (ESURI_ERR);
}
*status = smf_state_from_string(s);
assert(*status != -1);
free(s);
return (ESURI_OK);
}
/*
* Return ESURI_OK if the service is enabled and online, or if we were able to
* to put it online temporarily. Return ESURI_ERR otherwise.
*
* Note that libsvc(3LIB) does not offer any synchronous interface to enable a
* service. The functionality is only in svcadm(1M) which is what we use here.
*/
suri_err_t
suri_enable_service(struct suri_handle *sh, char *svcname)
{
char *cmd;
suri_err_t ret;
int status, ret2;
if ((ret = suri_getsvcstate(sh, &status, svcname)) != ESURI_OK)
return (ret);
switch (status) {
case SCF_STATE_ONLINE:
return (ESURI_OK);
case SCF_STATE_DISABLED:
ret2 = asprintf(&cmd,
"/sbin/svcadm enable -rst %s >/dev/null 2>&1", svcname);
if (ret2 == -1)
return (suri_err_set_static_desc(sh, ESURI_NOMEM));
ret2 = system(cmd);
free(cmd);
/* If we got -1, errno is supposed to have been set. */
if (ret2 == -1) {
suri_err_set_desc(sh, "%s \"%s\": %s",
ERR_SURI_SVC_ENABLE, svcname,
strerror(errno));
return (ESURI_ERR);
}
/*
* If the shell did not terminate normally we return an error.
* We do not assume there is any possibility that the service
* could have been put online.
*/
if (WIFEXITED(ret2) == 0) {
suri_err_set_desc(sh, "%s \"%s\"",
ERR_SURI_SVC_ENABLE, svcname);
return (ESURI_ERR);
}
/*
* If the shell did not return 0 then something must have
* failed. Again, we do not care what is the state of the
* service now, let's just bail out.
*/
if (WEXITSTATUS(ret2) != 0) {
suri_err_set_desc(sh, "%s \"%s\": %s %d",
ERR_SURI_SVC_ENABLE, svcname,
SURIGTEXT("shell returned"), WEXITSTATUS(ret2));
return (ESURI_ERR);
}
/*
* So, all seems fine up to now but is the service really
* online?
*/
if ((ret = suri_getsvcstate(sh, &status, svcname)) != ESURI_OK)
return (ret);
if (status == SCF_STATE_ONLINE)
return (ESURI_OK);
suri_err_set_desc(sh, "%s: \"%s\"",
SURIGTEXT("Failed to put service online"), svcname);
return (ESURI_ERR);
default:
/*
* Not online nor disabled. We will not try to figure out what
* is going on.
*/
suri_err_set_desc(sh, "%s: \"%s\"", ERR_SURI_SVC_ENABLE,
svcname);
return (ESURI_ERR);
}
}
/*
* We have to realpath() only the directory since names from /dev are symbolic
* links to the /devices directory managed by devfs(7FS). However, this
* function can be used on existing device paths only anyway.
*
* Return a specific error on failure and ESURI_OK on success.
*
* Note that the function does not make sure the file exists since it only uses
* realpath() on its directory. See suri_getfiletype() which is supposed to be
* used together with this function.
*
* If devpath_in_action is B_TRUE, do not put the device path into the
* description string. This way the caller hints at that the dev path is going
* to be in the action string. We do not want to print messages like the
* following one:
*
* Failed to look up URI for device: "/dev/dsk/x": No such device: "/dev/dsk/x
*/
#define SURI_CANNOT_CANON SURIGTEXT("Cannot canonicalize path")
#define SURI_NON_CANON SURIGTEXT("Non-canonical path")
suri_err_t
suri_enforce_real_devpath(struct suri_handle *sh, const char *path,
boolean_t devpath_in_action)
{
int n;
char *dn, *out = NULL, *rdn = NULL, *tmp = NULL;
suri_err_t ret = ESURI_OK;
assert(suri_enforce_block_dev(sh, path, B_FALSE) == ESURI_OK);
if (strlen(path) >= MAXPATHLEN)
return (suri_err_set_static_desc(sh, ESURI_NAMETOOLONG));
/* Caution, dirname(3C) below modifies its input */
if ((tmp = strdup(path)) == NULL)
return (suri_err_set_static_desc(sh, ESURI_NOMEM));
dn = dirname(tmp);
if ((rdn = realpath(dn, NULL)) == NULL) {
free(tmp);
if (devpath_in_action) {
suri_err_set_desc(sh, "%s: %s",
SURI_CANNOT_CANON, strerror(errno));
} else {
suri_err_set_desc(sh, "%s: %s: \"%s\"",
SURI_CANNOT_CANON, strerror(errno), dn);
}
return (ESURI_ERR);
}
free(tmp);
/* Caution, basename(3C) below modifies its input */
if ((tmp = strdup(path)) == NULL) {
free(rdn);
return (suri_err_set_static_desc(sh, ESURI_NOMEM));
}
if (strcmp(rdn, "/") == 0)
n = asprintf(&out, "/%s", basename(tmp));
else
n = asprintf(&out, "%s/%s", rdn, basename(tmp));
if (n == -1) {
ret = suri_err_set_static_desc(sh, ESURI_NOMEM);
goto err;
}
if (strlen(out) > MAXPATHLEN) {
ret = suri_err_set_static_desc(sh, ESURI_NAMETOOLONG);
goto err;
}
if (strcmp(path, out) != 0) {
if (devpath_in_action) {
suri_err_set_desc(sh, "%s", SURI_NON_CANON);
} else {
suri_err_set_desc(sh, "%s: \"%s\"",
SURI_NON_CANON, path);
}
ret = ESURI_ERR;
}
err:
if (tmp != NULL)
free(tmp);
if (rdn != NULL)
free(rdn);
if (out != NULL)
free(out);
return (ret);
}
/*
* Get an existing disk device path (may be in dev or in rdev) and return the
* device name with its slice stripped. The returned pointer points into the
* original string which is modified in this function.
*/
const char *
get_stripped_devname(char *devpath)
{
int ret;
regmatch_t pm[2];
ret = regexec(&suri_re_no_slice_devname, devpath, 2, pm, 0);
/*
* We are guaranteed to get an existing disk device path. If we fail it
* is an internal error.
*/
assert(ret == REG_OK);
/* Get rid of the slice part */
*(devpath + pm[1].rm_eo) = '\0';
/* Skip the "/dev/r?dsk/" prefix */
return (devpath + pm[1].rm_so);
}
/*
* Enforce the file path is a regular file.
*/
#define SURI_STAT_FAILED SURIGTEXT("Cannot get file status")
suri_err_t
suri_enforce_reg_file(struct suri_handle *sh, const char *path)
{
struct stat64 st;
/*
* We need to distinguish ENOENT from other errnos so that a consumer
* can decide whether an object needs to be created.
*/
if (stat64(path, &st) != 0) {
if (errno == ENOENT) {
suri_err_set_desc(sh, "%s: \"%s\"",
SURIGTEXT("No such file"), path);
return (ESURI_NOENT);
}
suri_err_set_desc(sh, "%s: %s: \"%s\"",
SURI_STAT_FAILED, strerror(errno), path);
return (ESURI_ERR);
}
if (!S_ISREG(st.st_mode)) {
suri_err_set_desc(sh, "%s: \"%s\"",
SURIGTEXT("Not a regular file"), path);
return (ESURI_ERR);
}
return (ESURI_OK);
}
/*
* Enforce the file path is a block device. See suri_enforce_real_devpath() on
* description and use of 'devpath_in_action'.
*/
#define SURI_NO_SUCH_DEV SURIGTEXT("No such device")
#define SURI_NOT_BLOCK_DEVICE SURIGTEXT("Not a block device")
suri_err_t
suri_enforce_block_dev(struct suri_handle *sh, const char *path,
boolean_t devpath_in_action)
{
struct stat64 st;
/* We only accept devices under /dev */
if (strncmp(path, _PATH_DEV, strlen(_PATH_DEV)) != 0) {
if (devpath_in_action) {
suri_err_set_desc(sh, "%s", ERR_SURI_NOT_IN_SLASH_DEV);
} else {
suri_err_set_desc(sh, "%s: \"%s\"",
ERR_SURI_NOT_IN_SLASH_DEV, path);
}
return (ESURI_ERR);
}
if (stat64(path, &st) != 0) {
if (errno == ENOENT) {
if (devpath_in_action) {
suri_err_set_desc(sh, "%s", SURI_NO_SUCH_DEV);
} else {
suri_err_set_desc(sh, "%s: \"%s\"",
SURI_NO_SUCH_DEV, path);
}
return (ESURI_NOENT);
}
if (devpath_in_action) {
suri_err_set_desc(sh, "%s: %s",
SURI_STAT_FAILED, strerror(errno));
} else {
suri_err_set_desc(sh, "%s: %s: \"%s\"",
SURI_STAT_FAILED, strerror(errno), path);
}
return (ESURI_ERR);
}
if (!S_ISBLK(st.st_mode)) {
if (devpath_in_action) {
suri_err_set_desc(sh, "%s:", SURI_NOT_BLOCK_DEVICE);
} else {
suri_err_set_desc(sh, "%s: \"%s\"",
SURI_NOT_BLOCK_DEVICE, path);
}
return (ESURI_ERR);
}
return (ESURI_OK);
}
/*
* Open the lofi control device. Optionally return lofi major device number.
*/
static suri_err_t
suri_open_lofictl(struct suri_handle *sh, int *lfd, major_t *major)
{
char *lofictl = _PATH_DEV LOFI_CTL_NAME;
if ((*lfd = open(lofictl, O_RDWR)) == -1) {
suri_err_set_desc(sh, "%s: %s: \"%s\"",
SURIGTEXT("Cannot open lofi control device"),
strerror(errno), lofictl);
return (ESURI_ERR);
}
if (major != NULL) {
struct stat st;
if (fstat(*lfd, &st) == -1) {
suri_err_set_desc(sh, "%s: %s: \"%s\"",
SURIGTEXT("Failed to get major device number"),
strerror(errno), lofictl);
return (ESURI_ERR);
}
*major = major(st.st_rdev);
}
return (ESURI_OK);
}
/*
* This might be the first time we have used this minor number. If so, it might
* also be that the /dev links are in the process of being created by devfsadmd
* (or that they will be created "soon"). We cannot return until they are there
* or the consumer of this library might try to use them and not find them.
*
* Note that this function has been taken from cmd/lofiadm/main.c.
*/
static int sleeptime = 2; /* number of seconds to sleep between stat's */
static int maxsleep = 30; /* maximum number of seconds to sleep */
static suri_err_t
suri_wait_until_dev_complete(struct suri_handle *sh, int minor)
{
struct stat64 buf;
int cursleep;
char blkpath[MAXPATHLEN];
char charpath[MAXPATHLEN];
di_devlink_handle_t hdl;
(void) snprintf(blkpath, sizeof (blkpath), _PATH_DEV "%s/%d",
LOFI_BLOCK_NAME, minor);
(void) snprintf(charpath, sizeof (charpath), _PATH_DEV "%s/%d",
LOFI_CHAR_NAME, minor);
/* Check if links already present */
if (stat64(blkpath, &buf) == 0 && stat64(charpath, &buf) == 0)
return (ESURI_OK);
/* First use di_devlink_init() */
if (hdl = di_devlink_init(LOFI_DRIVER_NAME, DI_MAKE_LINK)) {
(void) di_devlink_fini(&hdl);
goto out;
}
/*
* Under normal conditions, di_devlink_init(DI_MAKE_LINK) above will
* only fail if the caller is missing the sys_config privilege. In that
* case, wait for link creation via sysevents.
*/
for (cursleep = 0; cursleep < maxsleep; cursleep += sleeptime) {
if (stat64(blkpath, &buf) == 0 && stat64(charpath, &buf) == 0)
return (ESURI_OK);
(void) sleep(sleeptime);
}
/* One last try */
out:
if (stat64(blkpath, &buf) == -1) {
suri_err_set_desc(sh, "%s: \"%s\"",
SURIGTEXT("Cannot create lofi device"), blkpath);
return (ESURI_ERR);
}
if (stat64(charpath, &buf) == -1) {
suri_err_set_desc(sh, "%s: \"%s\"",
SURIGTEXT("Cannot create lofi device"), charpath);
return (ESURI_ERR);
}
return (ESURI_OK);
}
/*
* This is for a file URI only (so far). It maps directly
* suri_file->sf_prop_path property to the handle's mapped device.
*/
suri_err_t
suri_lofi_map(struct suri_handle *sh, boolean_t lookup_only)
{
int lfd;
suri_err_t ret;
struct lofi_ioctl li;
suri_file_t *suri_file = SURIH2FILE(sh);
if ((ret = suri_open_lofictl(sh, &lfd, NULL)) != ESURI_OK)
return (ret);
(void) memset(&li, 0, sizeof (li));
li.li_minor = 0;
/* li_filename is MAXPATHLEN long */
(void) strlcpy(li.li_filename, suri_file->sf_prop_path,
sizeof (li.li_filename));
if (ioctl(lfd, LOFI_GET_BY_FILENAME, &li) == -1) {
if (lookup_only) {
suri_err_set_desc(sh, "%s: \"%s\"",
SURIGTEXT("No such lofi mapping exists for file"),
suri_file->sf_prop_path);
return (ESURI_ERR);
}
/* Not mapped yet, let us map it. */
if (ioctl(lfd, LOFI_MAP_FILE, &li) == -1) {
suri_err_set_desc(sh, "%s: %s: \"%s\"",
SURIGTEXT("Cannot create lofi mapping"),
strerror(errno), suri_file->sf_prop_path);
return (ESURI_ERR);
}
/* Wait for the device links to be created to avoid races. */
ret = suri_wait_until_dev_complete(sh, li.li_minor);
if (ret != ESURI_OK)
return (ret);
}
(void) snprintf(sh->sh_mdev, sizeof (sh->sh_mdev),
_PATH_DEV "%s/%d", LOFI_BLOCK_NAME, li.li_minor);
return (ESURI_OK);
}
/*
* Translate an existing device name in the mdev property to a major/minor
* number.
*/
static suri_err_t
devname_to_major_minor(struct suri_handle *sh, major_t *major, minor_t *minor)
{
struct stat64 st;
assert(sh->sh_mdev[0] != '\0');
if (stat64(sh->sh_mdev, &st) == -1) {
suri_err_set_desc(sh, "%s: %s: \"%s\"",
SURIGTEXT("Failed to get minor/major numbers for device"),
strerror(errno), sh->sh_mdev);
return (ESURI_ERR);
}
*major = major(st.st_rdev);
*minor = minor(st.st_rdev);
return (ESURI_OK);
}
/*
* Reverse look up a lofi device name as set in the handle's mapped device to a
* filename. This is for a file URI only (so far) so we directly fill out
* suri_file->sf_prop_path.
*/
suri_err_t
suri_lofi_lookup_uri(struct suri_handle *sh)
{
int fd;
suri_err_t ret;
struct lofi_ioctl li;
major_t lofi_major, dev_major;
suri_file_t *suri_file = SURIH2FILE(sh);
(void) memset(&li, 0, sizeof (li));
if ((ret = devname_to_major_minor(sh, &dev_major,
(minor_t *)&li.li_minor)) != ESURI_OK) {
return (ret);
}
if ((ret = suri_open_lofictl(sh, &fd, &lofi_major)) != ESURI_OK)
return (ret);
if (lofi_major != dev_major) {
suri_err_set_desc(sh, "%s: \"%s\"",
SURIGTEXT("Not a lofi device"), sh->sh_mdev);
(void) close(fd);
return (ESURI_NOENT);
}
if (ioctl(fd, LOFI_GET_BY_MINOR, &li) == -1) {
suri_err_set_desc(sh, "%s: %s: \"%s\"",
SURIGTEXT("Failed to look up lofi minor device"),
strerror(errno), sh->sh_mdev);
(void) close(fd);
return (ESURI_ERR);
}
(void) snprintf(suri_file->sf_prop_path,
sizeof (suri_file->sf_prop_path), "%s", li.li_filename);
(void) close(fd);
return (ESURI_OK);
}
/*
* Remove the connection between a lofi device and a filename serving as a
* backing store. This function is used with a file URI only (so far) so we work
* directly with suri_file->sf_prop_path. Note that we need a filename to
* unmap the lofi device, not a lofi device name.
*/
suri_err_t
suri_lofi_unmap(struct suri_handle *sh)
{
int fd;
suri_err_t ret;
struct lofi_ioctl li;
suri_file_t *suri_file = SURIH2FILE(sh);
if ((ret = suri_open_lofictl(sh, &fd, NULL)) != ESURI_OK)
return (ret);
(void) memset(&li, 0, sizeof (li));
li.li_minor = 0;
/*
* Note that we use a unix path to unmap a lofi device, not the lofi
* device name itself. It is needed to use the file path since libsuri
* can unmap from a parsed state, too.
*/
(void) strlcpy(li.li_filename, suri_file->sf_prop_path,
sizeof (li.li_filename));
if (ioctl(fd, LOFI_UNMAP_FILE, &li) == -1) {
/*
* If the mapping was already removed or if the file does not
* exist ioctl() returns ENOENT. As with other unmap functions,
* we return ESURI_NOENT.
*/
if (errno == ENOENT) {
suri_err_set_desc(sh, "%s: \"%s\"",
SURIGTEXT("No lofi mapping found for object"),
suri_file->sf_prop_path);
return (ESURI_NOENT);
}
if (errno == EBUSY) {
suri_err_set_desc(sh, "%s: \"%s\"",
SURIGTEXT("Could not remove lofi mapping because "
"underlying object is busy"),
suri_file->sf_prop_path);
return (ESURI_BUSY);
}
suri_err_set_desc(sh, "%s: %s: \"%s\"",
SURIGTEXT("Failed to remove lofi mapping"),
strerror(errno), suri_file->sf_prop_path);
(void) close(fd);
return (ESURI_ERR);
}
(void) close(fd);
return (ESURI_OK);
}
#define SURI_EXPERIMENTAL_SWITCH "/var/tmp/suri-enable-experimental-uri-support"
/*
* Some URI types are experimental and we do not support those by default. Those
* are used for internal testing and debugging. If a specific file exists, we
* enable that support. Also, we enable it on debug builds by default.
*/
boolean_t
suri_experimental_enabled(void)
{
#ifdef DEBUG
return (B_TRUE);
#else
struct stat st;
if (stat(SURI_EXPERIMENTAL_SWITCH, &st) == 0)
return (B_TRUE);
else
return (B_FALSE);
#endif
}
#define SURI_HANDLE_ARRAY_INCREASE 5
/*
* Expand an array of suri handles. Current size includes a terminating NULL.
* Return an increased array with all handles copied to it. Increase the current
* size. Free the old array. If psh is NULL and *current_size 0, create a new
* array.
*
* If we cannot allocate memory to expand the array treat it as a major error
* and destroy the array. That is consistent with other parts of libsuri.
*/
struct suri_handle **
suri_expand_handle_array(struct suri_handle **psh, int *current_size)
{
struct suri_handle **tpsh = NULL;
assert(current_size != NULL);
assert(psh == NULL || (*current_size > 0));
if ((tpsh = calloc(*current_size + SURI_HANDLE_ARRAY_INCREASE,
sizeof (suri_handle_t *))) == NULL) {
suri_destroy_handle_array(&psh);
return (NULL);
}
(void) memcpy(tpsh, psh, *current_size * sizeof (suri_handle_t *));
*current_size += SURI_HANDLE_ARRAY_INCREASE;
if (psh != NULL)
free(psh);
return (tpsh);
}
/*
* Free all handles and then free the array, too.
*/
void
suri_destroy_handle_array(struct suri_handle ***psh)
{
int i;
assert(psh != NULL && *psh != NULL);
for (i = 0; (*psh)[i] != NULL; ++i)
suri_free((suri_handle_t)((*psh)[i]));
free(*psh);
*psh = NULL;
}
/*
* Get a number of handles in the handle array.
*/
int
suri_get_handle_count(struct suri_handle **psh)
{
int i = 0;
assert(psh != NULL);
while (psh[i] != NULL)
++i;
return (i);
}
/*
* A compare function for qsort(3C). We sort handles alphabetically based on URI
* strings.
*/
static int
suri_cmp_handle(const void *p1, const void *p2)
{
struct suri_handle *sh1 = *(struct suri_handle **)p1;
struct suri_handle *sh2 = *(struct suri_handle **)p2;
assert(sh1->sh_uri[0] != '\0');
assert(sh2->sh_uri[0] != '\0');
return (strcmp(sh1->sh_uri, sh2->sh_uri));
}
/*
* Go through a handle array and remove duplicates based on URI string
* comparison. Do it in place. Upon return, the array will contain a continuous
* sequence of sorted unique handles, terminated by NULL.
*/
void
suri_remove_duplicate_handles(struct suri_handle **psh)
{
int i, j, n;
/* We must have at least one handle. */
assert(psh[0] != NULL);
n = suri_get_handle_count(psh);
qsort(psh, n, sizeof (struct suri_handle *), suri_cmp_handle);
/*
* Index 'i' is used to index non-duplicate handles and it is not
* increased if the next handle to process (ie., psh[j]) is a duplicate
* of psh[i]. Index 'j' is used to go through the whole array handle by
* handle. We start with i=0 and j=1 but the "gap" widens as we hit
* duplicates.
*/
for (i = 0, j = 1; psh[j] != NULL; ++j) {
if (strcmp(psh[i]->sh_uri, psh[j]->sh_uri) == 0) {
suri_free((suri_handle_t)psh[j]);
continue;
}
++i;
/* No duplicates so far, no reshuffling needed. */
if (i == j)
continue;
/* Move it over the gap between i and j. */
psh[i] = psh[j];
}
/* psh[i] is the last handle in the array */
psh[i + 1] = NULL;
}
/*
* Resolve a hostname part of the authority section in the iSCSI URI.
*/
suri_err_t
suri_resolve_hostname(struct suri_handle *sh, struct addrinfo **res)
{
int err;
char *c, *s, *s_orig;
struct addrinfo hints;
suri_iscsi_t *suri_iscsi;
assert(res != NULL);
assert(sh->sh_uri_type == SURI_TYPE_ISCSI);
suri_iscsi = SURIH2ISCSI(sh);
assert(suri_iscsi->si_prop_hname[0] != '\0');
if ((s = s_orig = strdup(suri_iscsi->si_prop_hname)) == NULL)
return (suri_err_set_static_desc(sh, ESURI_NOMEM));
/* We can have an IPv6 address. Those are always in square brackets. */
if (s[0] == '[') {
++s;
c = strchr(s, ']');
/* Hostname property was already parsed. */
assert(c != NULL);
*c = '\0';
}
(void) memset(&hints, 0, sizeof (hints));
hints.ai_family = AF_UNSPEC;
hints.ai_socktype = SOCK_STREAM;
if ((err = getaddrinfo(s, NULL, &hints, res)) != 0) {
suri_err_set_desc(sh, "%s: %s: \"%s\"",
SURIGTEXT("Cannot resolve hostname to IP address"),
gai_strerror(err), suri_iscsi->si_prop_hname);
free(s_orig);
return (ESURI_ERR);
}
free(s_orig);
return (ESURI_OK);
}