diff options
| author | Joseph Hunkeler <jhunkeler@users.noreply.github.com> | 2021-06-16 12:09:10 -0400 | 
|---|---|---|
| committer | GitHub <noreply@github.com> | 2021-06-16 12:09:10 -0400 | 
| commit | c1476e3394f875f33834877a63f485ec6a6be8f6 (patch) | |
| tree | 1f0a93a9857871fbaf3ec9ad10a0ccc8c6f5bca4 | |
| parent | b0688d5619407b9ac44dd238dc66e0b6841279e0 (diff) | |
| download | multihome-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.yml | 18 | ||||
| -rw-r--r-- | CMakeLists.txt | 10 | ||||
| -rw-r--r-- | config.h.in | 12 | ||||
| -rw-r--r-- | init/init.csh | 12 | ||||
| -rw-r--r-- | init/init.sh | 12 | ||||
| -rw-r--r-- | multihome.c | 274 | ||||
| -rw-r--r-- | multihome.h | 6 | ||||
| -rw-r--r-- | tests.c | 2 | 
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); @@ -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; | 
