aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJoseph Hunkeler <jhunkeler@users.noreply.github.com>2021-06-16 12:09:10 -0400
committerGitHub <noreply@github.com>2021-06-16 12:09:10 -0400
commitc1476e3394f875f33834877a63f485ec6a6be8f6 (patch)
tree1f0a93a9857871fbaf3ec9ad10a0ccc8c6f5bca4
parentb0688d5619407b9ac44dd238dc66e0b6841279e0 (diff)
downloadmultihome-c1476e3394f875f33834877a63f485ec6a6be8f6.tar.gz
Add shell init scripts and host_groups (#3)
* Add shell init scripts (templates) * Check for additional symbols * Handle missing PATH_MAX declaration (e.g. Ubuntu) * Install shell scripts into $PREFIX/share/init * Replace _INIT declaration with HOST_GROUP * Add scripts_dir and config_host_group struct elements to global 'multihome' * Fix count_substrings() returning wrong type * Refactor RSYNC_BIN to MULTIHOME_RSYNC_BIN * get_timestamp() no longer accepts a buffer as an argument * write_init_script() now uses scripts in $PREFIX/share/init/ to generate output files * Add user_host_group(), which allows grouping systems to a single home directory by way of regex pattern matching * strip_domainname() returns modified pointer, not new storage * Fix strip_domainname test * Create configuration directory if it does not exist * CI needs to install multihome and use the correct script * Always resolve argv[0] to absolute path
-rw-r--r--.github/workflows/cmake.yml18
-rw-r--r--CMakeLists.txt10
-rw-r--r--config.h.in12
-rw-r--r--init/init.csh12
-rw-r--r--init/init.sh12
-rw-r--r--multihome.c274
-rw-r--r--multihome.h6
-rw-r--r--tests.c2
8 files changed, 270 insertions, 76 deletions
diff --git a/.github/workflows/cmake.yml b/.github/workflows/cmake.yml
index 8b6ba9b..7488458 100644
--- a/.github/workflows/cmake.yml
+++ b/.github/workflows/cmake.yml
@@ -38,21 +38,26 @@ jobs:
# Execute the build. You can specify a specific target with "--target <NAME>"
run: cmake --build . --config $BUILD_TYPE
+ - name: Install
+ working-directory: ${{runner.workspace}}/build
+ shell: bash
+ run: sudo make install
+
- name: Initialize home
working-directory: ${{runner.workspace}}/build
shell: bash
- run: ./multihome -s
+ run: multihome -s
- name: Unit Test
working-directory: ${{runner.workspace}}/build
shell: bash
- run: ./multihome -t
+ run: multihome -t
- name: Runtime Test
working-directory: ${{runner.workspace}}/build
shell: bash
run: |
- . ~/.multihome/init
+ . ~/.multihome/init.sh
. /etc/profile
. ~/.bash_profile
@@ -86,3 +91,10 @@ jobs:
test -f TESTFILE
test -f .gitconfig
test -L .gemrc
+
+ echo
+ for x in topdir/.multihome/init.*; do
+ echo "Dumping $x ..."
+ cat "$x"
+ echo
+ done \ No newline at end of file
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 0c49bfa..9edb081 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -4,6 +4,8 @@ include(CheckSymbolExists)
include(CheckCSourceCompiles)
set(CMAKE_C_STANDARD 99)
+set(DATA_DIR ${CMAKE_INSTALL_PREFIX}/share/${PROJECT_NAME})
+set(MULTIHOME_SCRIPTS_DIR ${CMAKE_INSTALL_PREFIX}/share/${PROJECT_NAME}/init)
include_directories("${CMAKE_CURRENT_BINARY_DIR}")
@@ -11,6 +13,9 @@ check_symbol_exists(basename "libgen.h" HAVE_BASENAME)
check_symbol_exists(dirname "libgen.h" HAVE_DIRNAME)
check_symbol_exists(getpwuid "pwd.h" HAVE_GETPWUID)
check_symbol_exists(geteuid "unistd.h" HAVE_GETEUID)
+check_symbol_exists(strstr "string.h" HAVE_STRSTR)
+check_symbol_exists(sprintf "string.h" HAVE_SPRINTF)
+check_symbol_exists(PATH_MAX "limits.h" HAVE_PATH_MAX)
check_c_source_compiles(
"
@@ -22,7 +27,7 @@ check_c_source_compiles(
HAVE_ARGP
)
-find_program(RSYNC_BIN
+find_program(MULTIHOME_RSYNC_BIN
NAMES rsync
REQUIRED)
@@ -34,3 +39,6 @@ add_executable(multihome
install(TARGETS multihome
RUNTIME DESTINATION bin)
+
+install(DIRECTORY init
+ DESTINATION ${DATA_DIR})
diff --git a/config.h.in b/config.h.in
index 6007830..28ad994 100644
--- a/config.h.in
+++ b/config.h.in
@@ -1 +1,11 @@
-#cmakedefine RSYNC_BIN "@RSYNC_BIN@"
+#ifndef CONFIG_H
+#define CONFIG_H
+
+#cmakedefine MULTIHOME_RSYNC_BIN "@MULTIHOME_RSYNC_BIN@"
+#cmakedefine MULTIHOME_SCRIPTS_DIR "@MULTIHOME_SCRIPTS_DIR@"
+#cmakedefine HAVE_PATH_MAX @HAVE_PATH_MAX@
+#if !HAVE_PATH_MAX
+ #define PATH_MAX 1024
+#endif
+
+#endif // CONFIG_H
diff --git a/init/init.csh b/init/init.csh
new file mode 100644
index 0000000..5c370c7
--- /dev/null
+++ b/init/init.csh
@@ -0,0 +1,12 @@
+# Set location of multihome to avoid PATH lookups
+setenv MULTIHOME "%s"
+if ( -x "$MULTIHOME" ) then
+ # Save HOME
+ setenv HOME_OLD "$HOME"
+ # Redeclare HOME
+ setenv HOME "`$MULTIHOME`"
+ # Switch to new HOME
+ if ( "$HOME" != "$HOME_OLD" ) then
+ cd "$HOME"
+ endif
+endif
diff --git a/init/init.sh b/init/init.sh
new file mode 100644
index 0000000..804d511
--- /dev/null
+++ b/init/init.sh
@@ -0,0 +1,12 @@
+# Set location of multihome to avoid PATH lookups
+MULTIHOME="%s"
+if [ -x "$MULTIHOME" ]; then
+ # Save HOME
+ HOME_OLD="$HOME"
+ # Redeclare HOME
+ HOME="$($MULTIHOME)"
+ # Switch to new HOME
+ if [ "$HOME" != "$HOME_OLD" ]; then
+ cd "$HOME"
+ fi
+fi
diff --git a/multihome.c b/multihome.c
index 1ce9c44..a3a99bb 100644
--- a/multihome.c
+++ b/multihome.c
@@ -16,9 +16,10 @@ struct {
char marker[PATH_MAX];
char entry_point[PATH_MAX];
char config_dir[PATH_MAX];
+ char config_host_group[PATH_MAX];
char config_transfer[PATH_MAX];
char config_skeleton[PATH_MAX];
- char config_init[PATH_MAX];
+ char scripts_dir[PATH_MAX];
} multihome;
/**
@@ -50,7 +51,7 @@ ssize_t count_substrings(const char *s, char *sub) {
char *str_orig;
size_t str_length;
size_t sub_length;
- size_t result;
+ ssize_t result;
str = strdup(s);
if (str == NULL) {
@@ -283,7 +284,7 @@ int copy(char *source, char *dest, int mode) {
strcat(args, "u");
}
- return shell((char *[]){RSYNC_BIN, args, source, dest, NULL});
+ return shell((char *[]){MULTIHOME_RSYNC_BIN, args, source, dest, NULL});
}
/**
@@ -305,21 +306,13 @@ int touch(char *filename) {
/**
* Get date and time as a string
*
- * If result is NULL this function will return a pointer to a heap address.
- * The caller is responsible for freeing memory.
- *
- * @param result buffer must be at least 22 bytes, or NULL
- * @return pointer to buffer containing date and time
+ * @return pointer to buffer containing date and time (do not free())
*/
-char *get_timestamp(char **result) {
+char *get_timestamp() {
+ static char result[100];
struct tm *tm;
time_t now;
- if ((*result) == NULL) {
- // I doubt anyone use this program ~10,000 years from now
- (*result) = calloc(strlen("xx-xx-xxxx @ xx:xx:xx") + 1, sizeof(char));
- }
-
// Get current time
time(&now);
@@ -327,60 +320,190 @@ char *get_timestamp(char **result) {
tm = localtime(&now);
// Write result to buffer
- sprintf((*result), "%02d-%02d-%d @ %02d:%02d:%02d",
+ memset(result, '\0', sizeof(result));
+ sprintf(result, "%02d-%02d-%d @ %02d:%02d:%02d",
tm->tm_mon + 1, tm->tm_mday, tm->tm_year + 1900,
tm->tm_hour, tm->tm_min, tm->tm_sec);
- return (*result);
+ return result;
+}
+
+char *str_ends_with(const char *s1, char *s2) {
+ if (!s1 || !s2) {
+ return NULL;
+ }
+ size_t s1_len;
+ size_t s2_len;
+ const char *start;
+
+ s1_len = strlen(s1);
+ s2_len = strlen(s2);
+
+ if (s2_len > s1_len) {
+ return NULL;
+ }
+
+ start = &s1[s1_len - s2_len];
+ return strstr(start, s2);
}
/**
- * Generate multihome initialization script
+ * Generate multihome initialization scripts
*/
void write_init_script() {
- const char *script_block = \
- "#\n# This script was generated on %s\n#\n\n"
- "# Set location of multihome to avoid PATH lookups\n"
- "MULTIHOME=%s\n"
- "if [ -x $MULTIHOME ]; then\n"
- " # Save HOME\n"
- " HOME_OLD=$HOME\n"
- " # Redeclare HOME\n"
- " HOME=$($MULTIHOME)\n"
- " # Switch to new HOME\n"
- " if [ \"$HOME\" != \"$HOME_OLD\" ]; then\n"
- " cd $HOME\n"
- " fi\n"
- "fi\n";
- char buf[PATH_MAX];
- char date[100];
- char *path;
- FILE *fp;
+ char buf[BUFSIZ];
+ DIR *d;
+ struct dirent *rec;
+ char *date;
- // Populate buf with the program's argv[0] record
- strcpy(buf, multihome.entry_point);
+ memset(buf, '\0', sizeof(buf));
- // Find the program's system path
- path = find_program(buf);
- if (path == NULL) {
- fprintf(stderr, "%s not found on $PATH\n", buf);
+ d = opendir(multihome.scripts_dir);
+ if (!d) {
+ perror(multihome.scripts_dir);
exit(1);
}
+ while ((rec = readdir(d)) != NULL) {
+ if (rec->d_type == DT_REG && strstr(rec->d_name, "init.")) {
+ FILE *fp_input;
+ FILE *fp_output;
+ char *script_name;
+ char path_input[PATH_MAX];
+ char path_output[PATH_MAX];
+
+ script_name = basename(rec->d_name);
+ sprintf(path_input, "%s/%s", multihome.scripts_dir, script_name);
+ fp_input = fopen(path_input, "r");
+ if (!fp_input) {
+ perror(rec->d_name);
+ exit(1);
+ }
+ memset(buf, '\0', sizeof(buf));
+ fread(buf, sizeof(char), sizeof(buf) - 1, fp_input);
+ fclose(fp_input);
+
+ sprintf(path_output, "%s/%s", multihome.config_dir, script_name);
+ fp_output = fopen(path_output, "w+");
+ if (!fp_output) {
+ perror(path_output);
+ exit(1);
+ }
+ date = get_timestamp();
+ fprintf(fp_output, "# Version: %s\n", VERSION);
+ fprintf(fp_output, "# Generated: %s\n\n", date);
+ fprintf(fp_output, buf, multihome.entry_point);
+ fclose(fp_output);
+ }
+ }
+ closedir(d);
+}
- // Clear buf and store the updated path
- memset(buf, '\0', sizeof(buf));
- strcpy(buf, path);
+/**
+ * End string on first occurrence of LF or whitespace
+ * @param str
+ * @return non-zero if LF not found
+ */
+static int strip(char **str) {
+ char *orig;
+ char *tmp;
+ char *result;
+
+ orig = (*str);
+ tmp = NULL;
+ result = strchr((*str), '\n');
+ if (result) {
+ *result = '\0';
+ }
+
+ tmp = (*str);
+ while(tmp) {
+ if (isblank(*tmp)) {
+ tmp++;
+ continue;
+ }
+ break;
+ }
+ size_t size;
+ size_t len;
+ size = tmp - (*str);
+ len = strlen(tmp);
- // Open init script for writing
- fp = fopen(multihome.config_init, "w+");
- if (fp == NULL) {
- perror(multihome.config_init);
- exit(errno);
+ memmove(orig, tmp, len);
+ len = strlen(orig) - size;
+ *((*str) + len) = '\0';
+
+ result = strrchr((*str), ' ');
+ if (result) {
+ *result = '\0';
}
- // Write init script
- fprintf(fp, script_block, get_timestamp((char **) &date), buf);
+ return 0;
+}
+
+/**
+ *
+ * @param hostname
+ * @return
+ */
+int user_host_group(char **hostname) {
+ int found;
+ FILE *fp;
+ char line[PATH_MAX];
+ char home_tmp[PATH_MAX];
+ size_t lineno;
+ int status;
+
+
+ fp = fopen(multihome.config_host_group, "r");
+ if (!fp) {
+ perror(multihome.config_host_group);
+ exit(1);
+ }
+
+ // Preserve hostname
+ strcpy(home_tmp, (*hostname));
+
+ found = 0;
+ for (size_t i = 0; (fgets(line, PATH_MAX - 1, fp) != NULL) && !found; i++) {
+ size_t alloc;
+ regex_t compiled;
+ regmatch_t match[100];
+ char **data;
+ int num_matches;
+
+ data = split(line, "=", &alloc);
+ strip(&data[0]);
+ strip(&data[1]);
+
+ if (regcomp(&compiled, data[0], 0) != 0) {
+ // handle compilation failure
+ fprintf(stderr, "%s:%zu:unable to compile regex pattern '%s'\n", multihome.config_host_group, i, data[0]);
+ free_array((void **) data, alloc);
+ continue;
+ }
+
+ num_matches = sizeof(match) / sizeof(regmatch_t);
+ status = regexec(&compiled, (*hostname), num_matches, match, REG_EXTENDED);
+ if (status == REG_NOMATCH) {
+ // Ignore unmatched records
+ } else if (status > 0) {
+ // handle fatal error
+ char errbuf[BUFSIZ];
+ regerror(status, &compiled, errbuf, BUFSIZ);
+ fprintf(stderr, "%s:%zu:regex %s\n", multihome.config_host_group, i, errbuf);
+ } else {
+ // Replace preserved hostname with the requested name, and stop
+ strcpy(home_tmp, data[1]);
+ found = 1;
+ }
+ regfree(&compiled);
+ free_array((void **)data, alloc);
+ }
fclose(fp);
+
+ // Replace the hostname
+ strcpy((*hostname), home_tmp);
+ return found;
}
/**
@@ -503,24 +626,16 @@ void user_transfer(int copy_mode) {
*/
char *strip_domainname(char *hostname) {
char *ptr;
- char *nodename;
-
- nodename = calloc(HOST_NAME_MAX, sizeof(char));
- if (nodename == NULL) {
- return NULL;
- }
- strncpy(nodename, hostname, HOST_NAME_MAX - 1);
- ptr = strchr(nodename, '.');
+ ptr = strchr(hostname, '.');
if (ptr != NULL) {
*ptr = '\0';
}
- return nodename;
+ return hostname;
}
-
// begin argp setup
static char doc[] = "Partition a home directory per-host when using a centrally mounted /home";
static char args_doc[] = "";
@@ -578,7 +693,7 @@ static error_t parse_opt (int key, char *arg, struct argp_state *state) {
* @return 0=not found, 1 found
*/
int rsync_exists() {
- if (access(RSYNC_BIN, F_OK) < 0) {
+ if (access(MULTIHOME_RSYNC_BIN, F_OK) < 0) {
return 1;
}
return 0;
@@ -612,7 +727,7 @@ int main(int argc, char *argv[]) {
// Refuse to operate if rsync is not available
if (rsync_exists() != 0) {
- fprintf(stderr, "rsync program not found (expecting: %s)\n", RSYNC_BIN);
+ fprintf(stderr, "rsync program not found (expecting: %s)\n", MULTIHOME_RSYNC_BIN);
return 1;
}
@@ -655,20 +770,43 @@ int main(int argc, char *argv[]) {
}
// Populate multihome struct
- strcpy(multihome.entry_point, argv[0]);
+ char *entry_point;
+ entry_point = find_program(argv[0]);
+ strcpy(multihome.entry_point, entry_point);
strcpy(multihome.path_old, path_old);
strcpy(multihome.path_root, MULTIHOME_ROOT);
+ strcpy(multihome.scripts_dir, MULTIHOME_SCRIPTS_DIR);
sprintf(multihome.config_dir, "%s/%s", multihome.path_old, MULTIHOME_CFGDIR);
- sprintf(multihome.config_init, "%s/%s", multihome.config_dir, MULTIHOME_CFG_INIT);
sprintf(multihome.config_transfer, "%s/%s", multihome.config_dir, MULTIHOME_CFG_TRANSFER);
sprintf(multihome.config_skeleton, "%s/%s", multihome.config_dir, MULTIHOME_CFG_SKEL);
+ sprintf(multihome.config_host_group, "%s/%s", multihome.config_dir, MULTIHOME_CFG_HOST_GROUP);
- // Use short hostname
+ // Generate configuration directory
+ if (access(multihome.config_dir, F_OK) < 0) {
+ fprintf(stderr, "Creating configuration directory: %s\n", multihome.config_dir);
+ if (mkdirs(multihome.config_dir) < 0) {
+ perror(multihome.config_dir);
+ return errno;
+ }
+ }
+
+ // Generate a blank host group configuration
+ if (access(multihome.config_host_group, F_OK) < 0) {
+ fprintf(stderr, "Creating new host group configuration: %s\n", multihome.config_host_group);
+ if (touch(multihome.config_host_group) < 0) {
+ perror(multihome.config_host_group);
+ return errno;
+ }
+ }
+
+ // The short hostname is used to establish the name for the new home directory
char *nodename;
nodename = strip_domainname(host_info.nodename);
- sprintf(multihome.path_new, "%s/%s/%s", multihome.path_old, multihome.path_root, nodename);
- free(nodename);
+ // When this host belongs to a host group, modify the hostname once more
+ user_host_group(&nodename);
+
+ sprintf(multihome.path_new, "%s/%s/%s", multihome.path_old, multihome.path_root, nodename);
sprintf(multihome.path_topdir, "%s/%s", multihome.path_new, MULTIHOME_TOPDIR);
sprintf(multihome.marker, "%s/%s", multihome.path_new, MULTIHOME_MARKER);
diff --git a/multihome.h b/multihome.h
index 80ce082..8f4c276 100644
--- a/multihome.h
+++ b/multihome.h
@@ -19,6 +19,8 @@
#include <wait.h>
#include <argp.h>
#include <time.h>
+#include <dirent.h>
+#include <regex.h>
#include "config.h"
#define VERSION "0.0.1"
@@ -26,7 +28,7 @@
#define MULTIHOME_TOPDIR "topdir"
#define MULTIHOME_CFGDIR ".multihome"
#define MULTIHOME_CFG_TRANSFER "transfer"
-#define MULTIHOME_CFG_INIT "init"
+#define MULTIHOME_CFG_HOST_GROUP "host_group"
#define MULTIHOME_CFG_SKEL "skel/" // NOTE: Trailing slash is required
#define MULTIHOME_MARKER ".multihome_controlled"
#define OS_SKEL_DIR "/etc/skel/" // NOTE: Trailing slash is required
@@ -46,7 +48,7 @@ int shell(char *args[]);
int mkdirs(char *path);
int copy(char *source, char *dest, int mode);
int touch(char *filename);
-char *get_timestamp(char **result);
+char *get_timestamp();
void write_init_script();
void user_transfer(int copy_mode);
char *strip_domainname(char *hostname);
diff --git a/tests.c b/tests.c
index 522d03d..1cd5a56 100644
--- a/tests.c
+++ b/tests.c
@@ -57,7 +57,7 @@ void test_touch() {
void test_strip_domainname() {
puts("strip_domainname()");
- char *input = "subdomain.domain.tld";
+ char *input = strdup("subdomain.domain.tld");
char *truth = "subdomain";
char *result;