#include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include /** Couldn't find any other way to get MAP_ANON on BSD */ #define __BSD_VISIBLE 1 #include #undef __BSD_VISIBLE #if 0 #ifdef __linux__ #include #include #include typedef uint32_t *futex_t; long futex_core(futex_t futex, int futex_op, uint32_t val, const struct timespec *timeout, uint32_t *uaddr2, uint32_t val3) { return syscall(SYS_futex, uaddr, futex_op, val, timeout, uaddr2, val3); } futex_wait(futex_t futex, uint32_t val) { futex_core(futex, FUTEX_WAIT, val, NULL, NULL, 0); } void suspend() { futex( } void wake(); #endif #endif #include #include #include // #define WIKIROOT "~/wiki/" #define DATE_FORMAT "%a %d %b. %Y %H:%M:%S %Z" #if __STDC_VERSION__ < 202311L #define typeof __typeof__ #endif /** The ends of pipes created pipe() */ #define READ_END 0 #define WRITE_END 1 /** Calculate length of a static array */ #define ARR_LEN(arr) ((size_t) (sizeof(arr) / sizeof((arr)[0]))) #define ARRAY(x, ...) ((typeof(x)[]) {x, __VA_ARGS__}) /* Call a procedure with a list of strings */ #define CALL(proc, ...) proc(ARR_LEN(ARRAY(__VA_ARGS__)), ARRAY(__VA_ARGS__)) typedef char **strlist; static pthread_mutex_t *stdout_lock = NULL, *stderr_lock = NULL; /** * Collect all wikis under WIKIROOT. * @param buf * Location where the wikis should be stored. * @returns -1 on failure (and print an error to stderr) * Number of found wikis otherwise (if 0 a message will also be * printed to stdout). */ int gather_wikis(const char *wiki_root, strlist *buf); /** Error callback for glob. */ int errfunc(const char *path, int err); int commit(const char *msg); int ammend(const char *msg); /** * Dispatch a git command through a shellout. * @param argc - length of argv * @param argv - arguments appended to the base git command. * The base git command is configured thruogh the variable `cmdline_base` */ int git(size_t argc, const char *argv[]); /** * Base command when calling git */ static char *cmdline_base[] = { "git", "-c", "color.status=always", "-c", "color.ui=always", }; /** * Compare a fixed string to an aribtary string. */ bool cmp(const char *fix, const char *var) { return strncmp(fix, var, strlen(fix)) == 0; } /** * Collects the globaly available wikis, and print a pretty list of them */ void print_available_wikis(); /** Print help string for the program to stdout */ void print_help(); /** * Find the directory to search for wikis in. * Returns a string which must be freed. * */ char *get_wikiroot(); /* * Concatenate all the strings in arr into a newly allocated string. */ char *strconcat(int count, const char *arr[]); /** * Primary dispatcher of commands. * @param wiki_root - Directory in which to find the given wiki * @param wiki - Which wiki to operate on * @param command - what to do * First member should probably either be the name of a git * subcommand, or one of our special commands. * Remaining member will be used as arguments to that subcommand. */ int wiki_do(const char *wiki_root, const char *wiki, int argc, const char *command[]); void write_stderr(const char *wiki, const char *fmt, ...); void write_stdout(const char *wiki, const char *fmt, ...); struct thread_data { int stdout; int stderr; int peer_pid; char *wiki; pthread_cond_t *cond; }; static void *start_thread(void *d) { struct thread_data *data = d; fprintf(stderr, "pid=%i, stdout=%i, stderr=%i\n", getpid(), data->stdout, data->stderr); /* FILE *out = fdopen(data->stdout, "w"); FILE *err = fdopen(data->stderr, "w"); */ char *line = NULL; size_t linecapp; fd_set fds; FD_SET(data->stdout, &fds); FD_SET(data->stderr, &fds); ssize_t len; // kill(data->peer_pid, SIGUSR1); fprintf(stderr, "'%s' We are here\n", data->wiki); pthread_cond_signal(data->cond); fprintf(stderr, "'%s' Signaled\n", data->wiki); munmap(data->cond, sizeof data->cond); // struct timeval timeout = { // .tv_sec = 1, // }; int count; for (;;) { printf("\n'%s' Running select:\n", data->wiki); struct timeval timeout = { .tv_sec = 1 }; if ((count = select(2, &fds, NULL, NULL, &timeout)) == -1) { fprintf(stderr, " Error in select: %s\n", strerror(errno)); write_stderr(data->wiki, " Error in select: %s\n", strerror(errno)); continue; } fprintf(stderr, " Got %i!\n", count); #if 1 if (FD_ISSET(data->stdout, &fds)) { fprintf(stderr, "on stdout\n"); errno = 0; #if 0 if ((len = getline(&line, &linecapp, out)) == -1) { if (errno) { write_stderr(data->wiki, "Failed reading stdout: %s", strerror(errno)); } /* descriptor was closed */ write_stderr(data->wiki, "stderr was closed"); } else { write_stdout(data->wiki, "%s", line); } #endif } if (FD_ISSET(data->stderr, &fds)) { fprintf(stderr, "on stderr\n"); errno = 0; #if 0 if ((len = getline(&line, &linecapp, err)) == -1) { if (errno) { write_stderr(data->wiki, "Failed reading stderr: %s", strerror(errno)); } /* descriptor was closed */ write_stderr(data->wiki, "stdout was closed"); } else { write_stderr(data->wiki, "%s", line); } #endif } #endif } return NULL; } struct cmd_args { /** Number of wikis to work on */ int wiki_count; strlist wiki_list; /** index into wiki_list, pointing at the next empty slot */ int wiki_list_ptr; }; int parse_commandline( struct cmd_args *cmd, int argc, const char *argv[]) { /* At most every other argument can be a requested wiki */ cmd->wiki_list = malloc(sizeof(*cmd->wiki_list) * argc / 2); /** index into wiki_list, pointing at the next empty slot */ cmd->wiki_list_ptr = 0; int arg = 1; for (; arg < argc; arg++) { if (cmp("--wiki", argv[arg]) || cmp("-w", argv[arg])) { cmd->wiki_list[cmd->wiki_list_ptr++] = (char *) argv[++arg]; ++cmd->wiki_count; continue; } else if (cmp("--list", argv[arg]) || cmp("-l", argv[arg])) { print_available_wikis(); exit(0); } else if (cmp("--help", argv[arg]) || cmp("-l", argv[arg]) || cmp("-?", argv[arg])) { print_help(); exit(0); } else { break; } } cmd->wiki_count = cmd->wiki_list_ptr; return arg; } int main (int argc, const char *argv[]) { syslog(4, "Start"); // truncate("/tmp/hugo/log", 0); printf("pid = %i\n", getpid()); struct cmd_args cmd = {}; int arg = parse_commandline(&cmd, argc, argv); char *wiki_root = get_wikiroot(); /** If no wikis were given, instead list all available wikis */ if (cmd.wiki_list_ptr == 0) { free(cmd.wiki_list); cmd.wiki_count = gather_wikis(wiki_root, &cmd.wiki_list); } /* If no further arguments are given, edit the given wiki */ if (arg == argc) { char *wiki; if (cmd.wiki_count == 0) { fprintf(stderr, "No wikis found\n"); return 1; } wiki = cmd.wiki_list[0]; char *path = malloc(1024); sprintf(path, "%s/%s/index.wiki", wiki_root, wiki); execlp("vim", "vim", path, NULL); } else { /* If further arguments are given, run these on each wiki */ int ret; /* locks heap allocated to allow them to default to NULL */ stdout_lock = malloc(sizeof(*stdout_lock)); stderr_lock = malloc(sizeof(*stderr_lock)); if ((ret = pthread_mutex_init(stdout_lock, NULL))) { fprintf(stderr, "Failed creating stdout mutex: %s\n", strerror(ret)); stdout_lock = NULL; } if ((ret = pthread_mutex_init(stderr_lock, NULL))) { fprintf(stderr, "Failed creating stderr mutex: %s\n", strerror(ret)); stderr_lock = NULL; } int *pids = malloc(sizeof(*pids) * cmd.wiki_count); for (int i = 0; i < cmd.wiki_count; i++) { int stdout_pipes[2], stderr_pipes[2]; if (pipe(stdout_pipes) == -1) { fprintf(stderr, "Failed opening stdout pipe for '%s': %s\n", cmd.wiki_list[i], strerror(errno)); continue; }; if (pipe(stderr_pipes) == -1) { fprintf(stderr, "Failed opening stderr pipe for '%s': %s\n", cmd.wiki_list[i], strerror(errno)); continue; }; pthread_condattr_t attr; pthread_condattr_init(&attr); pthread_condattr_setpshared(&attr, PTHREAD_PROCESS_SHARED); pthread_cond_t *cond = mmap(NULL, sizeof (pthread_cond_t), PROT_READ|PROT_WRITE, MAP_ANON|MAP_SHARED, -1, 0); if (cond == MAP_FAILED) { fprintf(stderr, "Failed establishing shared memory region: %s\n", strerror(errno)); continue; } pthread_cond_init(cond, &attr); pthread_condattr_destroy(&attr); int pid; switch ((pid = fork())) { case 0: ; FILE *err = fdopen(dup(2), "w"); // close(stdout_pipes[READ_END]); // close(stderr_pipes[READ_END]); printf("From %i to stdout\n", getpid()); if (dup2(stdout_pipes[WRITE_END], 1) == -1) { fprintf(err, "Failed replacing stdout: %s\n", strerror(errno)); } if (dup2(stderr_pipes[WRITE_END], 2) == -1) { fprintf(err, "Failed replacing stderr: %s\n", strerror(errno)); } // close(stdout_pipes[WRITE_END]); // close(stderr_pipes[WRITE_END]); { pthread_mutex_t cond_mutex; pthread_mutex_init(&cond_mutex, NULL); syslog(5, "'%s' Waiting on condition", cmd.wiki_list[i]); pthread_cond_wait(cond, &cond_mutex); syslog(5, "'%s' Awoken from condition", cmd.wiki_list[i]); pthread_mutex_unlock(&cond_mutex); pthread_mutex_destroy(&cond_mutex); } printf("From %i to pipe\n", getpid()); pthread_cond_destroy(cond); munmap(cond, sizeof cond); ret = wiki_do( wiki_root, cmd.wiki_list[i], argc - arg, &argv[0] + arg); exit(ret); case -1: fprintf(stderr, "Failed creating process for '%s': %s\n", cmd.wiki_list[i], strerror(errno)); continue; default: pids[i] = pid; // close(stdout_pipes[WRITE_END]); // close(stderr_pipes[WRITE_END]); pthread_t thread; struct thread_data *data = calloc(sizeof *data, 1); { struct thread_data dat = { .stdout = stdout_pipes[0], .stderr = stderr_pipes[0], .wiki = cmd.wiki_list[i], .peer_pid = pid, .cond = cond, }; memcpy(data, &dat, sizeof dat); } if ((ret = pthread_create(&thread, NULL, &start_thread, (void *) data))) { fprintf(stderr, "Failed starting thread: %s\n", strerror(ret)); } break; } } // sleep(3); for (int i = 0; i < cmd.wiki_count; i++) { fprintf(stderr, "Waiting on %i\n", pids[i]); int status; if (waitpid(pids[i], &status, 0) == -1) { if (errno == ECHILD) { fprintf(stderr, "No exit status available\n"); } else { fprintf(stderr, "Waitpid failed: %s\n", strerror(errno)); } } else { fprintf(stderr, "Child exited %s with status code %i", WIFEXITED(status) ? "normally" : "abnormaly", WEXITSTATUS(status)); if (WIFSIGNALED(status)) { fprintf(stderr, " by the signal %i", WTERMSIG(status)); } fprintf(stderr, "\n"); } } } free(wiki_root); // free(cmd.wiki_list); } int gather_wikis(const char *wiki_root, strlist *wiki_list) { int ret = 0; glob_t pglob = {}; ret = glob(CALL(strconcat, wiki_root, "/*"), 0, &errfunc, &pglob); switch (ret) { case 0: break; case GLOB_NOSPACE: fprintf(stderr, "Glob ran out of space\n"); ret = -1; goto gather_wikis_end; case GLOB_ABORTED: fprintf(stderr, "Glob was aborted\n"); ret = -1; goto gather_wikis_end; case GLOB_NOMATCH: printf("No Wikis found\n"); goto gather_wikis_end; default: fprintf(stderr, "Unknown glob error. errno: %s\n", strerror(errno)); ret = -1; goto gather_wikis_end; } *wiki_list = malloc(sizeof(*wiki_list) * pglob.gl_pathc); int idx = 0; for (int i = 0; i < pglob.gl_pathc; i++) { if (strstr(pglob.gl_pathv[i], "html") == NULL) { (*wiki_list)[idx++] = basename(pglob.gl_pathv[i]); } } ret = idx; gather_wikis_end: return ret; } /** * Error callback for glob. * Simply print the error and continue. */ int errfunc(const char *path, int err) { fprintf(stderr, "Failed opening %s: %s\n", path, strerror(err)); return 0; } #if 0 int commit_ammend (const char* action, const char *msg) { char *buf = NULL; if (msg == NULL) { time_t now = time(NULL); struct tm result; if (localtime_r(&now, &result) == NULL) { msg = "Now"; } else { buf = malloc(1024); /* TODO check result */ strftime(buf, 1024, DATE_FORMAT, &result); msg = buf; } } /* "add" has type (char (*)[4]) (pointer to array of length 4. * This just forces the type... */ CALL(git, (const char *) "add", "-A"); CALL(git, (const char *) "commit", "-m", msg); if (buf) free(buf); return 0; } #endif #if 0 int git(size_t argc, const char *argv[]) { pid_t pid; int ret; switch (pid = fork()) { case -1: break; case 0: /* child */ ; size_t cmdline_len = ARR_LEN(cmdline_base) + argc; char **commandline = malloc((cmdline_len + 1) * sizeof(*commandline)); for (size_t i = 0; i < ARR_LEN(cmdline_base); i++) { commandline[i] = cmdline_base[i]; } for (size_t i = 0; i < argc; i++) { commandline[i + ARR_LEN(cmdline_base)] = commandline[i]; } commandline[cmdline_len] = NULL; ret = execvp("git", commandline); if (ret) break; fprintf(stderr, "Exec "); for (size_t i = 0; i < cmdline_len; i++) { fprintf(stderr, " %s", commandline[i]); } fprintf(stderr, " failed: %s\n", strerror(errno)); exit(1); break; default: /* parent */ break; } return ret; return 0; } #endif void print_available_wikis() { printf( "Available Wikis:\n" "================\n"); strlist wikis; char *root = get_wikiroot(); int wiki_count = gather_wikis(root, &wikis); free(root); if (wiki_count < 0) { return; } for (int i = 0; i < wiki_count; i++) { printf("%i. %s\n", i, wikis[i]); free(wikis[i]); } free(wikis); } void print_help() { printf( "Wiki helper. Usage:\n" "--help | -h :: Display this help\n" "--list | -l :: Show available wikis\n" "--wiki | -w :: specify a specific wiki for operation\n" "\n" "Default acts on all wikis.\n" "\n" "wi commit [msg] :: Create a git commit on specified wikis\n" "wi ammend [msg] :: Change last commit\n" "wi g [msg] :: wi commit [msg]; wi push\n" "wi [git-command] :: run git commands on wikis\n"); } int wiki_do(const char *wiki_root, const char *wiki, int argc, const char *command[]) { if (wiki == NULL) return 1; char *chdir_dest; if (wiki[0] == '/') { chdir_dest = strdup(wiki); } else { chdir_dest = CALL(strconcat, wiki_root, "/", wiki); } if (chdir(chdir_dest)) { printf("failed chdir to %s : %s\n", chdir_dest, strerror(errno)); return 1; } free(chdir_dest); if (cmp("commit", command[0])) { // (command + 1) return 1; } if (cmp("ammend", command[0])) { // (command + 1) return 0; } if (cmp("grep", command[0])) { // (command + 1) return 0; } if (cmp("go", command[0]) || cmp("g", command[0])) { // (command + 1) return 0; } if (cmp("echo", command[0])) { syslog(3, "-- %s --", wiki); // char *buf = malloc(0x100); // sprintf(buf, "/tmp/hugo/%i", getpid()); // FILE *log = fopen("/tmp/hugo/log", "a"); // fprintf(log, "Gonna write!\n"); for (;;) { // syslog(4, "%s Wanting to print", wiki); int count = printf("-- %s --\n", wiki); // syslog(4, "%s printed count=%i", wiki, count); fflush(stdout); } // fprintf(log, "Wrote %i bytes\n", count); // sprintf(buf, "/proc/%i/fd", getpid()); #if 0 DIR *d = opendir(buf); struct dirent *ent; void *ptr = malloc(sizeof(struct dirent) + 1000 + 1); while (readdir_r(d, ptr, &ent) == 0) { if (ent == NULL) break; memset(buf, 0, 0x100); readlinkat(dirfd(d), ent->d_name, buf, 0x100); fprintf(log, "%s -> ", ent->d_name); fprintf(log, "%s\n", buf); } closedir(d); free(buf); #endif close(1); close(2); // :fclose(log); // fflush()q return 0; } return 0; } /** Get home directory of currently logged in user * Starts by looking at the environment variable HOME, and if that is * unset, instead get the home directory of the currently logged in * user, finally defaulting to '/'. * * The string returned is valid up until the next call to `getpwuid`. * */ const char *get_home() { const char *home = getenv("HOME"); struct passwd *pw; if (home == NULL) { pw = getpwuid(geteuid()); if (pw == NULL) { home = "/"; } else { home = pw->pw_dir; } } return home; } char *get_wikiroot() { { const char *result = getenv("WIKIROOT"); if (result != NULL) { return strdup(result); } } const char *home = getenv("HOME"); return CALL(strconcat, home, "/wiki"); } char *strconcat(int count, const char *arr[]) { size_t allocated = 100; size_t used = 0; char *dest = malloc(allocated); for (int i = 0; i < count; i++) { size_t len = strlen(arr[i]); if (used + len >= allocated) { allocated <<= 2; char *tmp = realloc(dest, allocated); /* TODO should the old buffer be freed? */ if (tmp == NULL) { fprintf(stderr, "Realloc failed\n"); exit(1); } dest = tmp; } memcpy(dest + used, arr[i], len); used += len; } dest[used] = '\0'; return dest; } int locked_write(pthread_mutex_t *lock, FILE *port, const char *fmt, va_list ap) { int err, ret; if (lock && (err = pthread_mutex_lock(lock))) { fprintf(stderr, "Failed locking mutex: %s\n", strerror(err)); } ret = vfprintf(port, fmt, ap); if (lock && (err = pthread_mutex_unlock(lock))) { fprintf(stderr, "Failed unlocking mutex: %s\n", strerror(err)); } return ret; } char *prepend_wiki(const char *wiki, const char *fmt) { int wiki_len = strlen(wiki); int fmt_len = strlen(fmt); char *true_fmt = malloc(wiki_len + fmt_len + 1); memcpy(true_fmt, wiki, wiki_len); memcpy(true_fmt + wiki_len, fmt, fmt_len); true_fmt[wiki_len + fmt_len] = '\0'; return true_fmt; } void write_stderr(const char *wiki, const char *fmt, ...) { char *true_fmt = prepend_wiki(wiki, fmt); va_list ap; va_start(ap, fmt); locked_write(stderr_lock, stdout, fmt, ap); va_end(ap); free(true_fmt); } void write_stdout(const char *wiki, const char *fmt, ...) { char *true_fmt = prepend_wiki(wiki, fmt); va_list ap; va_start(ap, fmt); locked_write(stdout_lock, stdout, fmt, ap); va_end(ap); free(true_fmt); }