aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJoseph Hunkeler <jhunkeler@gmail.com>2019-12-06 12:27:23 -0500
committerJoseph Hunkeler <jhunkeler@gmail.com>2019-12-06 12:27:23 -0500
commit64551ae0434176b19d4abf908eee08e75890dfb6 (patch)
tree6e7c9d34a9575fe09fc0145aa848ce830cd9df04
parentae93deb9b1f4c83addd90e49af35543ce0c23a38 (diff)
downloadspmc-64551ae0434176b19d4abf908eee08e75890dfb6.tar.gz
Cummulative work
-rw-r--r--CMakeLists.txt7
-rw-r--r--compat.c16
-rw-r--r--config.c186
-rw-r--r--config.h.in6
-rw-r--r--doxygen.conf379
-rw-r--r--spm.c343
-rw-r--r--spm.h68
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
diff --git a/spm.c b/spm.c
index 152f3f1..478dbb5 100644
--- a/spm.c
+++ b/spm.c
@@ -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
diff --git a/spm.h b/spm.h
new file mode 100644
index 0000000..381ddb7
--- /dev/null
+++ b/spm.h
@@ -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