#include <stdlib.h>
#include <errno.h>
#include <unistd.h>
#include <string.h>
#include <limits.h>
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include "util.h"
#include "checkers.h"
#include "vector.h"
#include "structs.h"
#include "debug.h"
#include "uxsock.h"
#include "file.h"
#include "wwids.h"
#include "defaults.h"
#include "config.h"
#include "devmapper.h"
/*
* Copyright (c) 2010 Benjamin Marzinski, Redhat
*/
static int
lookup_wwid(FILE *f, char *wwid) {
int c;
char buf[LINE_MAX];
int count;
while ((c = fgetc(f)) != EOF){
if (c != '/') {
if (fgets(buf, LINE_MAX, f) == NULL)
return 0;
else
continue;
}
count = 0;
while ((c = fgetc(f)) != '/') {
if (c == EOF)
return 0;
if (count >= WWID_SIZE - 1)
goto next;
if (wwid[count] == '\0')
goto next;
if (c != wwid[count++])
goto next;
}
if (wwid[count] == '\0')
return 1;
next:
if (fgets(buf, LINE_MAX, f) == NULL)
return 0;
}
return 0;
}
static int
write_out_wwid(int fd, char *wwid) {
int ret;
off_t offset;
char buf[WWID_SIZE + 3];
ret = snprintf(buf, WWID_SIZE + 3, "/%s/\n", wwid);
if (ret >= (WWID_SIZE + 3) || ret < 0){
condlog(0, "can't format wwid for writing (%d) : %s",
ret, strerror(errno));
return -1;
}
offset = lseek(fd, 0, SEEK_END);
if (offset < 0) {
condlog(0, "can't seek to the end of wwids file : %s",
strerror(errno));
return -1;
}
if (write(fd, buf, strlen(buf)) != (ssize_t)strlen(buf)) {
condlog(0, "cannot write wwid to wwids file : %s",
strerror(errno));
if (ftruncate(fd, offset))
condlog(0, "cannot truncate failed wwid write : %s",
strerror(errno));
return -1;
}
return 1;
}
int
replace_wwids(vector mp)
{
int i, can_write;
long fd;
struct multipath * mpp;
size_t len;
int ret = -1;
struct config *conf;
conf = get_multipath_config();
pthread_cleanup_push(put_multipath_config, conf);
fd = open_file(conf->wwids_file, &can_write, WWIDS_FILE_HEADER);
pthread_cleanup_pop(1);
if (fd < 0)
goto out;
pthread_cleanup_push(close_fd, (void*)fd);
if (!can_write) {
condlog(0, "cannot replace wwids. wwids file is read-only");
goto out_file;
}
if (ftruncate(fd, 0) < 0) {
condlog(0, "cannot truncate wwids file : %s", strerror(errno));
goto out_file;
}
if (lseek(fd, 0, SEEK_SET) < 0) {
condlog(0, "cannot seek to the start of the file : %s",
strerror(errno));
goto out_file;
}
len = strlen(WWIDS_FILE_HEADER);
if (write(fd, WWIDS_FILE_HEADER, len) != (ssize_t)len) {
condlog(0, "Can't write wwid file header : %s",
strerror(errno));
/* cleanup partially written header */
if (ftruncate(fd, 0) < 0)
condlog(0, "Cannot truncate header : %s",
strerror(errno));
goto out_file;
}
if (!mp || !mp->allocated) {
ret = 0;
goto out_file;
}
vector_foreach_slot(mp, mpp, i) {
if (write_out_wwid(fd, mpp->wwid) < 0)
goto out_file;
}
ret = 0;
out_file:
pthread_cleanup_pop(1);
out:
return ret;
}
int
do_remove_wwid(int fd, char *str) {
char buf[4097];
char *ptr;
off_t start = 0;
int bytes;
while (1) {
if (lseek(fd, start, SEEK_SET) < 0) {
condlog(0, "wwid file read lseek failed : %s",
strerror(errno));
return -1;
}
bytes = read(fd, buf, 4096);
if (bytes < 0) {
if (errno == EINTR || errno == EAGAIN)
continue;
condlog(0, "failed to read from wwids file : %s",
strerror(errno));
return -1;
}
if (!bytes) /* didn't find wwid to remove */
return 1;
buf[bytes] = '\0';
ptr = strstr(buf, str);
if (ptr != NULL) {
condlog(3, "found '%s'", str);
if (lseek(fd, start + (ptr - buf), SEEK_SET) < 0) {
condlog(0, "write lseek failed : %s",
strerror(errno));
return -1;
}
while (1) {
if (write(fd, "#", 1) < 0) {
if (errno == EINTR || errno == EAGAIN)
continue;
condlog(0, "failed to write to wwids file : %s", strerror(errno));
return -1;
}
return 0;
}
}
ptr = strrchr(buf, '\n');
if (ptr == NULL) { /* shouldn't happen, assume it is EOF */
condlog(4, "couldn't find newline, assuming end of file");
return 1;
}
start = start + (ptr - buf) + 1;
}
}
int
remove_wwid(char *wwid) {
long fd;
int len, can_write;
char *str;
int ret = -1;
struct config *conf;
len = strlen(wwid) + 4; /* two slashes the newline and a zero byte */
str = malloc(len);
if (str == NULL) {
condlog(0, "can't allocate memory to remove wwid : %s",
strerror(errno));
return -1;
}
pthread_cleanup_push(free, str);
if (snprintf(str, len, "/%s/\n", wwid) >= len) {
condlog(0, "string overflow trying to remove wwid");
ret = -1;
goto out;
}
condlog(3, "removing line '%s' from wwids file", str);
conf = get_multipath_config();
pthread_cleanup_push(put_multipath_config, conf);
fd = open_file(conf->wwids_file, &can_write, WWIDS_FILE_HEADER);
pthread_cleanup_pop(1);
if (fd < 0) {
ret = -1;
goto out;
}
pthread_cleanup_push(close_fd, (void*)fd);
if (!can_write) {
ret = -1;
condlog(0, "cannot remove wwid. wwids file is read-only");
} else
ret = do_remove_wwid(fd, str);
pthread_cleanup_pop(1);
out:
/* free(str) */
pthread_cleanup_pop(1);
return ret;
}
int
check_wwids_file(char *wwid, int write_wwid)
{
int fd, can_write, found, ret;
FILE *f;
struct config *conf;
conf = get_multipath_config();
pthread_cleanup_push(put_multipath_config, conf);
fd = open_file(conf->wwids_file, &can_write, WWIDS_FILE_HEADER);
pthread_cleanup_pop(1);
if (fd < 0)
return -1;
f = fdopen(fd, "r");
if (!f) {
condlog(0,"can't fdopen wwids file : %s", strerror(errno));
close(fd);
return -1;
}
found = lookup_wwid(f, wwid);
if (found) {
ret = 0;
goto out;
}
if (!write_wwid) {
ret = -1;
goto out;
}
if (!can_write) {
condlog(0, "wwids file is read-only. Can't write wwid");
ret = -1;
goto out;
}
if (fflush(f) != 0) {
condlog(0, "cannot fflush wwids file stream : %s",
strerror(errno));
ret = -1;
goto out;
}
ret = write_out_wwid(fd, wwid);
out:
fclose(f);
return ret;
}
int
should_multipath(struct path *pp1, vector pathvec, vector mpvec)
{
int i, ignore_new_devs, find_multipaths;
struct path *pp2;
struct config *conf;
conf = get_multipath_config();
ignore_new_devs = ignore_new_devs_on(conf);
find_multipaths = find_multipaths_on(conf);
put_multipath_config(conf);
if (!find_multipaths && !ignore_new_devs)
return 1;
condlog(4, "checking if %s should be multipathed", pp1->dev);
if (!ignore_new_devs) {
char tmp_wwid[WWID_SIZE];
struct multipath *mp = find_mp_by_wwid(mpvec, pp1->wwid);
if (mp != NULL &&
dm_get_uuid(mp->alias, tmp_wwid, WWID_SIZE) == 0 &&
!strncmp(tmp_wwid, pp1->wwid, WWID_SIZE)) {
condlog(3, "wwid %s is already multipathed, keeping it",
pp1->wwid);
return 1;
}
vector_foreach_slot(pathvec, pp2, i) {
if (pp1->dev == pp2->dev)
continue;
if (strncmp(pp1->wwid, pp2->wwid, WWID_SIZE) == 0) {
condlog(3, "found multiple paths with wwid %s, "
"multipathing %s", pp1->wwid, pp1->dev);
return 1;
}
}
}
if (check_wwids_file(pp1->wwid, 0) < 0) {
condlog(3, "wwid %s not in wwids file, skipping %s",
pp1->wwid, pp1->dev);
return 0;
}
condlog(3, "found wwid %s in wwids file, multipathing %s", pp1->wwid,
pp1->dev);
return 1;
}
int
remember_wwid(char *wwid)
{
int ret = check_wwids_file(wwid, 1);
if (ret < 0){
condlog(3, "failed writing wwid %s to wwids file", wwid);
return -1;
}
if (ret == 1)
condlog(3, "wrote wwid %s to wwids file", wwid);
else
condlog(4, "wwid %s already in wwids file", wwid);
return ret;
}
static const char shm_dir[] = MULTIPATH_SHM_BASE "failed_wwids";
static const char shm_lock[] = ".lock";
static const char shm_header[] = "multipath shm lock file, don't edit";
static char _shm_lock_path[sizeof(shm_dir)+sizeof(shm_lock)];
static const char *shm_lock_path = &_shm_lock_path[0];
static void init_shm_paths(void)
{
snprintf(_shm_lock_path, sizeof(_shm_lock_path),
"%s/%s", shm_dir, shm_lock);
}
static pthread_once_t shm_path_once = PTHREAD_ONCE_INIT;
static int multipath_shm_open(bool rw)
{
int fd;
int can_write;
pthread_once(&shm_path_once, init_shm_paths);
fd = open_file(shm_lock_path, &can_write, shm_header);
if (fd >= 0 && rw && !can_write) {
close(fd);
condlog(1, "failed to open %s for writing", shm_dir);
return -1;
}
return fd;
}
static void multipath_shm_close(void *arg)
{
long fd = (long)arg;
close(fd);
unlink(shm_lock_path);
}
static int _failed_wwid_op(const char *wwid, bool rw,
int (*func)(const char *), const char *msg)
{
char path[PATH_MAX];
long lockfd;
int r = -1;
if (safe_sprintf(path, "%s/%s", shm_dir, wwid)) {
condlog(1, "%s: path name overflow", __func__);
return -1;
}
lockfd = multipath_shm_open(rw);
if (lockfd == -1)
return -1;
pthread_cleanup_push(multipath_shm_close, (void *)lockfd);
r = func(path);
pthread_cleanup_pop(1);
if (r == WWID_FAILED_ERROR)
condlog(1, "%s: %s: %s", msg, wwid, strerror(errno));
else if (r == WWID_FAILED_CHANGED)
condlog(3, "%s: %s", msg, wwid);
else if (!rw)
condlog(4, "%s: %s is %s", msg, wwid,
r == WWID_IS_FAILED ? "failed" : "good");
return r;
}
static int _is_failed(const char *path)
{
struct stat st;
if (lstat(path, &st) == 0)
return WWID_IS_FAILED;
else if (errno == ENOENT)
return WWID_IS_NOT_FAILED;
else
return WWID_FAILED_ERROR;
}
static int _mark_failed(const char *path)
{
/* Called from _failed_wwid_op: we know that shm_lock_path exists */
if (_is_failed(path) == WWID_IS_FAILED)
return WWID_FAILED_UNCHANGED;
return (link(shm_lock_path, path) == 0 ? WWID_FAILED_CHANGED :
WWID_FAILED_ERROR);
}
static int _unmark_failed(const char *path)
{
if (_is_failed(path) == WWID_IS_NOT_FAILED)
return WWID_FAILED_UNCHANGED;
return (unlink(path) == 0 ? WWID_FAILED_CHANGED : WWID_FAILED_ERROR);
}
#define declare_failed_wwid_op(op, rw) \
int op ## _wwid(const char *wwid) \
{ \
return _failed_wwid_op(wwid, (rw), _ ## op, #op); \
}
declare_failed_wwid_op(is_failed, false)
declare_failed_wwid_op(mark_failed, true)
declare_failed_wwid_op(unmark_failed, true)
int remember_cmdline_wwid(void)
{
FILE *f = NULL;
char buf[LINE_MAX], *next, *ptr;
int ret = 0;
f = fopen("/proc/cmdline", "re");
if (!f) {
condlog(0, "can't open /proc/cmdline : %s", strerror(errno));
return -1;
}
if (!fgets(buf, sizeof(buf), f)) {
if (ferror(f))
condlog(0, "read of /proc/cmdline failed : %s",
strerror(errno));
else
condlog(0, "couldn't read /proc/cmdline");
fclose(f);
return -1;
}
fclose(f);
next = buf;
while((ptr = strstr(next, "mpath.wwid="))) {
ptr += 11;
next = strpbrk(ptr, " \t\n");
if (next) {
*next = '\0';
next++;
}
if (strlen(ptr)) {
if (remember_wwid(ptr) != 0)
ret = -1;
}
else {
condlog(0, "empty mpath.wwid kernel command line option");
ret = -1;
}
if (!next)
break;
}
return ret;
}