diff options
author | Joseph Hunkeler <jhunkeler@gmail.com> | 2019-12-06 12:27:23 -0500 |
---|---|---|
committer | Joseph Hunkeler <jhunkeler@gmail.com> | 2019-12-06 12:27:23 -0500 |
commit | 64551ae0434176b19d4abf908eee08e75890dfb6 (patch) | |
tree | 6e7c9d34a9575fe09fc0145aa848ce830cd9df04 | |
parent | ae93deb9b1f4c83addd90e49af35543ce0c23a38 (diff) | |
download | spmc-64551ae0434176b19d4abf908eee08e75890dfb6.tar.gz |
Cummulative work
-rw-r--r-- | CMakeLists.txt | 7 | ||||
-rw-r--r-- | compat.c | 16 | ||||
-rw-r--r-- | config.c | 186 | ||||
-rw-r--r-- | config.h.in | 6 | ||||
-rw-r--r-- | doxygen.conf | 379 | ||||
-rw-r--r-- | spm.c | 343 | ||||
-rw-r--r-- | spm.h | 68 |
7 files changed, 925 insertions, 80 deletions
diff --git a/CMakeLists.txt b/CMakeLists.txt index efefe50..a130cda 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,6 +1,11 @@ cmake_minimum_required(VERSION 3.15) project(spm C) +include(CheckSymbolExists) set(CMAKE_C_STANDARD 99) +include_directories(${CMAKE_CURRENT_BINARY_DIR}) +check_symbol_exists(strsep string.h HAVE_STRSEP) +configure_file(config.h.in config.h) -add_executable(spm spm.c)
\ No newline at end of file +add_executable(spm spm.c config.c spm.h config.h.in compat.c) +target_link_libraries(spm rt) diff --git a/compat.c b/compat.c new file mode 100644 index 0000000..082a602 --- /dev/null +++ b/compat.c @@ -0,0 +1,16 @@ +#include <string.h> +#include "config.h" + +#ifndef HAVE_STRSEP +// credit: Dan Cross via https://unixpapa.com/incnote/string.html +char *strsep(char **sp, char *sep) +{ + char *p, *s; + if (sp == NULL || *sp == NULL || **sp == '\0') return(NULL); + s = *sp; + p = s + strcspn(s, sep); + if (*p != '\0') *p++ = '\0'; + *sp = p; + return(s); +} +#endif
\ No newline at end of file diff --git a/config.c b/config.c new file mode 100644 index 0000000..c2d905b --- /dev/null +++ b/config.c @@ -0,0 +1,186 @@ +#include <ctype.h> +#include <stdio.h> +#include <string.h> +#include <stdlib.h> +#include "spm.h" + + +/// Remove leading whitespace from a string +/// \param sptr pointer to string +/// \return pointer to first non-whitespace character in string +char *lstrip(char *sptr) { + while (isblank(*sptr)) { + sptr++; + } + return sptr; +} + +/// Remove trailing whitespace from a string +/// \param sptr pointer to string +/// \return truncated string +char *strip(char *sptr) { + char *tmp = sptr + strlen(sptr) - 1; + while (isblank(*tmp) || *tmp == '\n') { + *tmp = '\0'; + tmp--; + } + return sptr; +} + +/// Determine if a string is empty +/// \param sptr pointer to string +/// \return 0=not empty, 1=empty +int isempty(char *sptr) { + char *tmp = sptr; + while (*tmp) { + if (!isblank(*tmp)) { + return 0; + } + tmp++; + } + return 1; +} + +/// Determine if a string is encapsulated by quotes +/// \param sptr pointer to string +/// \return 0=not quoted, 1=quoted +int isquoted(char *sptr) { + const char *quotes = "'\""; + char *quote_open = strpbrk(sptr, quotes); + if (!quote_open) { + return 0; + } + char *quote_close = strpbrk(quote_open + 1, quotes); + if (!quote_close) { + return 0; + } + return 1; +} + +Config **config_read(const char *filename) { + const char sep = '='; + char line[CONFIG_BUFFER_SIZE]; + FILE *fp = fopen(filename, "r"); + if (!fp) { + // errno will be set, so die, and let the caller handle it + return NULL; + } + int record_initial = 2; + Config **config = (Config **) calloc(record_initial, sizeof(Config *)); + int record = 0; + + + while (fgets(line, sizeof(line), fp) != NULL) { + char *lptr = line; + // Remove leading space and newlines + lptr = lstrip(lptr); + // Remove trailing space and newlines + lptr = strip(lptr); + + // Skip empty lines + if (isempty(lptr)) { + continue; + } + // Skip comment-only lines + if (*lptr == '#' || *lptr == ';') { + continue; + } + + // Get a pointer to the key pair separator + char *sep_pos = strchr(lptr, sep); + if (!sep_pos) { + printf("invalid entry on line %d: missing '%c': '%s'\n", record, sep, lptr); + continue; + } + + // Calculate key and value lengths dynamically + size_t key_length = strcspn(lptr, &sep); + size_t value_length = strlen(sep_pos); + + // Allocate a Config record + config[record] = (Config *)calloc(1, sizeof(Config)); + config[record]->key = (char *)calloc(key_length + 1, sizeof(char)); + config[record]->value = (char *)calloc(value_length + 1, sizeof(char)); + + // Shortcut our array at this point. Things get pretty ugly otherwise. + char *key = config[record]->key; + char *value = config[record]->value; + + // Populate the key and remove any trailing space + while (lptr != sep_pos) { + *key++ = *lptr++; + } + key = strip(key); + + // We're at the separator now, so skip over it + lptr++; + // and remove any leading space + lptr = lstrip(lptr); + + // Determine whether the string is surrounded by quotes, if so, get rid of them + if (isquoted(lptr)) { + // Move pointer beyond quote + lptr = strpbrk(lptr, "'\"") + 1; + // Terminate on closing quote + char *tmp = strpbrk(lptr, "'\""); + *tmp = '\0'; + } + + // Populate the value, and ignore any inline comments + while (*lptr) { + if (*lptr == '#' || *lptr == ';') { + // strip trailing whitespace where the comment is and stop processing + value = strip(value); + break; + } + *value++ = *lptr++; + } + + // increment record count + record++; + // Expand config by another record + config = (Config **)reallocarray(config, record + record_initial + 1, sizeof(Config *)); + } + return config; +} + +void config_free(Config **config) { + for (int i = 0; config[i] != NULL; i++) { + free(config[i]); + } + free(config); +} + +/// If the configuration contains `key` return a pointer to that record +/// \param config pointer to array of config records +/// \param key search for key in config records +/// \return success=pointer to record, failure=NULL +Config *config_get(Config **config, const char *key) { + for (int i = 0; config[i] != NULL; i++) { + if (!strcmp(config[i]->key, key)) { + return config[i]; + } + } + return NULL; +} + +void config_test(void) { + Config **config = config_read("program.conf"); + printf("Data Parsed:\n"); + for (int i = 0; config[i] != NULL; i++) { + printf("key: '%s', value: '%s'\n", config[i]->key, config[i]->value); + } + + printf("Testing config_get():\n"); + Config *cptr = NULL; + if ((cptr = config_get(config, "integer_value"))) { + printf("%s = %d\n", cptr->key, atoi(cptr->value)); + } + if ((cptr = config_get(config, "float_value"))) { + printf("%s = %.3f\n", cptr->key, atof(cptr->value)); + } + if ((cptr = config_get(config, "string_value"))) { + printf("%s = %s\n", cptr->key, cptr->value); + } + config_free(config); +} diff --git a/config.h.in b/config.h.in new file mode 100644 index 0000000..cedd8a4 --- /dev/null +++ b/config.h.in @@ -0,0 +1,6 @@ +#ifndef SPM_CONFIG_IN_H +#define SPM_CONFIG_IN_H + +#cmakedefine HAVE_STRSEP 1 + +#endif //SPM_CONFIG_IN_H diff --git a/doxygen.conf b/doxygen.conf new file mode 100644 index 0000000..e535d50 --- /dev/null +++ b/doxygen.conf @@ -0,0 +1,379 @@ +# Doxyfile 1.8.16 + +#--------------------------------------------------------------------------- +# Project related configuration options +#--------------------------------------------------------------------------- +DOXYFILE_ENCODING = UTF-8 +PROJECT_NAME = "Simple Package Manager" +PROJECT_NUMBER = ${VERSION} +PROJECT_BRIEF = "a.k.a. SPM" +PROJECT_LOGO = +OUTPUT_DIRECTORY = /home/jhunk/CLionProjects/spm/docs +CREATE_SUBDIRS = NO +ALLOW_UNICODE_NAMES = NO +OUTPUT_LANGUAGE = English +OUTPUT_TEXT_DIRECTION = None +BRIEF_MEMBER_DESC = YES +REPEAT_BRIEF = YES +ABBREVIATE_BRIEF = "The $name class" \ + "The $name widget" \ + "The $name file" \ + is \ + provides \ + specifies \ + contains \ + represents \ + a \ + an \ + the +ALWAYS_DETAILED_SEC = NO +INLINE_INHERITED_MEMB = NO +FULL_PATH_NAMES = YES +STRIP_FROM_PATH = +STRIP_FROM_INC_PATH = +SHORT_NAMES = NO +JAVADOC_AUTOBRIEF = NO +JAVADOC_BANNER = NO +QT_AUTOBRIEF = NO +MULTILINE_CPP_IS_BRIEF = NO +INHERIT_DOCS = YES +SEPARATE_MEMBER_PAGES = NO +TAB_SIZE = 4 +ALIASES = +TCL_SUBST = +OPTIMIZE_OUTPUT_FOR_C = YES +OPTIMIZE_OUTPUT_JAVA = NO +OPTIMIZE_FOR_FORTRAN = NO +OPTIMIZE_OUTPUT_VHDL = NO +OPTIMIZE_OUTPUT_SLICE = NO +EXTENSION_MAPPING = +MARKDOWN_SUPPORT = YES +TOC_INCLUDE_HEADINGS = 5 +AUTOLINK_SUPPORT = YES +BUILTIN_STL_SUPPORT = NO +CPP_CLI_SUPPORT = NO +SIP_SUPPORT = NO +IDL_PROPERTY_SUPPORT = YES +DISTRIBUTE_GROUP_DOC = NO +GROUP_NESTED_COMPOUNDS = NO +SUBGROUPING = YES +INLINE_GROUPED_CLASSES = NO +INLINE_SIMPLE_STRUCTS = NO +TYPEDEF_HIDES_STRUCT = NO +LOOKUP_CACHE_SIZE = 0 +#--------------------------------------------------------------------------- +# Build related configuration options +#--------------------------------------------------------------------------- +EXTRACT_ALL = NO +EXTRACT_PRIVATE = NO +EXTRACT_PRIV_VIRTUAL = NO +EXTRACT_PACKAGE = NO +EXTRACT_STATIC = NO +EXTRACT_LOCAL_CLASSES = YES +EXTRACT_LOCAL_METHODS = NO +EXTRACT_ANON_NSPACES = NO +HIDE_UNDOC_MEMBERS = NO +HIDE_UNDOC_CLASSES = NO +HIDE_FRIEND_COMPOUNDS = NO +HIDE_IN_BODY_DOCS = NO +INTERNAL_DOCS = NO +CASE_SENSE_NAMES = NO +HIDE_SCOPE_NAMES = YES +HIDE_COMPOUND_REFERENCE= NO +SHOW_INCLUDE_FILES = YES +SHOW_GROUPED_MEMB_INC = NO +FORCE_LOCAL_INCLUDES = NO +INLINE_INFO = YES +SORT_MEMBER_DOCS = YES +SORT_BRIEF_DOCS = NO +SORT_MEMBERS_CTORS_1ST = NO +SORT_GROUP_NAMES = NO +SORT_BY_SCOPE_NAME = NO +STRICT_PROTO_MATCHING = NO +GENERATE_TODOLIST = YES +GENERATE_TESTLIST = YES +GENERATE_BUGLIST = YES +GENERATE_DEPRECATEDLIST= YES +ENABLED_SECTIONS = +MAX_INITIALIZER_LINES = 30 +SHOW_USED_FILES = YES +SHOW_FILES = YES +SHOW_NAMESPACES = YES +FILE_VERSION_FILTER = +LAYOUT_FILE = +CITE_BIB_FILES = +#--------------------------------------------------------------------------- +# Configuration options related to warning and progress messages +#--------------------------------------------------------------------------- +QUIET = NO +WARNINGS = YES +WARN_IF_UNDOCUMENTED = YES +WARN_IF_DOC_ERROR = YES +WARN_NO_PARAMDOC = NO +WARN_AS_ERROR = NO +WARN_FORMAT = "$file:$line: $text" +WARN_LOGFILE = +#--------------------------------------------------------------------------- +# Configuration options related to the input files +#--------------------------------------------------------------------------- +INPUT = . +INPUT_ENCODING = UTF-8 +FILE_PATTERNS = *.c \ + *.cc \ + *.cxx \ + *.cpp \ + *.c++ \ + *.java \ + *.ii \ + *.ixx \ + *.ipp \ + *.i++ \ + *.inl \ + *.idl \ + *.ddl \ + *.odl \ + *.h \ + *.hh \ + *.hxx \ + *.hpp \ + *.h++ \ + *.cs \ + *.d \ + *.php \ + *.php4 \ + *.php5 \ + *.phtml \ + *.inc \ + *.m \ + *.markdown \ + *.md \ + *.mm \ + *.dox \ + *.py \ + *.pyw \ + *.f90 \ + *.f95 \ + *.f03 \ + *.f08 \ + *.f \ + *.for \ + *.tcl \ + *.vhd \ + *.vhdl \ + *.ucf \ + *.qsf \ + *.ice +RECURSIVE = NO +EXCLUDE = +EXCLUDE_SYMLINKS = NO +EXCLUDE_PATTERNS = +EXCLUDE_SYMBOLS = +EXAMPLE_PATH = +EXAMPLE_PATTERNS = * +EXAMPLE_RECURSIVE = NO +IMAGE_PATH = +INPUT_FILTER = +FILTER_PATTERNS = +FILTER_SOURCE_FILES = NO +FILTER_SOURCE_PATTERNS = +USE_MDFILE_AS_MAINPAGE = +#--------------------------------------------------------------------------- +# Configuration options related to source browsing +#--------------------------------------------------------------------------- +SOURCE_BROWSER = NO +INLINE_SOURCES = NO +STRIP_CODE_COMMENTS = YES +REFERENCED_BY_RELATION = NO +REFERENCES_RELATION = NO +REFERENCES_LINK_SOURCE = YES +SOURCE_TOOLTIPS = YES +USE_HTAGS = NO +VERBATIM_HEADERS = YES +#--------------------------------------------------------------------------- +# Configuration options related to the alphabetical class index +#--------------------------------------------------------------------------- +ALPHABETICAL_INDEX = YES +COLS_IN_ALPHA_INDEX = 5 +IGNORE_PREFIX = +#--------------------------------------------------------------------------- +# Configuration options related to the HTML output +#--------------------------------------------------------------------------- +GENERATE_HTML = NO +HTML_OUTPUT = html +HTML_FILE_EXTENSION = .html +HTML_HEADER = +HTML_FOOTER = +HTML_STYLESHEET = +HTML_EXTRA_STYLESHEET = +HTML_EXTRA_FILES = +HTML_COLORSTYLE_HUE = 220 +HTML_COLORSTYLE_SAT = 100 +HTML_COLORSTYLE_GAMMA = 80 +HTML_TIMESTAMP = NO +HTML_DYNAMIC_MENUS = YES +HTML_DYNAMIC_SECTIONS = NO +HTML_INDEX_NUM_ENTRIES = 100 +GENERATE_DOCSET = NO +DOCSET_FEEDNAME = "Doxygen generated docs" +DOCSET_BUNDLE_ID = org.doxygen.Project +DOCSET_PUBLISHER_ID = org.doxygen.Publisher +DOCSET_PUBLISHER_NAME = Publisher +GENERATE_HTMLHELP = NO +CHM_FILE = +HHC_LOCATION = +GENERATE_CHI = NO +CHM_INDEX_ENCODING = +BINARY_TOC = NO +TOC_EXPAND = NO +GENERATE_QHP = NO +QCH_FILE = +QHP_NAMESPACE = org.doxygen.Project +QHP_VIRTUAL_FOLDER = doc +QHP_CUST_FILTER_NAME = +QHP_CUST_FILTER_ATTRS = +QHP_SECT_FILTER_ATTRS = +QHG_LOCATION = +GENERATE_ECLIPSEHELP = NO +ECLIPSE_DOC_ID = org.doxygen.Project +DISABLE_INDEX = NO +GENERATE_TREEVIEW = NO +ENUM_VALUES_PER_LINE = 4 +TREEVIEW_WIDTH = 250 +EXT_LINKS_IN_WINDOW = NO +FORMULA_FONTSIZE = 10 +FORMULA_TRANSPARENT = YES +USE_MATHJAX = NO +MATHJAX_FORMAT = HTML-CSS +MATHJAX_RELPATH = https://cdnjs.cloudflare.com/ajax/libs/mathjax/2.7.5/ +MATHJAX_EXTENSIONS = +MATHJAX_CODEFILE = +SEARCHENGINE = YES +SERVER_BASED_SEARCH = NO +EXTERNAL_SEARCH = NO +SEARCHENGINE_URL = +SEARCHDATA_FILE = searchdata.xml +EXTERNAL_SEARCH_ID = +EXTRA_SEARCH_MAPPINGS = +#--------------------------------------------------------------------------- +# Configuration options related to the LaTeX output +#--------------------------------------------------------------------------- +GENERATE_LATEX = NO +LATEX_OUTPUT = latex +LATEX_CMD_NAME = +MAKEINDEX_CMD_NAME = makeindex +LATEX_MAKEINDEX_CMD = makeindex +COMPACT_LATEX = NO +PAPER_TYPE = a4 +EXTRA_PACKAGES = +LATEX_HEADER = +LATEX_FOOTER = +LATEX_EXTRA_STYLESHEET = +LATEX_EXTRA_FILES = +PDF_HYPERLINKS = YES +USE_PDFLATEX = YES +LATEX_BATCHMODE = NO +LATEX_HIDE_INDICES = NO +LATEX_SOURCE_CODE = NO +LATEX_BIB_STYLE = plain +LATEX_TIMESTAMP = NO +LATEX_EMOJI_DIRECTORY = +#--------------------------------------------------------------------------- +# Configuration options related to the RTF output +#--------------------------------------------------------------------------- +GENERATE_RTF = NO +RTF_OUTPUT = rtf +COMPACT_RTF = NO +RTF_HYPERLINKS = NO +RTF_STYLESHEET_FILE = +RTF_EXTENSIONS_FILE = +RTF_SOURCE_CODE = NO +#--------------------------------------------------------------------------- +# Configuration options related to the man page output +#--------------------------------------------------------------------------- +GENERATE_MAN = YES +MAN_OUTPUT = man +MAN_EXTENSION = .3 +MAN_SUBDIR = +MAN_LINKS = NO +#--------------------------------------------------------------------------- +# Configuration options related to the XML output +#--------------------------------------------------------------------------- +GENERATE_XML = NO +XML_OUTPUT = xml +XML_PROGRAMLISTING = YES +XML_NS_MEMB_FILE_SCOPE = NO +#--------------------------------------------------------------------------- +# Configuration options related to the DOCBOOK output +#--------------------------------------------------------------------------- +GENERATE_DOCBOOK = NO +DOCBOOK_OUTPUT = docbook +DOCBOOK_PROGRAMLISTING = NO +#--------------------------------------------------------------------------- +# Configuration options for the AutoGen Definitions output +#--------------------------------------------------------------------------- +GENERATE_AUTOGEN_DEF = NO +#--------------------------------------------------------------------------- +# Configuration options related to the Perl module output +#--------------------------------------------------------------------------- +GENERATE_PERLMOD = NO +PERLMOD_LATEX = NO +PERLMOD_PRETTY = YES +PERLMOD_MAKEVAR_PREFIX = +#--------------------------------------------------------------------------- +# Configuration options related to the preprocessor +#--------------------------------------------------------------------------- +ENABLE_PREPROCESSING = YES +MACRO_EXPANSION = NO +EXPAND_ONLY_PREDEF = NO +SEARCH_INCLUDES = YES +INCLUDE_PATH = +INCLUDE_FILE_PATTERNS = +PREDEFINED = +EXPAND_AS_DEFINED = +SKIP_FUNCTION_MACROS = YES +#--------------------------------------------------------------------------- +# Configuration options related to external references +#--------------------------------------------------------------------------- +TAGFILES = +GENERATE_TAGFILE = +ALLEXTERNALS = NO +EXTERNAL_GROUPS = YES +EXTERNAL_PAGES = YES +#--------------------------------------------------------------------------- +# Configuration options related to the dot tool +#--------------------------------------------------------------------------- +CLASS_DIAGRAMS = NO +DIA_PATH = +HIDE_UNDOC_RELATIONS = YES +HAVE_DOT = NO +DOT_NUM_THREADS = 0 +DOT_FONTNAME = Helvetica +DOT_FONTSIZE = 10 +DOT_FONTPATH = +CLASS_GRAPH = YES +COLLABORATION_GRAPH = YES +GROUP_GRAPHS = YES +UML_LOOK = NO +UML_LIMIT_NUM_FIELDS = 10 +TEMPLATE_RELATIONS = NO +INCLUDE_GRAPH = YES +INCLUDED_BY_GRAPH = YES +CALL_GRAPH = NO +CALLER_GRAPH = NO +GRAPHICAL_HIERARCHY = YES +DIRECTORY_GRAPH = YES +DOT_IMAGE_FORMAT = png +INTERACTIVE_SVG = NO +DOT_PATH = +DOTFILE_DIRS = +MSCFILE_DIRS = +DIAFILE_DIRS = +PLANTUML_JAR_PATH = +PLANTUML_CFG_FILE = +PLANTUML_INCLUDE_PATH = +DOT_GRAPH_MAX_NODES = 50 +MAX_DOT_GRAPH_DEPTH = 0 +DOT_TRANSPARENT = NO +DOT_MULTI_TARGETS = NO +GENERATE_LEGEND = YES +DOT_CLEANUP = YES
\ No newline at end of file @@ -5,26 +5,18 @@ #include <ctype.h> #include <errno.h> +#include <glob.h> #include <stdio.h> #include <stdlib.h> #include <limits.h> #include <stdarg.h> #include <string.h> #include <unistd.h> -#include <glob.h> +#include <time.h> +#include "config.h" +#include "spm.h" -#define SYSERROR stderr, "%s:%s:%d: %s\n", __FILE__, __FUNCTION__, __LINE__, strerror(errno) -#define DIRSEP_WIN32 '\\' -#define DIRSEP_UNIX '/' -#if defined(_WIN32) -#define DIRSEP DIRSEP_WIN32 -#define NOT_DIRSEP DIRSEP_UNIX -#else -#define DIRSEP DIRSEP_UNIX -#define NOT_DIRSEP DIRSEP_WIN32 -#endif -#define PKG_DIR "../pkgs" /** * A wrapper for `popen()` that executes non-interactive programs and reports their exit value. @@ -33,33 +25,41 @@ * ~~~{.c} * int fd = 1; // stdout * const char *log_file = "log.txt"; - * char *buf; + * Process *proc_info; * int status; * * // Send stderr to stdout - * status = shell(buf, "foo 2>&1"); + * shell(&proc_info, SHELL_OUTPUT, "foo 2>&1"); * // Send stdout and stderr to /dev/null - * status = shell(buf, "bar &>/dev/null"); + * shell(&proc_info, SHELL_OUTPUT,"bar &>/dev/null"); * // Send stdout from baz to log.txt - * status = shell(buf, "baz %d>%s", fd, log_file); + * shell(&proc_info, SHELL_OUTPUT, "baz %d>%s", fd, log_file); * // Do not record or redirect output from any streams - * status = shell(NULL, "biff"); + * shell(&proc_info, SHELL_DEFAULT, "biff"); * ~~~ * - * @param buf buffer to hold program output (to ignore output, set to NULL) + * @param Process uninitialized `Process` struct will be populated with process data * @param options change behavior of the function * @param fmt shell command to execute (accepts `printf` style formatters) * @param ... variadic arguments (used by `fmt`) - * @return shell exit code */ -#define SHELL_DEFAULT 1 << 0 -#define SHELL_OUTPUT 1 << 1 -int shell(char **buf, u_int8_t option, const char *fmt, ...) { +void shell(Process **proc_info, u_int64_t option, const char *fmt, ...) { va_list args; va_start(args, fmt); - FILE *proc; - int status = -1; + size_t bytes_read = 0; + size_t i = 0; + size_t new_buf_size = 0; + clockid_t clkid = CLOCK_REALTIME; + FILE *proc = NULL; + + (*proc_info) = (Process *)calloc(1, sizeof(Process)); + if (!(*proc_info)) { + fprintf(SYSERROR); + exit(errno); + } + (*proc_info)->returncode = -1; + char *outbuf = (char *)calloc(1, sizeof(char)); if (!outbuf) { fprintf(SYSERROR); @@ -73,57 +73,86 @@ int shell(char **buf, u_int8_t option, const char *fmt, ...) { vsnprintf(cmd, PATH_MAX, fmt, args); + if (option & SHELL_BENCHMARK) { + if (clock_gettime(clkid, &(*proc_info)->start_time) == -1) { + perror("clock_gettime"); + exit(errno); + } + } + proc = popen(cmd, "r"); if (!proc) { - return status; + return; + } + + if (option & SHELL_BENCHMARK) { + if (clock_gettime(clkid, &(*proc_info)->stop_time) == -1) { + perror("clock_gettime"); + exit(errno); + } + (*proc_info)->time_elapsed = ((*proc_info)->stop_time.tv_sec - (*proc_info)->start_time.tv_sec) + + ((*proc_info)->stop_time.tv_nsec - (*proc_info)->start_time.tv_nsec) / 1E9; } - size_t bytes_read = 0; - size_t i = 0; - size_t new_buf_size = 0; if (option & SHELL_OUTPUT) { - *buf = (char *)calloc(BUFSIZ, sizeof(char)); + (*proc_info)->output = (char *)calloc(BUFSIZ, sizeof(char)); - while (!feof(proc)) { - *outbuf = fgetc(proc); + while ((*outbuf = fgetc(proc)) != EOF) { if (i >= BUFSIZ) { new_buf_size = BUFSIZ + (i + bytes_read); - (*buf) = (char *)realloc((*buf), new_buf_size); + (*proc_info)->output = (char *)realloc((*proc_info)->output, new_buf_size); i = 0; } - if (isascii(*outbuf)) { - (*buf)[bytes_read] = *outbuf; + if (*outbuf) { + (*proc_info)->output[bytes_read] = *outbuf; } bytes_read++; i++; } } - status = pclose(proc); + (*proc_info)->returncode = pclose(proc); va_end(args); free(outbuf); free(cmd); - return status; -}; +} -/* +/** + * Free process resources allocated by `shell()` + * @param proc_info `Process` struct + */ +void shell_free(Process *proc_info) { + free(proc_info->output); + free(proc_info); +} + +/** + * Extract a single file from a tar archive into a directory + * + * @param archive path to tar archive + * @param filename known path inside the archive to extract + * @param destination where to extract file to (must exist) + * @return + */ int tar_extract_file(const char *archive, const char* filename, const char *destination) { + Process *proc = NULL; int status = -1; char cmd[PATH_MAX]; char output[3]; + sprintf(cmd, "tar xf %s %s -C %s 2>&1", archive, filename, destination); - FILE *proc = popen(cmd, "r"); + + shell(&proc, SHELL_OUTPUT, cmd); if (!proc) { - return status; - } - while (!feof(proc)) { - fgets(output, 2, proc); - printf("%s", output); + printf("%s\n", proc->output); + fprintf(SYSERROR); + return -1; } - putchar('\n'); - return pclose(proc); + status = proc->returncode; + shell_free(proc); + + return status; }; - */ /** * glob callback function @@ -160,7 +189,7 @@ int num_chars(const char *sptr, int ch) { * @return 0 = success, -1 = failure */ int startswith(const char *sptr, const char *pattern) { - for (int i = 0; i < strlen(pattern); i++) { + for (size_t i = 0; i < strlen(pattern); i++) { if (sptr[i] != pattern[i]) { return -1; } @@ -212,13 +241,40 @@ char *normpath(const char *path) { } /** + * Deletes any characters matching `chars` from `sptr` string + * + * @param sptr string to be modified in-place + * @param chars a string containing characters (e.g. " \n" would delete whitespace and line feeds) + */ +void strchrdel(char *sptr, const char *chars) { + while (*sptr != '\0') { + for (int i = 0; chars[i] != '\0'; i++) { + if (*sptr == chars[i]) { + memmove(sptr, sptr + 1, strlen(sptr)); + } + } + sptr++; + } +} + +int64_t strchroff(char *sptr, int ch) { + char *tmp = sptr; + while (*tmp != '\0') { + if (*tmp == ch) { + return tmp - sptr; + } + tmp++; + } + return -1; +} +/** * This function scans `sptr` from right to left removing any matches to `suffix` * from the string. * * @param sptr string to be modified * @param suffix string to be removed from `sptr` */ -void strip(char *sptr, const char *suffix) { +void substrdel(char *sptr, const char *suffix) { if (!sptr || !suffix) { return; } @@ -237,24 +293,7 @@ void strip(char *sptr, const char *suffix) { // Purge the suffix memset(target, '\0', suffix_len); // Recursive call continues removing suffix until it is gone - strip(sptr, suffix); - } -} - -/** - * Deletes any characters matching `chars` from `sptr` string - * - * @param sptr string to be modified in-place - * @param chars a string containing characters (e.g. " \n" would delete whitespace and line feeds) - */ -void strchrdel(char *sptr, const char *chars) { - while (*sptr != '\0') { - for (int i = 0; chars[i] != '\0'; i++) { - if (*sptr == chars[i]) { - memmove(sptr, sptr + 1, strlen(sptr)); - } - } - sptr++; + strip(sptr); } } @@ -320,33 +359,179 @@ char *find_package(const char *filename) { return find_file(PKG_DIR, filename); } -int main() { +char** split(char *sptr, const char* delim) +{ + int split_alloc = 0; + for (int i = 0; i < strlen(delim); i++) { + split_alloc += num_chars(sptr, delim[i]); + } + char **result = (char **)calloc(split_alloc + 2, sizeof(char *)); + + int i = 0; + //char *token = strsep(&sptr, delim); + char *token = NULL; + while((token = strsep(&sptr, delim)) != NULL) { + result[i] = (char *)calloc(1, sizeof(char) * strlen(token) + 1); + strcpy(result[i], token); + i++; + } + return result; +} + +void split_free(char **ptr) { + for (int i = 0; ptr[i] != NULL; i++) { + free(ptr[i]); + } + free(ptr); +} + +char *substring_between(char *sptr, const char *delims) { + int delim_count = strlen(delims); + if (delim_count != 2) { + return NULL; + } + + char *start = strpbrk(sptr, &delims[0]); + char *end = strpbrk(sptr, &delims[1]); + if (!start || !end) { + return NULL; + } + start++; // ignore leading delimiter + + long int length = end - start; + + char *result = (char *)calloc(1, sizeof(char) * length); + if (!result) { + return NULL; + } + + char *tmp = result; + while (start != end) { + *tmp = *start; + tmp++; + start++; + } + + return result; +} + +/** + * Uses `readelf` to determine whether a RPATH or RUNPATH is present + * + * TODO: Replace with OS-native solution(s) + * + * @param filename path to executable or library + * @return -1=OS error, 0=has rpath, 1=not found + */ +int has_rpath(const char *filename) { + int result = 1; + Process *proc_info = NULL; + char *path = strdup(filename); + const char *preamble = "readelf -d"; + + // sanitize input path + strchrdel(path, "&;|"); + + char sh_cmd[strlen(preamble) + 1 + strlen(path) + 1]; + memset(sh_cmd, '\0', sizeof(sh_cmd)); + + sprintf(sh_cmd, "%s %s", preamble, path); + free(path); + + shell(&proc_info, SHELL_OUTPUT, sh_cmd); + if (!proc_info) { + fprintf(SYSERROR); + return -1; + } + + strip(proc_info->output); + char **lines = split(proc_info->output, "\n"); + for (int i = 0; lines[i] != NULL; i++) { + if (strstr(lines[i], "RPATH") || strstr(lines[i], "RUNPATH")) { + result = 0; + } + } + shell_free(proc_info); + split_free(lines); + return result; +} + +/** + * Parses `readelf` output and returns an RPATH or RUNPATH if one is defined + * + * TODO: Replace with OS-native solution(s) + * + * @param filename path to executable or library + * @return RPATH string, NULL=error (caller is responsible for freeing memory) + */ +char *get_rpath(const char *filename) { + if ((has_rpath(filename)) != 0) { + return NULL; + } + + Process *proc_info = NULL; + char *path = strdup(filename); + const char *preamble = "readelf -d"; + char *rpath = NULL; + + // sanitize input path + strchrdel(path, "&;|"); + + char sh_cmd[strlen(preamble) + 1 + strlen(path) + 1]; + memset(sh_cmd, '\0', sizeof(sh_cmd)); + + sprintf(sh_cmd, "%s %s", preamble, path); + free(path); + + shell(&proc_info, SHELL_OUTPUT, sh_cmd); + if (!proc_info) { + fprintf(SYSERROR); + return NULL; + } + + strip(proc_info->output); + char **lines = split(proc_info->output, "\n"); + for (int i = 0; lines[i] != NULL; i++) { + if (strstr(lines[i], "RPATH") || strstr(lines[i], "RUNPATH")) { + rpath = substring_between(lines[i], "[]"); + break; + } + } + shell_free(proc_info); + split_free(lines); + return rpath; +} + +int main(int argc, char *argv[]) { + // not much to see here yet + // at the moment this will all be random tests, for better or worse + // everything here is subject to change without notice + const char *pkg_name = "python"; char *package = NULL; package = find_package(pkg_name); if (package != NULL) { printf("Package found: %s\n", package); free(package); - } - else { - fprintf(stderr,"Package does not exist: %s\n", pkg_name); + } else { + fprintf(stderr, "Package does not exist: %s\n", pkg_name); } const char *testpath = "x:\\a\\b\\c"; const char *teststring = "this is test!"; + const char *testprog = "/tmp/a.out"; char *normalized = normpath(testpath); - char *buf = NULL; printf("%s becomes %s\n", testpath, normalized); printf("%d\n", startswith(testpath, "x:\\")); printf("%d\n", endswith(teststring, "test!")); - //tar_extract_file("one", "two", testpath); - - int retval = -1; - retval = shell(&buf, SHELL_OUTPUT, "env; env; env; env; env; env; env; env"); - strip(buf, "\n"); - printf("%s\n", buf); - free(normalized); + + if ((has_rpath(testprog)) == 0) { + printf("RPATH found\n"); + char *rpath = get_rpath(testprog); + printf("RPATH is: %s\n", rpath); + free(rpath); + } return 0; }
\ No newline at end of file @@ -0,0 +1,68 @@ +#ifndef SPM_SPM_H +#define SPM_SPM_H + +// spm.c +#define SYSERROR stderr, "%s:%s:%d: %s\n", __FILE__, __FUNCTION__, __LINE__, strerror(errno) +#define DIRSEP_WIN32 '\\' +#define DIRSEP_UNIX '/' +#if defined(_WIN32) +#define DIRSEP DIRSEP_WIN32 +#define NOT_DIRSEP DIRSEP_UNIX +#else +#define DIRSEP DIRSEP_UNIX +#define NOT_DIRSEP DIRSEP_WIN32 +#endif + +#define PKG_DIR "../pkgs" + +#define SHELL_DEFAULT 1L << 0L +#define SHELL_OUTPUT 1L << 1L +#define SHELL_BENCHMARK 1L << 2L +typedef struct { + struct timespec start_time, stop_time; + double time_elapsed; + int returncode; + char *output; +} Process; + +void shell(Process **proc_info, u_int64_t option, const char *fmt, ...); +void shell_free(Process *proc_info); +int tar_extract_file(const char *archive, const char* filename, const char *destination); +int errglob(const char *epath, int eerrno); +int num_chars(const char *sptr, int ch); +int startswith(const char *sptr, const char *pattern); +int endswith(const char *sptr, const char *pattern); +char *normpath(const char *path); +void strchrdel(char *sptr, const char *chars); +int64_t strchroff(char *sptr, int ch); +void substrdel(char *sptr, const char *suffix); +char *find_file(const char *root, const char *filename); +char *find_package(const char *filename); +char** split(char *sptr, const char* delim); +void split_free(char **ptr); +char *substring_between(char *sptr, const char *delims); +int has_rpath(const char *filename); +char *get_rpath(const char *filename); + + + +// config.c +#define CONFIG_BUFFER_SIZE 1024 + +typedef struct { + char *key; + char *value; +} Config; + +char *lstrip(char *sptr); +char *strip(char *sptr); +int isempty(char *sptr); +int isquoted(char *sptr); +Config **config_read(const char *filename); +void config_free(Config **config); +void config_test(void); + + + + +#endif //SPM_SPM_H |