// Copyright(c) 2018-2020, Intel Corporation
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are met:
//
// * Redistributions of source code must retain the above copyright notice,
// this list of conditions and the following disclaimer.
// * Redistributions in binary form must reproduce the above copyright notice,
// this list of conditions and the following disclaimer in the documentation
// and/or other materials provided with the distribution.
// * Neither the name of Intel Corporation nor the names of its contributors
// may be used to endorse or promote products derived from this software
// without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
// POSSIBILITY OF SUCH DAMAGE.
#define _GNU_SOURCE
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif // HAVE_CONFIG_H
#include <getopt.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <pwd.h>
#include "command_line.h"
#include "config_file.h"
#include "monitored_device.h"
#ifdef LOG
#undef LOG
#endif
#define LOG(format, ...) \
log_printf("args: " format, ##__VA_ARGS__)
extern fpgad_supported_device default_supported_devices_table[];
#define OPT_STR ":hdl:p:s:n:c:v"
STATIC struct option longopts[] = {
{ "help", no_argument, NULL, 'h' },
{ "daemon", no_argument, NULL, 'd' },
{ "logfile", required_argument, NULL, 'l' },
{ "pidfile", required_argument, NULL, 'p' },
{ "socket", required_argument, NULL, 's' },
{ "null-bitstream", required_argument, NULL, 'n' },
{ "config", required_argument, NULL, 'c' },
{ "version", no_argument, NULL, 'v' },
{ 0, 0, 0, 0 }
};
#define DEFAULT_DIR_ROOT "/var/lib/opae"
#define DEFAULT_DIR_ROOT_SIZE 13
#define DEFAULT_LOG "fpgad.log"
#define DEFAULT_PID "fpgad.pid"
#define DEFAULT_CFG "fpgad.cfg"
void cmd_show_help(FILE *fptr)
{
fprintf(fptr, "Usage: fpgad <options>\n");
fprintf(fptr, "\n");
fprintf(fptr, "\t-d,--daemon run as daemon process.\n");
fprintf(fptr, "\t-l,--logfile <file> the log file for daemon mode [%s].\n", DEFAULT_LOG);
fprintf(fptr, "\t-p,--pidfile <file> the pid file for daemon mode [%s].\n", DEFAULT_PID);
fprintf(fptr, "\t-s,--socket <sock> the unix domain socket [/tmp/fpga_event_socket].\n");
fprintf(fptr, "\t-n,--null-bitstream <file> NULL bitstream (for AP6 handling, may be\n"
"\t given multiple times).\n");
fprintf(fptr, "\t-c,--config <file> the configuration file [%s].\n", DEFAULT_CFG);
fprintf(fptr, "\t-v,--version display the version and exit.\n");
}
STATIC bool cmd_register_null_gbs(struct fpgad_config *c, char *null_gbs_path)
{
char *canon_path = NULL;
if (c->num_null_gbs < (sizeof(c->null_gbs) / sizeof(c->null_gbs[0]))) {
canon_path = canonicalize_file_name(null_gbs_path);
if (canon_path) {
memset(&c->null_gbs[c->num_null_gbs], 0,
sizeof(opae_bitstream_info));
if (opae_load_bitstream(canon_path,
&c->null_gbs[c->num_null_gbs])) {
LOG("failed to load NULL GBS \"%s\"\n", canon_path);
opae_unload_bitstream(&c->null_gbs[c->num_null_gbs]);
free(canon_path);
return false;
}
c->num_null_gbs++;
LOG("registering NULL bitstream \"%s\"\n", canon_path);
} else {
LOG("error with NULL GBS argument: %s\n", strerror(errno));
return false;
}
} else {
LOG("maximum number of NULL bitstreams exceeded. Ignoring -n option.\n");
}
return true;
}
int cmd_parse_args(struct fpgad_config *c, int argc, char *argv[])
{
int getopt_ret;
int option_index;
size_t len;
while (-1 != (getopt_ret = getopt_long(argc, argv, OPT_STR, longopts, &option_index))) {
const char *tmp_optarg = optarg;
if (optarg && ('=' == *tmp_optarg))
++tmp_optarg;
if (!optarg && (optind < argc) &&
(NULL != argv[optind]) &&
('-' != argv[optind][0]))
tmp_optarg = argv[optind++];
switch (getopt_ret) {
case 'h':
cmd_show_help(stdout);
return -2;
break;
case 'd':
c->daemon = 1;
LOG("daemon requested\n");
break;
case 'l':
if (tmp_optarg) {
len = strnlen(tmp_optarg, PATH_MAX - 1);
memcpy(c->logfile, tmp_optarg, len);
c->logfile[len] = '\0';
} else {
LOG("missing logfile parameter.\n");
return 1;
}
break;
case 'p':
if (tmp_optarg) {
len = strnlen(tmp_optarg, PATH_MAX - 1);
memcpy(c->pidfile, tmp_optarg, len);
c->pidfile[len] = '\0';
} else {
LOG("missing pidfile parameter.\n");
return 1;
}
break;
case 'n':
if (tmp_optarg) {
if (!cmd_register_null_gbs(c, (char *)tmp_optarg)) {
LOG("invalid null gbs path: \"%s\"\n", tmp_optarg);
return 1;
}
} else {
LOG("missing bitstream parameter.\n");
return 1;
}
break;
case 's':
if (tmp_optarg) {
c->api_socket = tmp_optarg;
LOG("daemon socket is %s\n", c->api_socket);
} else {
LOG("missing socket parameter.\n");
return 1;
}
break;
case 'c':
if (tmp_optarg) {
len = strnlen(tmp_optarg, PATH_MAX - 1);
memcpy(c->cfgfile, tmp_optarg, len);
c->cfgfile[len] = '\0';
} else {
LOG("missing cfgfile parameter.\n");
return 1;
}
break;
case 'v':
fprintf(stdout, "fpgad %s %s%s\n",
OPAE_VERSION,
OPAE_GIT_COMMIT_HASH,
OPAE_GIT_SRC_TREE_DIRTY ? "*":"");
return -2;
break;
case ':':
LOG("Missing option argument.\n");
return 1;
case '?':
LOG("Invalid command option.\n");
return 1;
default:
LOG("Invalid command option.\n");
return 1;
}
}
return 0;
}
int cmd_canonicalize_paths(struct fpgad_config *c)
{
char *sub;
bool def;
mode_t mode;
struct stat stat_buf;
bool search = true;
char buf[PATH_MAX];
char *canon_path;
uid_t uid;
size_t len;
uid = geteuid();
if (!uid) {
// If we're being run as root, then use DEFAULT_DIR_ROOT
// as the working directory.
memcpy(c->directory, DEFAULT_DIR_ROOT, sizeof(DEFAULT_DIR_ROOT));
c->directory[sizeof(DEFAULT_DIR_ROOT)] = '\0';
mode = 0755;
c->filemode = 0026;
} else {
// We're not root. Try to use ${HOME}/.opae
struct passwd *passwd;
passwd = getpwuid(uid);
canon_path = canonicalize_file_name(passwd->pw_dir);
if (canon_path) {
snprintf(c->directory, sizeof(c->directory),
"%s/.opae", canon_path);
free(canon_path);
} else {
// ${HOME} not found or invalid - use current dir.
if (getcwd(buf, sizeof(buf))) {
if (snprintf(c->directory, sizeof(c->directory),
"%s/.opae", buf) < 0) {
len = strnlen("./.opae",
sizeof(c->directory) - 1);
memcpy(c->directory, "./.opae", len);
c->directory[len] = '\0';
}
} else {
// Current directory not found - use /
len = strnlen("/.opae", sizeof(c->directory) - 1);
memcpy(c->directory, "/.opae", len);
c->directory[len] = '\0';
}
}
mode = 0775;
c->filemode = 0022;
}
if (cmd_path_is_symlink(c->directory)) {
LOG("Aborting - working directory contains a link: %s\n.",
c->directory);
return 1;
}
LOG("daemon working directory is %s\n", c->directory);
// Create the directory if it doesn't exist.
if (lstat(c->directory, &stat_buf) && (errno == ENOENT)) {
if (mkdir(c->directory, mode)) {
LOG("mkdir failed\n");
return 1;
}
}
// Verify logfile and pidfile do not contain ".."
// nor "/".
def = false;
sub = strstr(c->logfile, "..");
if (sub)
def = true;
sub = strstr(c->logfile, "/");
if (sub)
def = true;
if (def || (c->logfile[0] == '\0')) {
if (snprintf(c->logfile, sizeof(c->logfile),
"%s/%s", c->directory, DEFAULT_LOG) < 0) {
len = strnlen("./" DEFAULT_LOG,
sizeof(c->logfile) - 1);
memcpy(c->logfile, "./" DEFAULT_LOG, len);
c->logfile[len] = '\0';
}
} else {
len = strnlen(c->logfile, sizeof(buf) - 1);
memcpy(buf, c->logfile, len);
buf[len] = '\0';
if (snprintf(c->logfile, sizeof(c->logfile),
"%s/%s", c->directory, buf) < 0) {
len = strnlen("./" DEFAULT_LOG,
sizeof(c->logfile) - 1);
memcpy(c->logfile, "./" DEFAULT_LOG, len);
c->logfile[len] = '\0';
}
}
if (cmd_path_is_symlink(c->logfile)) {
LOG("Aborting - log file path contains a link: %s\n.",
c->logfile);
return 1;
}
LOG("daemon log file is %s\n", c->logfile);
def = false;
sub = strstr(c->pidfile, "..");
if (sub)
def = true;
sub = strstr(c->pidfile, "/");
if (sub)
def = true;
if (def || (c->pidfile[0] == '\0')) {
if (snprintf(c->pidfile, sizeof(c->pidfile),
"%s/%s", c->directory, DEFAULT_PID) < 0) {
len = strnlen("./" DEFAULT_PID,
sizeof(c->pidfile) - 1);
memcpy(c->pidfile, "./" DEFAULT_PID, len);
c->pidfile[len] = '\0';
}
} else {
len = strnlen(c->pidfile, sizeof(buf) - 1);
memcpy(buf, c->pidfile, len);
buf[len] = '\0';
if (snprintf(c->pidfile, sizeof(c->pidfile),
"%s/%s", c->directory, buf) < 0) {
len = strnlen("./" DEFAULT_PID,
sizeof(c->pidfile) - 1);
memcpy(c->pidfile, "./" DEFAULT_PID, len);
c->pidfile[len] = '\0';
}
}
if (cmd_path_is_symlink(c->pidfile)) {
LOG("Aborting - pid file path contains a link: %s\n.",
c->pidfile);
return 1;
}
LOG("daemon pid file is %s\n", c->pidfile);
// Verify cfgfile doesn't contain ".."
def = false;
sub = strstr(c->cfgfile, "..");
if (sub)
def = true;
if (def || (c->cfgfile[0] == '\0')) {
search = true;
} else {
canon_path = canonicalize_file_name(c->cfgfile);
if (canon_path) {
if (!cmd_path_is_symlink(c->cfgfile)) {
len = strnlen(canon_path,
sizeof(c->cfgfile) - 1);
memcpy(c->cfgfile,
canon_path,
len);
c->cfgfile[len] = '\0';
if (!cfg_load_config(c)) {
LOG("daemon cfg file is %s\n",
c->cfgfile);
search = false; // found and loaded it
}
}
free(canon_path);
}
}
if (search) {
c->cfgfile[0] = '\0';
if (cfg_find_config_file(c))
LOG("failed to find config file.\n");
else {
if (cfg_load_config(c))
LOG("failed to load config file %s\n",
c->cfgfile);
else
LOG("daemon cfg file is %s\n", c->cfgfile);
}
}
if (!c->supported_devices) {
LOG("using default configuration.\n");
c->cfgfile[0] = '\0';
c->supported_devices = default_supported_devices_table;
}
return 0;
}
void cmd_destroy(struct fpgad_config *c)
{
unsigned i;
if (c->daemon)
unlink(c->pidfile);
for (i = 0 ; i < c->num_null_gbs ; ++i) {
if (c->null_gbs[i].filename)
free((char *)c->null_gbs[i].filename);
opae_unload_bitstream(&c->null_gbs[i]);
}
c->num_null_gbs = 0;
if (c->supported_devices &&
(c->supported_devices != default_supported_devices_table)) {
for (i = 0 ; c->supported_devices[i].library_path ; ++i) {
fpgad_supported_device *d = &c->supported_devices[i];
if (d->library_path)
free((void *)d->library_path);
if (d->config)
free((void *)d->config);
}
free(c->supported_devices);
}
c->supported_devices = NULL;
}
bool cmd_path_is_symlink(const char *path)
{
char component[PATH_MAX];
struct stat stat_buf;
size_t len;
char *pslash;
len = strnlen(path, PATH_MAX - 1);
if (!len) // empty path
return false;
memcpy(component, path, len);
component[len] = '\0';
if (component[0] == '/') {
// absolute path
pslash = realpath(path, component);
if (strcmp(component, path))
return true;
} else {
// relative path
pslash = strrchr(component, '/');
while (pslash) {
if (fstatat(AT_FDCWD, component,
&stat_buf, AT_SYMLINK_NOFOLLOW)) {
return false;
}
if (S_ISLNK(stat_buf.st_mode))
return true;
*pslash = '\0';
pslash = strrchr(component, '/');
}
if (fstatat(AT_FDCWD, component,
&stat_buf, AT_SYMLINK_NOFOLLOW)) {
return false;
}
if (S_ISLNK(stat_buf.st_mode))
return true;
}
return false;
}