// 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 #endif // HAVE_CONFIG_H #include #include #include #include #include #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 \n"); fprintf(fptr, "\n"); fprintf(fptr, "\t-d,--daemon run as daemon process.\n"); fprintf(fptr, "\t-l,--logfile the log file for daemon mode [%s].\n", DEFAULT_LOG); fprintf(fptr, "\t-p,--pidfile the pid file for daemon mode [%s].\n", DEFAULT_PID); fprintf(fptr, "\t-s,--socket the unix domain socket [/tmp/fpga_event_socket].\n"); fprintf(fptr, "\t-n,--null-bitstream NULL bitstream (for AP6 handling, may be\n" "\t given multiple times).\n"); fprintf(fptr, "\t-c,--config 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; }