/*
* Copyright (c) 2017 Red Hat, Inc.
*
* This file is licensed to you under your choice of the GNU Lesser
* General Public License, version 2.1 or any later version (LGPLv2.1 or
* later), or the Apache License 2.0.
*/
#define _GNU_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <fcntl.h>
#include <string.h>
#include <errno.h>
#include <inttypes.h>
#include "libtcmu_log.h"
#include "libtcmu_common.h"
#include "libtcmu_priv.h"
#define CFGFS_BUF_SIZE 4096
int tcmu_cfgfs_get_int(const char *path)
{
int fd;
char buf[16];
ssize_t ret;
unsigned long val;
fd = open(path, O_RDONLY);
if (fd == -1) {
if (errno == ENOENT) {
tcmu_err("Kernel does not support configfs file %s.\n",
path);
} else {
tcmu_err("Could not open configfs file %s: %s\n",
path, strerror(errno));
}
return -errno;
}
ret = read(fd, buf, sizeof(buf));
close(fd);
if (ret == -1) {
tcmu_err("Could not read configfs to read attribute %s: %s\n",
path, strerror(errno));
return -errno;
}
val = strtoul(buf, NULL, 0);
if (val > INT_MAX ) {
tcmu_err("could not convert string %s to value\n", buf);
return -EINVAL;
}
return val;
}
int tcmu_cfgfs_dev_get_attr_int(struct tcmu_device *dev, const char *name)
{
char path[PATH_MAX];
snprintf(path, sizeof(path), CFGFS_CORE"/%s/%s/attrib/%s",
dev->tcm_hba_name, dev->tcm_dev_name, name);
return tcmu_cfgfs_get_int(path);
}
uint64_t tcmu_cfgfs_dev_get_info_u64(struct tcmu_device *dev, const char *name,
int *fn_ret)
{
int fd;
char path[PATH_MAX];
char buf[CFGFS_BUF_SIZE];
ssize_t ret;
char *rover;
char *search_pattern;
uint64_t val;
*fn_ret = 0;
snprintf(path, sizeof(path), CFGFS_CORE"/%s/%s/info",
dev->tcm_hba_name, dev->tcm_dev_name);
fd = open(path, O_RDONLY);
if (fd == -1) {
if (errno == ENOENT) {
tcmu_err("Kernel does not support device info file %s.\n",
path);
} else {
tcmu_err("Could not open device info file %s: %s\n",
path, strerror(errno));
}
*fn_ret = -errno;
return 0;
}
ret = read(fd, buf, sizeof(buf));
close(fd);
if (ret == -1) {
tcmu_err("Could not read configfs to read dev info: %s\n",
strerror(errno));
*fn_ret = -EINVAL;
return 0;
} else if (ret == 0) {
tcmu_err("Invalid device info.\n");
*fn_ret = -EINVAL;
return 0;
}
buf[ret-1] = '\0'; /* paranoid? Ensure null terminated */
if (asprintf(&search_pattern, " %s: ", name) < 0) {
tcmu_err("Could not create search string.\n");
*fn_ret = -ENOMEM;
return 0;
}
rover = strstr(buf, search_pattern);
free(search_pattern);
if (!rover) {
tcmu_err("Could not find \" %s: \" in %s: %s\n", name, path,
strerror(errno));
*fn_ret = -EINVAL;
return 0;
}
rover += strlen(name) + 3; /* name plus ':' and spaces before/after */
val = strtoull(rover, NULL, 0);
if (val == ULLONG_MAX) {
tcmu_err("Could not get %s: %s\n", name, strerror(errno));
*fn_ret = -EINVAL;
return 0;
}
return val;
}
int tcmu_cfgfs_dev_set_ctrl_u64(struct tcmu_device *dev, const char *key,
uint64_t val)
{
char path[PATH_MAX];
char buf[CFGFS_BUF_SIZE];
snprintf(path, sizeof(path), CFGFS_CORE"/%s/%s/control",
dev->tcm_hba_name, dev->tcm_dev_name);
snprintf(buf, sizeof(buf), "%s=%"PRIu64"", key, val);
return tcmu_cfgfs_set_str(path, buf, strlen(buf) + 1);
}
int tcmu_cfgfs_mod_param_set_u32(const char *name, uint32_t val)
{
char path[PATH_MAX];
snprintf(path, sizeof(path), CFGFS_MOD_PARAM"/%s", name);
return tcmu_cfgfs_set_u32(path, val);
}
/*
* Return a string that contains the device's WWN, or NULL.
*
* Callers must free the result with free().
*/
char *tcmu_cfgfs_dev_get_wwn(struct tcmu_device *dev)
{
int fd;
char path[PATH_MAX];
char buf[CFGFS_BUF_SIZE];
char *ret_buf;
int ret;
snprintf(path, sizeof(path),
CFGFS_CORE"/%s/%s/wwn/vpd_unit_serial",
dev->tcm_hba_name, dev->tcm_dev_name);
fd = open(path, O_RDONLY);
if (fd == -1) {
if (errno == ENOENT) {
tcmu_err("Kernel does not support unit serial file %s.\n",
path);
} else {
tcmu_err("Could not open unit serial file %s: %s\n",
path, strerror(errno));
}
return NULL;
}
ret = read(fd, buf, sizeof(buf));
close(fd);
if (ret == -1) {
tcmu_err("Could not read configfs to read unit serial: %s\n",
strerror(errno));
return NULL;
} else if (ret == 0) {
tcmu_err("Invalid VPD serial number.\n");
return NULL;
}
/* Kill the trailing '\n' */
buf[ret-1] = '\0';
/* Skip to the good stuff */
ret = asprintf(&ret_buf, "%s", &buf[28]);
if (ret == -1) {
tcmu_err("could not convert string to value: %s\n",
strerror(errno));
return NULL;
}
return ret_buf;
}
char *tcmu_cfgfs_get_str(const char *path)
{
int fd, n;
char buf[CFGFS_BUF_SIZE];
ssize_t ret;
char *val;
memset(buf, 0, sizeof(buf));
fd = open(path, O_RDONLY);
if (fd == -1) {
if (errno == ENOENT) {
tcmu_err("Kernel does not support configfs file %s.\n",
path);
} else {
tcmu_err("Could not open configfs file %s: %s\n",
path, strerror(errno));
}
return NULL;
}
ret = read(fd, buf, sizeof(buf));
close(fd);
if (ret == -1) {
tcmu_err("Could not read configfs to read attribute %s: %s\n",
path, strerror(errno));
return NULL;
}
if (ret == 0)
return NULL;
/*
* Some files like members will terminate each member/line with a null
* char. Except for the last one, replace it with '\n' so parsers will
* just see an empty member.
*/
if (ret != strlen(buf)) {
do {
n = strlen(buf);
buf[n] = '\n';
} while (n < ret);
}
/*
* Some files like members ends with a null char, but other files like
* the alua ones end with a newline.
*/
if (buf[ret - 1] == '\n')
buf[ret - 1] = '\0';
if (buf[ret - 1] != '\0') {
if (ret >= CFGFS_BUF_SIZE) {
tcmu_err("Invalid cfgfs file %s: not enough space for ending null char.\n",
path);
return NULL;
}
/*
* In case the file does "return sprintf()" with no ending
* newline add the ending null so we will not crash below.
*/
buf[ret] = '\0';
}
val = strdup(buf);
if (!val) {
tcmu_err("could not copy buffer %s : %s\n",
buf, strerror(errno));
return NULL;
}
return val;
}
int tcmu_cfgfs_set_str(const char *path, const char *val, int val_len)
{
int fd;
ssize_t ret;
fd = open(path, O_WRONLY);
if (fd == -1) {
if (errno == ENOENT) {
tcmu_err("Kernel does not support configfs file %s.\n",
path);
} else {
tcmu_err("Could not open configfs file %s: %s\n",
path, strerror(errno));
}
return -errno;
}
ret = write(fd, val, val_len);
close(fd);
if (ret == -1) {
tcmu_err("Could not write configfs to write attribute %s: %s\n",
path, strerror(errno));
return -errno;
}
return 0;
}
int tcmu_cfgfs_set_u32(const char *path, uint32_t val)
{
char buf[20];
sprintf(buf, "%"PRIu32"", val);
return tcmu_cfgfs_set_str(path, buf, strlen(buf) + 1);
}
int tcmu_cfgfs_dev_exec_action(struct tcmu_device *dev, const char *name,
uint32_t val)
{
char path[PATH_MAX];
int ret;
snprintf(path, sizeof(path), CFGFS_CORE"/%s/%s/action/%s",
dev->tcm_hba_name, dev->tcm_dev_name, name);
tcmu_dev_dbg(dev, "executing action %s\n", name);
ret = tcmu_cfgfs_set_u32(path, val);
tcmu_dev_dbg(dev, "action %s done\n", name);
return ret;
}