diff --git a/Android.bp b/Android.bp index b2c5090..693caaf 100644 --- a/Android.bp +++ b/Android.bp @@ -2,8 +2,10 @@ cc_binary { name: "tqftpserv", vendor: true, srcs: [ + "config.c", + "logging.c", "tqftpserv.c", "translate.c", ], - shared_libs: ["libqrtr"], + shared_libs: ["libqrtr", "libconfig"], } diff --git a/config.c b/config.c new file mode 100644 index 0000000..9d2412d --- /dev/null +++ b/config.c @@ -0,0 +1,296 @@ +// SPDX-License-Identifier: BSD-3-Clause +/* + * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries. + */ + +#include +#include +#include +#include +#include +#include +#include +#include "config.h" +#include "logging.h" + +/* Global configuration instance */ +struct tqftp_config tqftp_config; + +/** + * tqftp_config_init_defaults() - Initialize configuration with default values + * @config: Configuration structure to initialize + */ +void tqftp_config_init_defaults(struct tqftp_config *config) +{ + /* Initialize path configuration */ + strncpy(config->readonly_path, DEFAULT_READONLY_PATH, sizeof(config->readonly_path) - 1); + strncpy(config->readwrite_path, DEFAULT_READWRITE_PATH, sizeof(config->readwrite_path) - 1); + strncpy(config->firmware_base, DEFAULT_FIRMWARE_BASE, sizeof(config->firmware_base) - 1); + strncpy(config->updates_dir, DEFAULT_UPDATES_DIR, sizeof(config->updates_dir) - 1); + strncpy(config->temp_dir, DEFAULT_TEMP_DIR, sizeof(config->temp_dir) - 1); + strncpy(config->config_file, DEFAULT_CONFIG_FILE, sizeof(config->config_file) - 1); + + /* Null terminate all strings */ + config->readonly_path[sizeof(config->readonly_path) - 1] = '\0'; + config->readwrite_path[sizeof(config->readwrite_path) - 1] = '\0'; + config->firmware_base[sizeof(config->firmware_base) - 1] = '\0'; + config->updates_dir[sizeof(config->updates_dir) - 1] = '\0'; + config->temp_dir[sizeof(config->temp_dir) - 1] = '\0'; + config->config_file[sizeof(config->config_file) - 1] = '\0'; + + /* Initialize logging configuration - always use LOG_DAEMON as per @lumag's feedback */ + config->log_config.facility = LOG_DAEMON; + config->log_config.min_level = LOG_INFO; + config->log_config.options = LOG_PID | LOG_CONS; + config->log_config.use_console = 0; + config->log_config.ident = "tqftpserv"; +} + +/** + * set_config_string() - Safely set a configuration string value + * @dest: Destination buffer + * @dest_size: Size of destination buffer + * @src: Source string + * @name: Configuration parameter name (for logging) + */ +static void set_config_string(char *dest, size_t dest_size, const char *src, const char *name) +{ + if (strlen(src) >= dest_size) { + TQFTP_LOG_WARNING("Configuration value for '%s' too long, truncating", name); + } + strncpy(dest, src, dest_size - 1); + dest[dest_size - 1] = '\0'; +} + +/** + * tqftp_config_load_file() - Load configuration from file using libconfig + * @config: Configuration structure to populate + * @filename: Path to configuration file + * + * Uses libconfig library for robust configuration parsing with: + * - Proper syntax validation + * - Type checking + * - Hierarchical configuration support + * - Better error reporting + * + * Return: 0 on success, -1 on error + */ +int tqftp_config_load_file(struct tqftp_config *config, const char *filename) +{ + config_t cfg; + const char *str_val; + int int_val; + int ret = 0; + + config_init(&cfg); + + /* Read the configuration file */ + if (!config_read_file(&cfg, filename)) { + if (config_error_type(&cfg) != CONFIG_ERR_FILE_IO) { + TQFTP_LOG_ERR("Configuration error in %s:%d - %s", + filename, config_error_line(&cfg), config_error_text(&cfg)); + } else { + /* File doesn't exist - this is not an error for optional config */ + TQFTP_LOG_DEBUG("Configuration file %s not found, using defaults", filename); + } + config_destroy(&cfg); + return -1; + } + + TQFTP_LOG_INFO("Loading configuration from %s", filename); + + /* Load path configuration */ + if (config_lookup_string(&cfg, "readonly_path", &str_val)) { + set_config_string(config->readonly_path, sizeof(config->readonly_path), str_val, "readonly_path"); + TQFTP_LOG_DEBUG("Config: readonly_path = %s", str_val); + } + + if (config_lookup_string(&cfg, "readwrite_path", &str_val)) { + set_config_string(config->readwrite_path, sizeof(config->readwrite_path), str_val, "readwrite_path"); + TQFTP_LOG_DEBUG("Config: readwrite_path = %s", str_val); + } + + if (config_lookup_string(&cfg, "firmware_base", &str_val)) { + set_config_string(config->firmware_base, sizeof(config->firmware_base), str_val, "firmware_base"); + TQFTP_LOG_DEBUG("Config: firmware_base = %s", str_val); + } + + if (config_lookup_string(&cfg, "updates_dir", &str_val)) { + set_config_string(config->updates_dir, sizeof(config->updates_dir), str_val, "updates_dir"); + TQFTP_LOG_DEBUG("Config: updates_dir = %s", str_val); + } + + if (config_lookup_string(&cfg, "temp_dir", &str_val)) { + set_config_string(config->temp_dir, sizeof(config->temp_dir), str_val, "temp_dir"); + TQFTP_LOG_DEBUG("Config: temp_dir = %s", str_val); + } + + /* Load logging configuration */ + if (config_lookup_string(&cfg, "log_level", &str_val)) { + int level = tqftp_parse_log_level(str_val); + if (level >= 0) { + config->log_config.min_level = level; + TQFTP_LOG_DEBUG("Config: log_level = %s (%d)", str_val, level); + } else { + TQFTP_LOG_WARNING("Invalid log level '%s' in config file", str_val); + } + } + + /* Log facility is always LOG_DAEMON as per @lumag's feedback */ + if (config_lookup_string(&cfg, "log_facility", &str_val)) { + config->log_config.facility = LOG_DAEMON; + TQFTP_LOG_INFO("Log facility is fixed to 'daemon' (ignoring config value '%s')", str_val); + } + + if (config_lookup_bool(&cfg, "log_console", &int_val)) { + config->log_config.use_console = int_val ? 1 : 0; + TQFTP_LOG_DEBUG("Config: log_console = %s", int_val ? "true" : "false"); + } + + /* Support both boolean and string formats for log_console */ + if (config_lookup_string(&cfg, "log_console", &str_val)) { + if (strcasecmp(str_val, "yes") == 0 || strcasecmp(str_val, "true") == 0 || strcmp(str_val, "1") == 0) { + config->log_config.use_console = 1; + } else if (strcasecmp(str_val, "no") == 0 || strcasecmp(str_val, "false") == 0 || strcmp(str_val, "0") == 0) { + config->log_config.use_console = 0; + } else { + TQFTP_LOG_WARNING("Invalid log_console value '%s' (use true/false or yes/no)", str_val); + } + TQFTP_LOG_DEBUG("Config: log_console = %s", config->log_config.use_console ? "true" : "false"); + } + + config_destroy(&cfg); + TQFTP_LOG_INFO("Configuration loaded successfully from %s", filename); + return ret; +} + +/** + * tqftp_config_print_help() - Print help message + * @progname: Program name + */ +void tqftp_config_print_help(const char *progname) +{ + printf("Usage: %s [OPTIONS]\n", progname); + printf("\nTQFTP Server - TFTP over QRTR\n"); + printf("\nPath Configuration:\n"); + printf(" --readonly-path PATH Set readonly path prefix (default: %s)\n", DEFAULT_READONLY_PATH); + printf(" --readwrite-path PATH Set readwrite path prefix (default: %s)\n", DEFAULT_READWRITE_PATH); + printf(" --firmware-base PATH Set firmware base directory (default: %s)\n", DEFAULT_FIRMWARE_BASE); + printf(" --updates-dir DIR Set updates directory name (default: %s)\n", DEFAULT_UPDATES_DIR); + printf(" --temp-dir PATH Set temporary directory (default: %s)\n", DEFAULT_TEMP_DIR); + printf("\nLogging Options:\n"); + printf(" -l, --log-level LEVEL Set minimum log level\n"); + printf(" (emerg, alert, crit, err, warn, notice, info, debug)\n"); + printf(" Default: info\n"); + printf(" -c, --console Log to console instead of syslog\n"); + printf(" (Note: log facility is always 'daemon')\n"); + printf("\nConfiguration File:\n"); + printf(" --config FILE Load configuration from file (default: %s)\n", DEFAULT_CONFIG_FILE); + printf(" --no-config Don't load default configuration file\n"); + printf("\nGeneral Options:\n"); + printf(" -h, --help Show this help message\n"); + printf("\nConfiguration File Format (libconfig syntax):\n"); + printf(" # Comments start with # or //\n"); + printf(" readonly_path = \"/readonly/firmware/image/\";\n"); + printf(" readwrite_path = \"/readwrite/\";\n"); + printf(" firmware_base = \"/lib/firmware/\";\n"); + printf(" updates_dir = \"updates/\";\n"); + printf(" temp_dir = \"/tmp/tqftpserv\";\n"); + printf(" log_level = \"info\";\n"); + printf(" log_console = false; # or true, yes, no\n"); + printf("\nExamples:\n"); + printf(" %s # Run with default settings\n", progname); + printf(" %s -l debug -c # Debug level logging to console\n", progname); + printf(" %s --firmware-base /custom/firmware # Use custom firmware directory\n", progname); + printf(" %s --config /etc/custom.conf # Use custom config file\n", progname); + printf("\n"); +} + +/** + * tqftp_config_parse_args() - Parse command line arguments + * @argc: Argument count + * @argv: Argument vector + * @config: Configuration structure to populate + * + * Return: 0 on success, 1 if help shown, -1 on error + */ +int tqftp_config_parse_args(int argc, char **argv, struct tqftp_config *config) +{ + int opt; + int level; + int load_config_file = 1; + + static struct option long_options[] = { + {"readonly-path", required_argument, 0, 1001}, + {"readwrite-path", required_argument, 0, 1002}, + {"firmware-base", required_argument, 0, 1003}, + {"updates-dir", required_argument, 0, 1004}, + {"temp-dir", required_argument, 0, 1005}, + {"config", required_argument, 0, 1006}, + {"no-config", no_argument, 0, 1007}, + {"log-level", required_argument, 0, 'l'}, + {"console", no_argument, 0, 'c'}, + {"help", no_argument, 0, 'h'}, + {0, 0, 0, 0} + }; + + while ((opt = getopt_long(argc, argv, "l:ch", long_options, NULL)) != -1) { + switch (opt) { + case 1001: /* --readonly-path */ + set_config_string(config->readonly_path, sizeof(config->readonly_path), optarg, "readonly-path"); + break; + + case 1002: /* --readwrite-path */ + set_config_string(config->readwrite_path, sizeof(config->readwrite_path), optarg, "readwrite-path"); + break; + + case 1003: /* --firmware-base */ + set_config_string(config->firmware_base, sizeof(config->firmware_base), optarg, "firmware-base"); + break; + + case 1004: /* --updates-dir */ + set_config_string(config->updates_dir, sizeof(config->updates_dir), optarg, "updates-dir"); + break; + + case 1005: /* --temp-dir */ + set_config_string(config->temp_dir, sizeof(config->temp_dir), optarg, "temp-dir"); + break; + + case 1006: /* --config */ + set_config_string(config->config_file, sizeof(config->config_file), optarg, "config"); + break; + + case 1007: /* --no-config */ + load_config_file = 0; + break; + + case 'l': + level = tqftp_parse_log_level(optarg); + if (level < 0) { + fprintf(stderr, "Invalid log level: %s\n", optarg); + return -1; + } + config->log_config.min_level = level; + break; + + case 'c': + config->log_config.use_console = 1; + break; + + case 'h': + tqftp_config_print_help(argv[0]); + return 1; + + default: + tqftp_config_print_help(argv[0]); + return -1; + } + } + + /* Load configuration file if requested */ + if (load_config_file && access(config->config_file, R_OK) == 0) { + tqftp_config_load_file(config, config->config_file); + } + + return 0; +} diff --git a/config.h b/config.h new file mode 100644 index 0000000..fc739d9 --- /dev/null +++ b/config.h @@ -0,0 +1,53 @@ +// SPDX-License-Identifier: BSD-3-Clause +/* + * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries. + */ + +#ifndef __CONFIG_H__ +#define __CONFIG_H__ + +#include +#include "logging.h" + +/* Configuration structure for TQFTP server */ +struct tqftp_config { + /* Path configuration */ + char readonly_path[PATH_MAX]; + char readwrite_path[PATH_MAX]; + char firmware_base[PATH_MAX]; + char updates_dir[PATH_MAX]; + char temp_dir[PATH_MAX]; + + /* Logging configuration */ + struct tqftp_log_config log_config; + + /* Configuration file path */ + char config_file[PATH_MAX]; +}; + +/* Default configuration values */ +#define DEFAULT_READONLY_PATH "/readonly/firmware/image/" +#define DEFAULT_READWRITE_PATH "/readwrite/" + +#ifndef ANDROID +#define DEFAULT_FIRMWARE_BASE "/lib/firmware/" +#define DEFAULT_TEMP_DIR "/tmp/tqftpserv" +#define DEFAULT_UPDATES_DIR "updates/" +#else +#define DEFAULT_FIRMWARE_BASE "/vendor/firmware/" +#define DEFAULT_TEMP_DIR "/data/vendor/tmp/tqftpserv" +#define DEFAULT_UPDATES_DIR "updates/" +#endif + +#define DEFAULT_CONFIG_FILE "/etc/tqftpserv.conf" + +/* Global configuration */ +extern struct tqftp_config tqftp_config; + +/* Function prototypes */ +void tqftp_config_init_defaults(struct tqftp_config *config); +int tqftp_config_load_file(struct tqftp_config *config, const char *filename); +int tqftp_config_parse_args(int argc, char **argv, struct tqftp_config *config); +void tqftp_config_print_help(const char *progname); + +#endif /* __CONFIG_H__ */ diff --git a/logging.c b/logging.c new file mode 100644 index 0000000..4e50420 --- /dev/null +++ b/logging.c @@ -0,0 +1,122 @@ +// SPDX-License-Identifier: BSD-3-Clause-Clear +/* + * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries. + */ + +#include +#include +#include +#include +#include +#include "logging.h" + +/* Global logging configuration */ +struct tqftp_log_config tqftp_log_cfg = { + .facility = LOG_DAEMON, + .min_level = LOG_INFO, + .options = LOG_PID | LOG_CONS, + .use_console = 0, + .ident = "tqftpserv" +}; + +/* Log level name mappings */ +static const struct { + const char *name; + int level; +} log_levels[] = { + {"emerg", LOG_EMERG}, + {"emergency", LOG_EMERG}, + {"alert", LOG_ALERT}, + {"crit", LOG_CRIT}, + {"critical", LOG_CRIT}, + {"err", LOG_ERR}, + {"error", LOG_ERR}, + {"warn", LOG_WARNING}, + {"warning", LOG_WARNING}, + {"notice", LOG_NOTICE}, + {"info", LOG_INFO}, + {"debug", LOG_DEBUG}, + {NULL, -1} +}; + + +/** + * tqftp_log_init() - Initialize logging with default configuration + * @ident: Program identifier for log messages + */ +void tqftp_log_init(const char *ident) +{ + tqftp_log_cfg.ident = ident; + openlog(tqftp_log_cfg.ident, tqftp_log_cfg.options, tqftp_log_cfg.facility); +} + +/** + * tqftp_log_init_with_config() - Initialize logging with custom configuration + * @config: Logging configuration structure + */ +void tqftp_log_init_with_config(struct tqftp_log_config *config) +{ + if (config) { + tqftp_log_cfg = *config; + } + + if (!tqftp_log_cfg.use_console) { + openlog(tqftp_log_cfg.ident, tqftp_log_cfg.options, tqftp_log_cfg.facility); + } +} + +/** + * tqftp_log() - Internal logging function + * @priority: Syslog priority level + * @fmt: Format string + * @...: Format arguments + */ +void tqftp_log(int priority, const char *fmt, ...) +{ + va_list args; + char buffer[1024]; + time_t now; + struct tm *tm_info; + char time_str[64]; + + va_start(args, fmt); + + if (tqftp_log_cfg.use_console) { + /* Log to console with timestamp */ + time(&now); + tm_info = localtime(&now); + strftime(time_str, sizeof(time_str), "%Y-%m-%d %H:%M:%S", tm_info); + + vsnprintf(buffer, sizeof(buffer), fmt, args); + fprintf(stderr, "%s [TQFTP] %s\n", time_str, buffer); + fflush(stderr); + } else { + /* Log to syslog */ + vsnprintf(buffer, sizeof(buffer), fmt, args); + syslog(priority, "[TQFTP] %s", buffer); + } + + va_end(args); +} + +/** + * tqftp_parse_log_level() - Parse log level string + * @level_str: Log level string (e.g., "info", "debug", "error") + * + * Return: Log level constant on success, -1 on error + */ +int tqftp_parse_log_level(const char *level_str) +{ + int i; + + if (!level_str) + return -1; + + for (i = 0; log_levels[i].name; i++) { + if (!strcasecmp(level_str, log_levels[i].name)) { + return log_levels[i].level; + } + } + + return -1; +} diff --git a/logging.h b/logging.h new file mode 100644 index 0000000..e3c2ea6 --- /dev/null +++ b/logging.h @@ -0,0 +1,51 @@ +// SPDX-License-Identifier: BSD-3-Clause +/* + * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries. + */ + +#ifndef __LOGGING_H__ +#define __LOGGING_H__ + +#include +#include +#include +#include + +/* Logging configuration structure */ +struct tqftp_log_config { + int facility; + int min_level; + int options; + int use_console; + const char *ident; +}; + +/* Global logging configuration */ +extern struct tqftp_log_config tqftp_log_cfg; + +/* Initialize syslog for the application */ +#define LOG_INIT(ident) tqftp_log_init(ident) +#define LOG_INIT_WITH_CONFIG(config) tqftp_log_init_with_config(config) + +/* Close syslog */ +#define LOG_CLOSE() closelog() + +/* Function prototypes */ +void tqftp_log_init(const char *ident); +void tqftp_log_init_with_config(struct tqftp_log_config *config); +int tqftp_parse_log_level(const char *level_str); + +/* Internal logging function */ +void tqftp_log(int priority, const char *fmt, ...); + +/* Project-specific logging macros with different priority levels */ +#define TQFTP_LOG_EMERG(fmt, ...) do { if (LOG_EMERG >= tqftp_log_cfg.min_level) tqftp_log(LOG_EMERG, fmt, ##__VA_ARGS__); } while(0) +#define TQFTP_LOG_ALERT(fmt, ...) do { if (LOG_ALERT >= tqftp_log_cfg.min_level) tqftp_log(LOG_ALERT, fmt, ##__VA_ARGS__); } while(0) +#define TQFTP_LOG_CRIT(fmt, ...) do { if (LOG_CRIT >= tqftp_log_cfg.min_level) tqftp_log(LOG_CRIT, fmt, ##__VA_ARGS__); } while(0) +#define TQFTP_LOG_ERR(fmt, ...) do { if (LOG_ERR >= tqftp_log_cfg.min_level) tqftp_log(LOG_ERR, fmt, ##__VA_ARGS__); } while(0) +#define TQFTP_LOG_WARNING(fmt, ...) do { if (LOG_WARNING >= tqftp_log_cfg.min_level) tqftp_log(LOG_WARNING, fmt, ##__VA_ARGS__); } while(0) +#define TQFTP_LOG_NOTICE(fmt, ...) do { if (LOG_NOTICE >= tqftp_log_cfg.min_level) tqftp_log(LOG_NOTICE, fmt, ##__VA_ARGS__); } while(0) +#define TQFTP_LOG_INFO(fmt, ...) do { if (LOG_INFO >= tqftp_log_cfg.min_level) tqftp_log(LOG_INFO, fmt, ##__VA_ARGS__); } while(0) +#define TQFTP_LOG_DEBUG(fmt, ...) do { if (LOG_DEBUG >= tqftp_log_cfg.min_level) tqftp_log(LOG_DEBUG, fmt, ##__VA_ARGS__); } while(0) + +#endif /* __LOGGING_H__ */ diff --git a/meson.build b/meson.build index 3c7a067..2da8b66 100644 --- a/meson.build +++ b/meson.build @@ -4,15 +4,21 @@ project('tqftpserv', 'c', version: '1.1', default_options: [ - 'warning_level=1', + 'warning_level=3', 'buildtype=release', + 'werror=true' ]) prefix = get_option('prefix') +# Add specific warning flags not covered by warning_level=3 +add_project_arguments('-Wsign-compare', '-Wunused-parameter', '-Wtype-limits', '-Wimplicit-function-declaration', language : 'c') + zstd_dep = dependency('libzstd') add_project_arguments('-DHAVE_ZSTD', language : 'c') +libconfig_dep = dependency('libconfig') + # Not required to build the executable, only to install unit file systemd = dependency('systemd', required : false) if systemd.found() @@ -26,12 +32,14 @@ endif qrtr_dep = dependency('qrtr') -tqftpserv_srcs = ['translate.c', +tqftpserv_srcs = ['config.c', + 'logging.c', + 'translate.c', 'tqftpserv.c', 'zstd-decompress.c'] executable('tqftpserv', tqftpserv_srcs, - dependencies : [qrtr_dep, zstd_dep], + dependencies : [qrtr_dep, zstd_dep, libconfig_dep], install : true) if systemd.found() diff --git a/tqftpserv.c b/tqftpserv.c index 9f7c15b..6322e1d 100644 --- a/tqftpserv.c +++ b/tqftpserv.c @@ -15,6 +15,8 @@ #include "list.h" #include "translate.h" +#include "logging.h" +#include "config.h" #define MAX(x, y) ((x) > (y) ? (x) : (y)) @@ -61,9 +63,9 @@ static ssize_t tftp_send_data(struct tftp_client *client, unsigned int block, size_t offset, size_t response_size) { ssize_t len; - size_t send_len; - char *buf = client->blk_buf; - char *p = buf; + ssize_t send_len; + uint8_t *buf = client->blk_buf; + uint8_t *p = buf; *p++ = 0; *p++ = OP_DATA; @@ -73,7 +75,7 @@ static ssize_t tftp_send_data(struct tftp_client *client, len = pread(client->fd, p, client->blksize, offset); if (len < 0) { - printf("[TQFTP] failed to read data\n"); + TQFTP_LOG_ERR("failed to read data"); free(buf); return len; } @@ -108,7 +110,7 @@ static int tftp_send_ack(int sock, int block) return send(sock, &ack, sizeof(ack), 0); } -static int tftp_send_oack(int sock, size_t *blocksize, size_t *tsize, +static int tftp_send_oack(int sock, size_t *blocksize, ssize_t *tsize, size_t *wsize, unsigned int *timeoutms, size_t *rsize, off_t *seek) { @@ -234,7 +236,7 @@ static void parse_options(const char *buf, size_t len, size_t *blksize, } else if (!strcmp(opt, "seek")) { *seek = atoi(value); } else { - printf("[TQFTP] Ignoring unknown option '%s' with value '%s'\n", opt, value); + TQFTP_LOG_WARN("Ignoring unknown option '%s' with value '%s'", opt, value); } } } @@ -267,7 +269,7 @@ static void handle_rrq(const char *buf, size_t len, struct sockaddr_qrtr *sq) if (strcasecmp(mode, "octet")) { /* XXX: error */ - printf("[TQFTP] not octet, reject\n"); + TQFTP_LOG_ERR("Mode is not octet, reject"); return; } @@ -277,25 +279,25 @@ static void handle_rrq(const char *buf, size_t len, struct sockaddr_qrtr *sq) &timeoutms, &rsize, &seek); } - printf("[TQFTP] RRQ: %s (mode=%s rsize=%ld seek=%ld)\n", filename, mode, rsize, seek); + TQFTP_LOG_DEBUG("RRQ: %s (mode=%s rsize=%ld seek=%ld)", filename, mode, rsize, seek); sock = qrtr_open(0); if (sock < 0) { /* XXX: error */ - printf("[TQFTP] unable to create new qrtr socket, reject\n"); + TQFTP_LOG_ERR("unable to create new qrtr socket, reject"); return; } ret = connect(sock, (struct sockaddr *)sq, sizeof(*sq)); if (ret < 0) { /* XXX: error */ - printf("[TQFTP] unable to connect new qrtr socket to remote\n"); + TQFTP_LOG_ERR("unable to connect new qrtr socket to remote"); return; } fd = translate_open(filename, O_RDONLY); if (fd < 0) { - printf("[TQFTP] unable to open %s (%d), reject\n", filename, errno); + TQFTP_LOG_ERR("unable to open %s (%d), reject", filename, errno); tftp_send_error(sock, 1, "file not found"); return; } @@ -318,23 +320,23 @@ static void handle_rrq(const char *buf, size_t len, struct sockaddr_qrtr *sq) client->blk_buf = calloc(1, blksize + 4); if (!client->blk_buf) { - printf("[TQFTP] Memory allocation failure\n"); + TQFTP_LOG_ERR("Memory allocation failure"); return; } client->rw_buf = calloc(1, client->rw_buf_size); if (!client->rw_buf) { - printf("[TQFTP] Memory allocation failure\n"); + TQFTP_LOG_ERR("Memory allocation failure"); return; } - // printf("[TQFTP] new reader added\n"); + TQFTP_LOG_DEBUG("new reader added"); list_add(&readers, &client->node); if (do_oack) { tftp_send_oack(client->sock, &blksize, - tsize ? (size_t*)&tsize : NULL, + tsize ? &tsize : NULL, wsize ? &wsize : NULL, &client->timeoutms, rsize ? &rsize : NULL, @@ -367,11 +369,11 @@ static void handle_wrq(const char *buf, size_t len, struct sockaddr_qrtr *sq) if (strcasecmp(mode, "octet")) { /* XXX: error */ - printf("[TQFTP] not octet, reject\n"); + TQFTP_LOG_ERR("not octet, reject"); return; } - printf("[TQFTP] WRQ: %s (%s)\n", filename, mode); + TQFTP_LOG_DEBUG("WRQ: %s (%s)", filename, mode); if (p < buf + len) { do_oack = true; @@ -382,21 +384,21 @@ static void handle_wrq(const char *buf, size_t len, struct sockaddr_qrtr *sq) fd = translate_open(filename, O_WRONLY | O_CREAT); if (fd < 0) { /* XXX: error */ - printf("[TQFTP] unable to open %s (%d), reject\n", filename, errno); + TQFTP_LOG_ERR("unable to open %s (%d), reject", filename, errno); return; } sock = qrtr_open(0); if (sock < 0) { /* XXX: error */ - printf("[TQFTP] unable to create new qrtr socket, reject\n"); + TQFTP_LOG_ERR("unable to create new qrtr socket, reject"); return; } ret = connect(sock, (struct sockaddr *)sq, sizeof(*sq)); if (ret < 0) { /* XXX: error */ - printf("[TQFTP] unable to connect new qrtr socket to remote\n"); + TQFTP_LOG_ERR("unable to connect new qrtr socket to remote"); return; } @@ -414,23 +416,23 @@ static void handle_wrq(const char *buf, size_t len, struct sockaddr_qrtr *sq) client->blk_buf = calloc(1, blksize + 4); if (!client->blk_buf) { - printf("[TQFTP] Memory allocation failure\n"); + TQFTP_LOG_ERR("Memory allocation failure"); return; } client->rw_buf = calloc(1, client->rw_buf_size); if (!client->rw_buf) { - printf("[TQFTP] Memory allocation failure\n"); + TQFTP_LOG_ERR("Memory allocation failure"); return; } - // printf("[TQFTP] new writer added\n"); + TQFTP_LOG_DEBUG("new writer added"); list_add(&writers, &client->node); if (do_oack) { tftp_send_oack(client->sock, &blksize, - tsize ? (size_t*)&tsize : NULL, + tsize ? &tsize : NULL, wsize ? &wsize : NULL, &client->timeoutms, rsize ? &rsize : NULL, @@ -457,14 +459,14 @@ static int handle_reader(struct tftp_client *client) if (len < 0) { ret = -errno; if (ret != -ENETRESET) - fprintf(stderr, "[TQFTP] recvfrom failed: %d\n", ret); + TQFTP_LOG_ERROR("recvfrom failed: %d", ret); return -1; } /* Drop unsolicited messages */ if (sq.sq_node != client->sq.sq_node || sq.sq_port != client->sq.sq_port) { - printf("[TQFTP] Discarding spoofed message\n"); + TQFTP_LOG_ERR("Discarding spoofed message"); return -1; } @@ -474,12 +476,12 @@ static int handle_reader(struct tftp_client *client) int err = buf[2] << 8 | buf[3]; /* "End of Transfer" is not an error, used with stat(2)-like calls */ if (err == ERROR_END_OF_TRANSFER) - printf("[TQFTP] Remote returned END OF TRANSFER: %d - %s\n", err, buf + 4); + TQFTP_LOG_DEBUG("Remote returned END OF TRANSFER: %d - %s", err, buf + 4); else - printf("[TQFTP] Remote returned an error: %d - %s\n", err, buf + 4); + TQFTP_LOG_ERR("Remote returned an error: %d - %s", err, buf + 4); return -1; } else if (opcode != OP_ACK) { - printf("[TQFTP] Expected ACK, got %d\n", opcode); + TQFTP_LOG_ERR("Expected ACK, got %d", opcode); return -1; } @@ -500,7 +502,7 @@ static int handle_reader(struct tftp_client *client) n = tftp_send_data(client, block + 1, offset, response_size); if (n < 0) { - printf("[TQFTP] Sent block %d failed: %zd\n", block + 1, n); + TQFTP_LOG_ERR("Sent block %d failed: %zd", block + 1, n); break; } // printf("[TQFTP] Sent block %d of %zd\n", block + 1, n); @@ -530,7 +532,7 @@ static int handle_writer(struct tftp_client *client) if (len < 0) { ret = -errno; if (ret != -ENETRESET) - fprintf(stderr, "[TQFTP] recvfrom failed: %d\n", ret); + TQFTP_LOG_ERROR("recvfrom failed: %d", ret); return -1; } @@ -542,7 +544,7 @@ static int handle_writer(struct tftp_client *client) opcode = buf[0] << 8 | buf[1]; block = buf[2] << 8 | buf[3]; if (opcode != OP_DATA) { - printf("[TQFTP] Expected DATA opcode, got %d\n", opcode); + TQFTP_LOG_ERR("Expected DATA opcode, got %d", opcode); tftp_send_error(client->sock, 4, "Expected DATA opcode"); return -1; } @@ -554,7 +556,7 @@ static int handle_writer(struct tftp_client *client) if (block != client->blk_expected) { uint16_t blk_expected = client->blk_expected; - printf("[TQFTP] Block number out of sequence: %d (expected %d)\n", + TQFTP_LOG_ERR("Block number out of sequence: %d (expected %d)", block, blk_expected); tftp_send_error(client->sock, 4, "Block number out of sequence"); @@ -581,7 +583,7 @@ static int handle_writer(struct tftp_client *client) ret = write(client->fd, client->rw_buf, client->blk_offset); if (ret < 0) { /* XXX: report error */ - printf("[TQFTP] failed to write data\n"); + TQFTP_LOG_ERR("failed to write data"); return -1; } @@ -617,18 +619,41 @@ int main(int argc, char **argv) int ret; int fd; + /* Initialize configuration with defaults */ + tqftp_config_init_defaults(&tqftp_config); + + /* Parse command line arguments */ + ret = tqftp_config_parse_args(argc, argv, &tqftp_config); + if (ret == 1) { + /* Help was shown */ + return 0; + } else if (ret < 0) { + /* Error in arguments */ + return 1; + } + + /* Initialize logging */ + tqftp_log_init_with_config(&tqftp_config.log_config); + + TQFTP_LOG_INFO("TQFTP server starting"); + TQFTP_LOG_DEBUG("Configuration: readonly_path=%s, readwrite_path=%s, firmware_base=%s, temp_dir=%s", + tqftp_config.readonly_path, tqftp_config.readwrite_path, + tqftp_config.firmware_base, tqftp_config.temp_dir); + fd = qrtr_open(0); if (fd < 0) { - fprintf(stderr, "failed to open qrtr socket\n"); + TQFTP_LOG_ERR("failed to open qrtr socket"); exit(1); } ret = qrtr_publish(fd, 4096, 1, 0); if (ret < 0) { - fprintf(stderr, "failed to publish service registry service\n"); + TQFTP_LOG_ERR("failed to publish service registry service"); exit(1); } + TQFTP_LOG_INFO("TQFTP server ready, listening for connections"); + for (;;) { FD_ZERO(&rfds); FD_SET(fd, &rfds); @@ -649,7 +674,7 @@ int main(int argc, char **argv) if (errno == EINTR) { continue; } else { - fprintf(stderr, "select failed\n"); + TQFTP_LOG_ERROR("select failed"); break; } } @@ -676,7 +701,7 @@ int main(int argc, char **argv) if (len < 0) { ret = -errno; if (ret != -ENETRESET) - fprintf(stderr, "[TQFTP] recvfrom failed: %d\n", ret); + TQFTP_LOG_ERROR("recvfrom failed: %d", ret); return ret; } @@ -684,20 +709,20 @@ int main(int argc, char **argv) if (sq.sq_port == QRTR_PORT_CTRL) { ret = qrtr_decode(&pkt, buf, len, &sq); if (ret < 0) { - fprintf(stderr, "[TQFTP] unable to decode qrtr packet\n"); + TQFTP_LOG_ERROR("unable to decode qrtr packet"); return ret; } switch (pkt.type) { case QRTR_TYPE_BYE: - // fprintf(stderr, "[TQFTP] got bye\n"); + TQFTP_LOG_DEBUG("Got bye on QRTE_PORT_CTRL port"); list_for_each_entry_safe(client, next, &writers, node) { if (client->sq.sq_node == sq.sq_node) client_close_and_free(client); } break; case QRTR_TYPE_DEL_CLIENT: - // fprintf(stderr, "[TQFTP] got del_client\n"); + TQFTP_LOG_DEBUG("Got DEL_CLIENT on QRTE_PORT_CTRL port"); list_for_each_entry_safe(client, next, &writers, node) { if (!memcmp(&client->sq, &sq, sizeof(sq))) client_close_and_free(client); @@ -711,18 +736,19 @@ int main(int argc, char **argv) opcode = buf[0] << 8 | buf[1]; switch (opcode) { case OP_RRQ: + TQFTP_LOG_DEBUG("Got OP_WRQ"); handle_rrq(buf, len, &sq); break; case OP_WRQ: - // printf("[TQFTP] write\n"); + TQFTP_LOG_DEBUG("Got OP_WRQ"); handle_wrq(buf, len, &sq); break; case OP_ERROR: buf[len] = '\0'; - printf("[TQFTP] received error: %d - %s\n", buf[2] << 8 | buf[3], buf + 4); + TQFTP_LOG_ERR("received error: %d - %s", buf[2] << 8 | buf[3], buf + 4); break; default: - printf("[TQFTP] unhandled op %d\n", opcode); + TQFTP_LOG_ERR("unhandled op %d", opcode); break; } } diff --git a/tqftpserv.conf.example b/tqftpserv.conf.example new file mode 100644 index 0000000..c979ae9 --- /dev/null +++ b/tqftpserv.conf.example @@ -0,0 +1,63 @@ +# TQFTP Server Configuration File +# This file demonstrates all available configuration options + +# Path Configuration +# These paths control where the server looks for files and stores temporary data + +# Readonly path prefix - where readonly firmware files are accessed +readonly_path=/readonly/firmware/image/ + +# Readwrite path prefix - where temporary/writable files are stored +readwrite_path=/readwrite/ + +# Firmware base directory - where firmware files are located +firmware_base=/lib/firmware/ + +# Updates directory name - subdirectory within firmware_base for updates +updates_dir=updates/ + +# Temporary directory - where the server stores temporary files +temp_dir=/tmp/tqftpserv + +# Logging Configuration +# Controls how and where log messages are output + +# Minimum log level to output +# Options: emerg, alert, crit, err, warn, notice, info, debug +# Default: info +log_level=info + +# Syslog facility to use +# Options: daemon, user, mail, news, uucp, authpriv, ftp, cron, syslog, local0-7 +# Default: daemon +log_facility=daemon + +# Whether to log to console instead of syslog +# Options: yes, no, true, false, 1, 0 +# Default: no +log_console=no + +# Example configurations for different environments: + +# Development configuration (uncomment to use): +# log_level=debug +# log_console=yes +# temp_dir=/tmp/tqftpserv-dev + +# Production configuration (uncomment to use): +# log_level=warn +# log_facility=local0 +# temp_dir=/var/tmp/tqftpserv + +# Android configuration (uncomment to use): +# firmware_base=/vendor/firmware/ +# temp_dir=/data/vendor/tmp/tqftpserv +# log_level=info +# log_console=no + +# Custom embedded system configuration (uncomment to use): +# readonly_path=/mnt/firmware/readonly/ +# readwrite_path=/mnt/firmware/readwrite/ +# firmware_base=/mnt/firmware/base/ +# temp_dir=/mnt/tmp/tqftpserv +# log_level=notice diff --git a/translate.c b/translate.c index 7f7c2b9..1a326d9 100644 --- a/translate.c +++ b/translate.c @@ -2,6 +2,10 @@ /* * Copyright (c) 2019, Linaro Ltd. */ + +/* For asprintf */ +#define _GNU_SOURCE + #include #include #include @@ -17,18 +21,8 @@ #include "translate.h" #include "zstd-decompress.h" - -#define READONLY_PATH "/readonly/firmware/image/" -#define READWRITE_PATH "/readwrite/" - -#ifndef ANDROID -#define FIRMWARE_BASE "/lib/firmware/" -#define TQFTPSERV_TMP "/tmp/tqftpserv" -#define UPDATES_DIR "updates/" -#else -#define FIRMWARE_BASE "/vendor/firmware/" -#define TQFTPSERV_TMP "/data/vendor/tmp/tqftpserv" -#endif +#include "logging.h" +#include "config.h" static int open_maybe_compressed(const char *path); @@ -130,19 +124,19 @@ static int translate_readonly(const char *file) } /* now try with base path */ - if (strlen(FIRMWARE_BASE) + strlen(UPDATES_DIR) + strlen(firmware_value) + 1 + + if (strlen(tqftp_config.firmware_base) + strlen(tqftp_config.updates_dir) + strlen(firmware_value) + 1 + strlen(file) + 1 > sizeof(path)) continue; - strcpy(path, FIRMWARE_BASE); - strcat(path, UPDATES_DIR); + strcpy(path, tqftp_config.firmware_base); + strcat(path, tqftp_config.updates_dir); strcat(path, firmware_path); strcat(path, "/"); strcat(path, file); fd = open_maybe_compressed(path); if (fd < 0) { - strcpy(path, FIRMWARE_BASE); + strcpy(path, tqftp_config.firmware_base); strcat(path, firmware_path); strcat(path, "/"); strcat(path, file); @@ -153,7 +147,7 @@ static int translate_readonly(const char *file) break; if (errno != ENOENT) - warn("failed to open %s", path); + TQFTP_LOG_WARN("failed to open %s: %s", path, strerror(errno)); } free(firmware_value_copy); @@ -175,22 +169,24 @@ static int translate_readwrite(const char *file, int flags) int ret; int fd; - ret = mkdir(TQFTPSERV_TMP, 0700); + ret = mkdir(tqftp_config.temp_dir, 0700); if (ret < 0 && errno != EEXIST) { - warn("failed to create temporary tqftpserv directory"); + TQFTP_LOG_WARN("failed to create temporary tqftpserv directory %s: %s", + tqftp_config.temp_dir, strerror(errno)); return -1; } - base = open(TQFTPSERV_TMP, O_RDONLY | O_DIRECTORY); + base = open(tqftp_config.temp_dir, O_RDONLY | O_DIRECTORY); if (base < 0) { - warn("failed top open temporary tqftpserv directory"); + TQFTP_LOG_WARN("failed to open temporary tqftpserv directory %s: %s", + tqftp_config.temp_dir, strerror(errno)); return -1; } fd = openat(base, file, flags, 0600); close(base); if (fd < 0) - warn("failed to open %s", file); + TQFTP_LOG_WARN("failed to open %s: %s", file, strerror(errno)); return fd; } @@ -205,12 +201,12 @@ static int translate_readwrite(const char *file, int flags) */ int translate_open(const char *path, int flags) { - if (!strncmp(path, READONLY_PATH, strlen(READONLY_PATH))) - return translate_readonly(path + strlen(READONLY_PATH)); - else if (!strncmp(path, READWRITE_PATH, strlen(READWRITE_PATH))) - return translate_readwrite(path + strlen(READWRITE_PATH), flags); + if (!strncmp(path, tqftp_config.readonly_path, strlen(tqftp_config.readonly_path))) + return translate_readonly(path + strlen(tqftp_config.readonly_path)); + else if (!strncmp(path, tqftp_config.readwrite_path, strlen(tqftp_config.readwrite_path))) + return translate_readwrite(path + strlen(tqftp_config.readwrite_path), flags); - fprintf(stderr, "invalid path %s, rejecting\n", path); + TQFTP_LOG_ERR("invalid path %s, rejecting", path); errno = ENOENT; return -1; } @@ -232,7 +228,8 @@ static int open_maybe_compressed(const char *path) if (access(path, F_OK) == 0) return open(path, O_RDONLY); - asprintf(&path_with_zstd_extension, "%s%s", path, ZSTD_EXTENSION); + if (asprintf(&path_with_zstd_extension, "%s%s", path, ZSTD_EXTENSION) == -1) + return -1; if (access(path_with_zstd_extension, F_OK) == 0) fd = zstd_decompress_file(path_with_zstd_extension); diff --git a/zstd-decompress.c b/zstd-decompress.c index 5166fad..bdd539e 100644 --- a/zstd-decompress.c +++ b/zstd-decompress.c @@ -30,7 +30,7 @@ int zstd_decompress_file(const char *filename) /* Figure out the size of the file. */ struct stat file_stat; if (stat(filename, &file_stat) == -1) { - fprintf(stderr, "stat %s failed (%s)\n", filename, strerror(errno)); + TQFTP_LOG_ERRNO("stat %s failed", filename); return -1; } @@ -38,13 +38,13 @@ int zstd_decompress_file(const char *filename) const int input_file_fd = open(filename, O_RDONLY); if (input_file_fd == -1) { - perror("open failed"); + TQFTP_LOG_PERROR("open failed"); return -1; } void* const compressed_buffer = mmap(NULL, file_size, PROT_READ, MAP_POPULATE | MAP_PRIVATE, input_file_fd, 0); if (compressed_buffer == MAP_FAILED) { - perror("mmap failed"); + TQFTP_LOG_PERROR("mmap failed"); close(input_file_fd); return -1; } @@ -52,26 +52,26 @@ int zstd_decompress_file(const char *filename) const unsigned long long decompressed_size = ZSTD_getFrameContentSize(compressed_buffer, file_size); if (decompressed_size == ZSTD_CONTENTSIZE_UNKNOWN) { - fprintf(stderr, "Content size could not be determined for %s\n", filename); + TQFTP_LOG_ERR("Content size could not be determined for %s", filename); munmap(compressed_buffer, file_size); return -1; } if (decompressed_size == ZSTD_CONTENTSIZE_ERROR) { - fprintf(stderr, "Error getting content size for %s\n", filename); + TQFTP_LOG_ERR("Error getting content size for %s", filename); munmap(compressed_buffer, file_size); return -1; } void* const decompressed_buffer = malloc((size_t)decompressed_size); if (decompressed_buffer == NULL) { - perror("malloc failed"); + TQFTP_LOG_PERROR("malloc failed"); munmap(compressed_buffer, file_size); return -1; } const size_t return_size = ZSTD_decompress(decompressed_buffer, decompressed_size, compressed_buffer, file_size); if (ZSTD_isError(return_size)) { - fprintf(stderr, "ZSTD_decompress failed: %s\n", ZSTD_getErrorName(return_size)); + TQFTP_LOG_ERR("ZSTD_decompress failed: %s", ZSTD_getErrorName(return_size)); free(decompressed_buffer); munmap(compressed_buffer, file_size); return -1; @@ -79,14 +79,14 @@ int zstd_decompress_file(const char *filename) const int output_file_fd = memfd_create(filename, 0); if (output_file_fd == -1) { - perror("memfd_create failed"); + TQFTP_LOG_PERROR("memfd_create failed"); free(decompressed_buffer); munmap(compressed_buffer, file_size); return -1; } - if (write(output_file_fd, decompressed_buffer, decompressed_size) != decompressed_size) { - perror("write failed"); + if (write(output_file_fd, decompressed_buffer, decompressed_size) != (ssize_t)decompressed_size) { + TQFTP_LOG_PERROR("write failed"); close(output_file_fd); free(decompressed_buffer); munmap(compressed_buffer, file_size); diff --git a/zstd-decompress.h b/zstd-decompress.h index 2747ed4..d657bb6 100644 --- a/zstd-decompress.h +++ b/zstd-decompress.h @@ -7,13 +7,14 @@ #define __ZSTD_DECOMPRESS_H__ #include +#include "logging.h" #ifdef HAVE_ZSTD int zstd_decompress_file(const char *filename); #else static int zstd_decompress_file(const char *filename) { - fprintf(stderr, "Built without ZSTD support\n"); + TQFTP_LOG_ERR("Built without ZSTD support: %s", filename); return -1; } #endif