diff --git a/README.md b/README.md index 3d7910a..0f4b63b 100644 --- a/README.md +++ b/README.md @@ -95,6 +95,15 @@ is the right default. make ``` +Build just the internal static library artifact: + +```sh +make libatch.a +``` + +Note: `libatch.a` is currently an internal build artifact used to modularize +`atch` internals. It is not yet a documented/stable public API. + ## Usage ``` diff --git a/atch.c b/atch.c index 9a516de..3e45f09 100644 --- a/atch.c +++ b/atch.c @@ -1,4 +1,9 @@ #include "atch.h" +#include "atch_cmd.h" +#include "atch_cli_opts.h" +#include "atch_cli_runtime.h" +#include "atch_paths.h" +#include "atch_session.h" /* Env-var name string, computed from progname at startup. */ const char *session_envvar; @@ -26,64 +31,6 @@ static void init_envvar_names(void) session_envvar = envname; } -/* Returns the basename of the current session socket path. */ -const char *session_shortname(void) -{ - const char *p = strrchr(sockname, '/'); - return p ? p + 1 : sockname; -} - -/* Returns the directory where session sockets are stored. */ -void get_session_dir(char *buf, size_t size) -{ - const char *home = getenv("HOME"); - const char *base = strrchr(progname, '/'); - struct passwd *pw; - - base = base ? base + 1 : progname; - - /* If $HOME is unset or empty, try the passwd database. */ - if (!home || !*home) { - pw = getpwuid(getuid()); - if (pw && pw->pw_dir && *pw->pw_dir) - home = pw->pw_dir; - } - - /* Use $HOME only if it is set and not the root directory. */ - if (home && *home && strcmp(home, "/") != 0) - snprintf(buf, size, "%s/.cache/%s", home, base); - else - snprintf(buf, size, "/tmp/.%s-%d", base, (int)getuid()); -} - -/* -** Long-path helper for Unix socket operations: saves cwd, chdirs into the -** directory part of path, calls fn(basename), then restores cwd. -** Used by connect_socket and create_socket to handle paths > sun_path limit. -*/ -int socket_with_chdir(char *path, int (*fn)(char *)) -{ - char *slash = strrchr(path, '/'); - int dirfd, s; - - if (!slash) { - errno = ENAMETOOLONG; - return -1; - } - dirfd = open(".", O_RDONLY); - if (dirfd < 0) - return -1; - *slash = '\0'; - s = chdir(path) >= 0 ? fn(slash + 1) : -1; - *slash = '/'; - if (s >= 0 && fchdir(dirfd) < 0) { - close(s); - s = -1; - } - close(dirfd); - return s; -} - /* argv[0] from the program */ char *progname; /* The name of the passed in socket. */ @@ -108,665 +55,7 @@ int no_ansiterm = 0; struct termios orig_term; int dont_have_tty; -/* Parse a size string: bare number, or number with k/K (×1024) or m/M (×1048576). -** Writes result to *out. Returns 0 on success, 1 on error. */ -static int parse_size(const char *s, size_t *out) -{ - char *end; - unsigned long v; - - if (!s || !*s) - return 1; - v = strtoul(s, &end, 10); - if (end == s) - return 1; - if (*end == 'k' || *end == 'K') { - v *= 1024; - end++; - } else if (*end == 'm' || *end == 'M') { - v *= 1024 * 1024; - end++; - } - if (*end != '\0') - return 1; - *out = (size_t)v; - return 0; -} - -/* -** Parse option flags from argv/argc. Stops at '--' or a non-option argument. -** Returns 0 on success, 1 on error (message already printed). -*/ -static int parse_options(int *argc, char ***argv) -{ - while (*argc >= 1 && ***argv == '-') { - char *p; - - if (strcmp((*argv)[0], "--") == 0) { - ++(*argv); - --(*argc); - break; - } - - for (p = (*argv)[0] + 1; *p; ++p) { - if (*p == 'E') - detach_char = -1; - else if (*p == 'z') - no_suspend = 1; - else if (*p == 'q') - quiet = 1; - else if (*p == 't') - no_ansiterm = 1; - else if (*p == 'e') { - ++(*argv); - --(*argc); - if (*argc < 1) { - printf("%s: No escape character " - "specified.\n", progname); - printf("Try '%s --help' for more " - "information.\n", progname); - return 1; - } - if ((*argv)[0][0] == '^' && (*argv)[0][1]) { - if ((*argv)[0][1] == '?') - detach_char = '\177'; - else - detach_char = - (*argv)[0][1] & 037; - } else - detach_char = (*argv)[0][0]; - break; - } else if (*p == 'r') { - ++(*argv); - --(*argc); - if (*argc < 1) { - printf("%s: No redraw method " - "specified.\n", progname); - printf("Try '%s --help' for more " - "information.\n", progname); - return 1; - } - if (strcmp((*argv)[0], "none") == 0) - redraw_method = REDRAW_NONE; - else if (strcmp((*argv)[0], "ctrl_l") == 0) - redraw_method = REDRAW_CTRL_L; - else if (strcmp((*argv)[0], "winch") == 0) - redraw_method = REDRAW_WINCH; - else { - printf("%s: Invalid redraw method " - "specified.\n", progname); - printf("Try '%s --help' for more " - "information.\n", progname); - return 1; - } - break; - } else if (*p == 'R') { - ++(*argv); - --(*argc); - if (*argc < 1) { - printf("%s: No clear method " - "specified.\n", progname); - printf("Try '%s --help' for more " - "information.\n", progname); - return 1; - } - if (strcmp((*argv)[0], "none") == 0) - clear_method = CLEAR_NONE; - else if (strcmp((*argv)[0], "move") == 0) - clear_method = CLEAR_MOVE; - else { - printf("%s: Invalid clear method " - "specified.\n", progname); - printf("Try '%s --help' for more " - "information.\n", progname); - return 1; - } - break; - } else if (*p == 'C') { - ++(*argv); - --(*argc); - if (*argc < 1) { - printf("%s: No log size " - "specified.\n", progname); - printf("Try '%s --help' for more " - "information.\n", progname); - return 1; - } - if (parse_size((*argv)[0], &log_max_size)) { - printf("%s: Invalid log size " - "'%s'.\n", progname, (*argv)[0]); - printf("Try '%s --help' for more " - "information.\n", progname); - return 1; - } - break; - } else { - printf("%s: Invalid option '-%c'\n", - progname, *p); - printf("Try '%s --help' for more " - "information.\n", progname); - return 1; - } - } - ++(*argv); - --(*argc); - } - return 0; -} - -/* Expand a bare session name to its full socket path in-place. */ -static void expand_sockname(void) -{ - char dir[512]; - size_t fulllen; - char *full; - char *slash; - - if (strchr(sockname, '/') != NULL) - return; - - get_session_dir(dir, sizeof(dir)); - slash = strrchr(dir, '/'); - if (slash) { - *slash = '\0'; - mkdir(dir, 0700); - *slash = '/'; - } - mkdir(dir, 0700); - fulllen = strlen(dir) + 1 + strlen(sockname); - full = malloc(fulllen + 1); - snprintf(full, fulllen + 1, "%s/%s", dir, sockname); - sockname = full; -} - -/* Return argv unchanged if argc > 0; otherwise return a {shell, NULL} argv. */ -static char **use_shell_if_no_cmd(int argc, char **argv) -{ - static char *shell_argv[2]; - const char *shell; - struct passwd *pw; - - if (argc > 0) - return argv; - shell = getenv("SHELL"); - if (!shell || !*shell) { - pw = getpwuid(getuid()); - if (pw && pw->pw_shell && *pw->pw_shell) - shell = pw->pw_shell; - } - if (!shell || !*shell) - shell = "/bin/sh"; - shell_argv[0] = (char *)shell; - shell_argv[1] = NULL; - return shell_argv; -} - -/* Snapshot terminal settings; sets dont_have_tty if not a tty. */ -static void save_term(void) -{ - if (tcgetattr(0, &orig_term) < 0) { - memset(&orig_term, 0, sizeof(struct termios)); - dont_have_tty = 1; - } -} - -/* Print error and return 1 if no tty is available. */ -static int require_tty(void) -{ - if (dont_have_tty) { - printf("%s: attaching to a session requires a terminal.\n", - progname); - return 1; - } - return 0; -} - -/* Consume first arg as session name, expand it, advance argc/argv. */ -static int consume_session(int *argc, char ***argv) -{ - if (*argc < 1) { - printf("%s: No session was specified.\n", progname); - printf("Try '%s --help' for more information.\n", progname); - return 1; - } - sockname = **argv; - ++(*argv); - --(*argc); - expand_sockname(); - return 0; -} - -/* True if arg matches any of the given names (NULL slots are ignored). */ -static int is_cmd(const char *arg, const char *a, const char *b, const char *c) -{ - return strcmp(arg, a) == 0 || - (b && strcmp(arg, b) == 0) || (c && strcmp(arg, c) == 0); -} - -/* atch list [-a] */ -static int cmd_list(int argc, char **argv) -{ - int show_all = 0; - - while (argc >= 1 && strcmp(argv[0], "-a") == 0) { - show_all = 1; - argc--; - argv++; - } - return list_main(show_all); -} - -/* atch current -** SESSION_ENVVAR holds the colon-separated ancestry chain, outermost first. -** A single (non-nested) session has no colon. */ -static int cmd_current(void) -{ - const char *chain = getenv(SESSION_ENVVAR); - char *copy, *seg, *colon; - const char *name; - int first; - - if (!chain || !*chain) - return 1; - - copy = strdup(chain); - if (!copy) - return 1; - - first = 1; - seg = copy; - for (;;) { - colon = strchr(seg, ':'); - if (colon) - *colon = '\0'; - name = strrchr(seg, '/'); - if (!first) - printf(" > "); - printf("%s", name ? name + 1 : seg); - first = 0; - if (!colon) - break; - seg = colon + 1; - } - printf("\n"); - free(copy); - return 0; -} - -/* atch attach — strict attach, fail if missing */ -static int cmd_attach(int argc, char **argv) -{ - if (parse_options(&argc, &argv)) - return 1; - if (consume_session(&argc, &argv)) - return 1; - if (parse_options(&argc, &argv)) - return 1; - if (argc > 0) { - printf("%s: Invalid number of arguments.\n", progname); - printf("Try '%s --help' for more information.\n", progname); - return 1; - } - save_term(); - if (require_tty()) - return 1; - return attach_main(0); -} - -/* atch new [cmd...] — create session and attach */ -static int cmd_new(int argc, char **argv) -{ - if (parse_options(&argc, &argv)) - return 1; - if (consume_session(&argc, &argv)) - return 1; - if (parse_options(&argc, &argv)) - return 1; - argv = use_shell_if_no_cmd(argc, argv); - save_term(); - if (require_tty()) - return 1; - if (master_main(argv, 1, 0) != 0) - return 1; - if (!quiet) - printf("%s: session '%s' created\n", progname, - session_shortname()); - return attach_main(0); -} - -/* atch start [cmd...] — create detached */ -static int cmd_start(int argc, char **argv) -{ - if (parse_options(&argc, &argv)) - return 1; - if (consume_session(&argc, &argv)) - return 1; - if (parse_options(&argc, &argv)) - return 1; - argv = use_shell_if_no_cmd(argc, argv); - save_term(); - if (master_main(argv, 0, 0) != 0) - return 1; - if (!quiet) - printf("%s: session '%s' started\n", progname, - session_shortname()); - return 0; -} - -/* atch run [cmd...] — create, master stays in foreground */ -static int cmd_run(int argc, char **argv) -{ - if (parse_options(&argc, &argv)) - return 1; - if (consume_session(&argc, &argv)) - return 1; - if (parse_options(&argc, &argv)) - return 1; - argv = use_shell_if_no_cmd(argc, argv); - save_term(); - return master_main(argv, 0, 1); -} - -/* atch push — pipe stdin into session */ -static int cmd_push(int argc, char **argv) -{ - if (consume_session(&argc, &argv)) - return 1; - if (argc > 0) { - printf("%s: Invalid number of arguments.\n", progname); - printf("Try '%s --help' for more information.\n", progname); - return 1; - } - return push_main(); -} - -/* atch kill — stop session */ -static int cmd_kill(int argc, char **argv) -{ - int force = 0; - - /* Accept -f / --force before or after the session name. */ - while (argc >= 1 && (strcmp(argv[0], "-f") == 0 || - strcmp(argv[0], "--force") == 0)) { - force = 1; - argc--; - argv++; - } - if (consume_session(&argc, &argv)) - return 1; - while (argc >= 1 && (strcmp(argv[0], "-f") == 0 || - strcmp(argv[0], "--force") == 0)) { - force = 1; - argc--; - argv++; - } - if (argc > 0) { - printf("%s: Invalid number of arguments.\n", progname); - printf("Try '%s --help' for more information.\n", progname); - return 1; - } - return kill_main(force); -} - -/* atch clear — truncate the on-disk session log */ -static int cmd_clear(int argc, char **argv) -{ - char log_path[600]; - int fd; - - if (argc > 0) { - if (consume_session(&argc, &argv)) - return 1; - } else { - const char *chain = getenv(SESSION_ENVVAR); - const char *last; - - if (!chain || !*chain) { - printf("%s: No session was specified.\n", progname); - printf("Try '%s --help' for more information.\n", - progname); - return 1; - } - last = strrchr(chain, ':'); - sockname = (char *)(last ? last + 1 : chain); - } - if (argc > 0) { - printf("%s: Invalid number of arguments.\n", progname); - printf("Try '%s --help' for more information.\n", progname); - return 1; - } - snprintf(log_path, sizeof(log_path), "%s.log", sockname); - fd = open(log_path, O_WRONLY | O_TRUNC); - if (fd >= 0) { - close(fd); - if (!quiet) - printf("%s: session '%s' log cleared\n", - progname, session_shortname()); - } else if (errno != ENOENT) { - printf("%s: %s: %s\n", progname, log_path, strerror(errno)); - return 1; - } - return 0; -} - -/* Scan log fd backward and return the byte offset to seek to before -** printing the last nlines lines of output. */ -static off_t find_tail_start(int fd, off_t size, int nlines) -{ - char buf[BUFSIZE]; - int count = 0; - off_t pos = size; - - while (pos > 0 && count <= nlines) { - off_t chunk = pos > (off_t)sizeof(buf) ? (off_t)sizeof(buf) : pos; - ssize_t n; - ssize_t i; - - pos -= chunk; - lseek(fd, pos, SEEK_SET); - n = read(fd, buf, (size_t)chunk); - if (n <= 0) - break; - for (i = n - 1; i >= 0; i--) { - if (buf[i] == '\n') { - if (++count > nlines) - return pos + i + 1; - } - } - } - return 0; -} - -/* atch tail [-f] [-n N] — print last N lines of session log */ -static int cmd_tail(int argc, char **argv) -{ - int follow = 0, nlines = 10; - char log_path[600]; - unsigned char rbuf[BUFSIZE]; - off_t size, start; - ssize_t n; - int fd; - - /* Parse -f and -n N (also -nN) before the session name */ - while (argc >= 1 && argv[0][0] == '-' && argv[0][1] != '\0') { - if (strcmp(argv[0], "-f") == 0) { - follow = 1; - argc--; - argv++; - } else if (strcmp(argv[0], "-n") == 0) { - if (argc < 2) { - printf("%s: -n requires an argument\n", progname); - printf("Try '%s --help' for more information.\n", - progname); - return 1; - } - nlines = atoi(argv[1]); - argc -= 2; - argv += 2; - } else if (strncmp(argv[0], "-n", 2) == 0 && - argv[0][2] != '\0') { - nlines = atoi(argv[0] + 2); - argc--; - argv++; - } else { - printf("%s: Invalid option '%s'\n", progname, argv[0]); - printf("Try '%s --help' for more information.\n", - progname); - return 1; - } - } - if (nlines < 1) - nlines = 1; - - if (consume_session(&argc, &argv)) - return 1; - if (argc > 0) { - printf("%s: Invalid number of arguments.\n", progname); - printf("Try '%s --help' for more information.\n", progname); - return 1; - } - - snprintf(log_path, sizeof(log_path), "%s.log", sockname); - fd = open(log_path, O_RDONLY); - if (fd < 0) { - if (errno == ENOENT) - printf("%s: no log for session '%s'\n", progname, - session_shortname()); - else - printf("%s: %s: %s\n", progname, log_path, - strerror(errno)); - return 1; - } - - size = lseek(fd, 0, SEEK_END); - if (size > 0) { - start = find_tail_start(fd, size, nlines); - lseek(fd, start, SEEK_SET); - while ((n = read(fd, rbuf, sizeof(rbuf))) > 0) - write(1, rbuf, (size_t)n); - } - - if (follow) { - signal(SIGPIPE, SIG_IGN); - for (;;) { - usleep(250000); - while ((n = read(fd, rbuf, sizeof(rbuf))) > 0) - write(1, rbuf, (size_t)n); - } - } - - close(fd); - return 0; -} - -static int cmd_rm(int argc, char **argv) -{ - int all = 0; - - if (argc >= 1 && strcmp(argv[0], "-a") == 0) { - all = 1; - argc--; - argv++; - } - - if (all) { - if (argc > 0) { - printf("%s: Invalid number of arguments.\n", progname); - printf("Try '%s --help' for more information.\n", - progname); - return 1; - } - return rm_main(1); - } - - if (consume_session(&argc, &argv)) - return 1; - if (argc > 0) { - printf("%s: Invalid number of arguments.\n", progname); - printf("Try '%s --help' for more information.\n", progname); - return 1; - } - return rm_main(0); -} - -/* Default: atch [cmd...] — attach-or-create */ -static int cmd_open(char *session, int argc, char **argv) -{ - sockname = session; - expand_sockname(); - if (parse_options(&argc, &argv)) - return 1; - argv = use_shell_if_no_cmd(argc, argv); - save_term(); - if (require_tty()) - return 1; - if (attach_main(1) != 0) { - if (errno == ECONNREFUSED || errno == ENOENT) { - int saved_errno = errno; - - replay_session_log(saved_errno); - if (saved_errno == ECONNREFUSED) - unlink(sockname); - if (master_main(argv, 1, 0) != 0) - return 1; - if (!quiet) - printf("%s: session '%s' created\n", progname, - session_shortname()); - } - return attach_main(0); - } - return 0; -} - -static void usage(void) -{ - printf(PACKAGE_NAME " - version %s, compiled on %s at %s.\n" - "Usage:\n" - " " PACKAGE_NAME " [ [command...]]" - "\t\tAttach to session or create it\n" - " " PACKAGE_NAME " [options] ...\n" - "\n" - "Commands:\n" - " attach " - "\t\t\tStrict attach (fail if session missing)\n" - " new [command...]" - "\tCreate session and attach\n" - " start [command...]" - "\tCreate session, detached\n" - " run [command...]" - "\tCreate session, master in foreground\n" - " push " - "\t\t\tPipe stdin into session\n" - " kill [-f] " - "\t\tStop session (SIGTERM then SIGKILL)\n" - " -f, --force\t\t\tSkip grace period, send SIGKILL immediately\n" - " clear []" - "\t\t\tTruncate the session log\n" - " tail [-f] [-n N] " - "\tPrint last N lines of session log\n" - " -f\t\t\t\tFollow log output\n" - " -n \t\t\tNumber of lines (default 10)\n" - " list [-a]\t\t\t\tList sessions (-a includes exited)\n" - " rm [-a] []" - "\t\tRemove stale/exited session(s)\n" - " -a\t\t\t\tRemove all stale and exited sessions\n" - " current\t\t\t\tPrint current session name\n" - "\n" - "Options:\n" - " -e \tSet detach character (default: ^\\)\n" - " -E\t\tDisable detach character\n" - " -r \tRedraw method: none | ctrl_l | winch\n" - " -R \tClear method: none | move\n" - " -z\t\tDisable suspend key\n" - " -q\t\tSuppress messages\n" - " -t\t\tDisable VT100 assumptions\n" - " -C \tLog cap: 0=disable, e.g. 128k, 4m (default 1m)\n" - "\nURL: " PACKAGE_URL "\n\n", - PACKAGE_VERSION, __DATE__, __TIME__); - exit(0); -} - -int main(int argc, char **argv) +int atch_cli_main(int argc, char **argv) { const char *cmd; @@ -802,7 +91,7 @@ int main(int argc, char **argv) if (c != 'e' && c != 'E' && c != 'r' && c != 'R' && c != 'z' && c != 'q' && c != 't' && c != 'C') break; - if (parse_options(&argc, &argv)) + if (atch_parse_options(&argc, &argv)) return 1; } if (argc < 1) @@ -841,7 +130,8 @@ int main(int argc, char **argv) sockname = *argv; ++argv; --argc; - expand_sockname(); + if (expand_sockname()) + return 1; if (mode == 'p') { if (argc > 0) { @@ -864,7 +154,7 @@ int main(int argc, char **argv) return kill_main(0); } - if (parse_options(&argc, &argv)) + if (atch_parse_options(&argc, &argv)) return 1; if (mode != 'a') argv = use_shell_if_no_cmd(argc, argv); @@ -915,29 +205,32 @@ int main(int argc, char **argv) ++argv; --argc; - if (is_cmd(cmd, "list", "l", "ls")) + switch (atch_resolve_command(cmd)) { + case ATCH_CMD_LIST: return cmd_list(argc, argv); - if (is_cmd(cmd, "current", NULL, NULL)) + case ATCH_CMD_CURRENT: return cmd_current(); - if (is_cmd(cmd, "attach", "a", NULL)) + case ATCH_CMD_ATTACH: return cmd_attach(argc, argv); - if (is_cmd(cmd, "new", "n", NULL)) + case ATCH_CMD_NEW: return cmd_new(argc, argv); - if (is_cmd(cmd, "start", "s", NULL)) + case ATCH_CMD_START: return cmd_start(argc, argv); - if (is_cmd(cmd, "run", NULL, NULL)) + case ATCH_CMD_RUN: return cmd_run(argc, argv); - if (is_cmd(cmd, "push", "p", NULL)) + case ATCH_CMD_PUSH: return cmd_push(argc, argv); - if (is_cmd(cmd, "kill", "k", NULL)) + case ATCH_CMD_KILL: return cmd_kill(argc, argv); - if (is_cmd(cmd, "clear", NULL, NULL)) + case ATCH_CMD_CLEAR: return cmd_clear(argc, argv); - if (is_cmd(cmd, "tail", NULL, NULL)) + case ATCH_CMD_TAIL: return cmd_tail(argc, argv); - if (is_cmd(cmd, "rm", NULL, NULL)) + case ATCH_CMD_RM: return cmd_rm(argc, argv); - - /* Smart default: treat first arg as session name → attach-or-create */ - return cmd_open((char *)cmd, argc, argv); + case ATCH_CMD_OPEN: + default: + /* Smart default: treat first arg as session name → attach-or-create */ + return cmd_open((char *)cmd, argc, argv); + } } diff --git a/atch_cli.h b/atch_cli.h new file mode 100644 index 0000000..e7ac532 --- /dev/null +++ b/atch_cli.h @@ -0,0 +1,6 @@ +#ifndef ATCH_CLI_H +#define ATCH_CLI_H + +int atch_cli_main(int argc, char **argv); + +#endif diff --git a/atch_cli_opts.c b/atch_cli_opts.c new file mode 100644 index 0000000..d553113 --- /dev/null +++ b/atch_cli_opts.c @@ -0,0 +1,140 @@ +#include "atch.h" +#include "atch_cli_opts.h" + +/* Parse a size string: bare number, or number with k/K (×1024) or m/M (×1048576). +** Writes result to *out. Returns 0 on success, 1 on error. */ +static int parse_size(const char *s, size_t *out) +{ + char *end; + unsigned long v; + + if (!s || !*s) + return 1; + v = strtoul(s, &end, 10); + if (end == s) + return 1; + if (*end == 'k' || *end == 'K') { + v *= 1024; + end++; + } else if (*end == 'm' || *end == 'M') { + v *= 1024 * 1024; + end++; + } + if (*end != '\0') + return 1; + *out = (size_t)v; + return 0; +} + +/* Parse option flags from argv/argc. Stops at '--' or a non-option argument. +** Returns 0 on success, 1 on error (message already printed). */ +int atch_parse_options(int *argc, char ***argv) +{ + while (*argc >= 1 && ***argv == '-') { + char *p; + + if (strcmp((*argv)[0], "--") == 0) { + ++(*argv); + --(*argc); + break; + } + + for (p = (*argv)[0] + 1; *p; ++p) { + if (*p == 'E') + detach_char = -1; + else if (*p == 'z') + no_suspend = 1; + else if (*p == 'q') + quiet = 1; + else if (*p == 't') + no_ansiterm = 1; + else if (*p == 'e') { + ++(*argv); + --(*argc); + if (*argc < 1) { + printf("%s: No escape character specified.\n", progname); + printf("Try '%s --help' for more information.\n", + progname); + return 1; + } + if ((*argv)[0][0] == '^' && (*argv)[0][1]) { + if ((*argv)[0][1] == '?') + detach_char = '\177'; + else + detach_char = (*argv)[0][1] & 037; + } else + detach_char = (*argv)[0][0]; + break; + } else if (*p == 'r') { + ++(*argv); + --(*argc); + if (*argc < 1) { + printf("%s: No redraw method specified.\n", progname); + printf("Try '%s --help' for more information.\n", + progname); + return 1; + } + if (strcmp((*argv)[0], "none") == 0) + redraw_method = REDRAW_NONE; + else if (strcmp((*argv)[0], "ctrl_l") == 0) + redraw_method = REDRAW_CTRL_L; + else if (strcmp((*argv)[0], "winch") == 0) + redraw_method = REDRAW_WINCH; + else { + printf("%s: Invalid redraw method specified.\n", progname); + printf("Try '%s --help' for more information.\n", + progname); + return 1; + } + break; + } else if (*p == 'R') { + ++(*argv); + --(*argc); + if (*argc < 1) { + printf("%s: No clear method specified.\n", progname); + printf("Try '%s --help' for more information.\n", + progname); + return 1; + } + if (strcmp((*argv)[0], "none") == 0) + clear_method = CLEAR_NONE; + else if (strcmp((*argv)[0], "move") == 0) + clear_method = CLEAR_MOVE; + else { + printf("%s: Invalid clear method specified.\n", progname); + printf("Try '%s --help' for more information.\n", + progname); + return 1; + } + break; + } else if (*p == 'C') { + ++(*argv); + --(*argc); + if (*argc < 1) { + printf("%s: No log size specified.\n", progname); + printf("Try '%s --help' for more information.\n", + progname); + return 1; + } + if (parse_size((*argv)[0], &log_max_size)) { + printf("%s: Invalid log size '%s'.\n", progname, + (*argv)[0]); + printf("Try '%s --help' for more information.\n", + progname); + return 1; + } + break; + } else { + printf("%s: Invalid option '-%c'\n", progname, *p); + printf("Try '%s --help' for more information.\n", progname); + return 1; + } + + if (*argc < 2) + break; + } + ++(*argv); + --(*argc); + } + return 0; +} diff --git a/atch_cli_opts.h b/atch_cli_opts.h new file mode 100644 index 0000000..45b481a --- /dev/null +++ b/atch_cli_opts.h @@ -0,0 +1,6 @@ +#ifndef ATCH_CLI_OPTS_H +#define ATCH_CLI_OPTS_H + +int atch_parse_options(int *argc, char ***argv); + +#endif diff --git a/atch_cli_runtime.c b/atch_cli_runtime.c new file mode 100644 index 0000000..fa2b5f2 --- /dev/null +++ b/atch_cli_runtime.c @@ -0,0 +1,498 @@ +#include "atch.h" +#include "atch_cli_opts.h" +#include "atch_session.h" + +/* Expand session name to full socket path in-place. */ +int expand_sockname(void) +{ + char *full = atch_expand_session_name_dup(sockname); + if (!full) { + printf("%s: out of memory\n", progname); + return 1; + } + sockname = full; + return 0; +} + +/* Return argv unchanged if argc > 0; otherwise return a {shell, NULL} argv. */ +char **use_shell_if_no_cmd(int argc, char **argv) +{ + static char *shell_argv[2]; + const char *shell; + struct passwd *pw; + + if (argc > 0) + return argv; + shell = getenv("SHELL"); + if (!shell || !*shell) { + pw = getpwuid(getuid()); + if (pw && pw->pw_shell && *pw->pw_shell) + shell = pw->pw_shell; + } + if (!shell || !*shell) + shell = "/bin/sh"; + shell_argv[0] = (char *)shell; + shell_argv[1] = NULL; + return shell_argv; +} + +/* Snapshot terminal settings; sets dont_have_tty if not a tty. */ +void save_term(void) +{ + if (tcgetattr(0, &orig_term) < 0) { + memset(&orig_term, 0, sizeof(struct termios)); + dont_have_tty = 1; + } +} + +/* Print error and return 1 if no tty is available. */ +int require_tty(void) +{ + if (dont_have_tty) { + printf("%s: attaching to a session requires a terminal.\n", + progname); + return 1; + } + return 0; +} + +/* Consume first arg as session name, expand it, advance argc/argv. */ +int consume_session(int *argc, char ***argv) +{ + if (*argc < 1) { + printf("%s: No session was specified.\n", progname); + printf("Try '%s --help' for more information.\n", progname); + return 1; + } + sockname = **argv; + ++(*argv); + --(*argc); + if (expand_sockname()) + return 1; + return 0; +} + +/* atch list [-a] */ +int cmd_list(int argc, char **argv) +{ + int show_all = 0; + + while (argc >= 1 && strcmp(argv[0], "-a") == 0) { + show_all = 1; + argc--; + argv++; + } + return list_main(show_all); +} + +/* atch current +** SESSION_ENVVAR holds the colon-separated ancestry chain, outermost first. +** A single (non-nested) session has no colon. */ +int cmd_current(void) +{ + const char *chain = getenv(SESSION_ENVVAR); + char *copy, *seg, *colon; + const char *name; + int first; + + if (!chain || !*chain) + return 1; + + copy = strdup(chain); + if (!copy) + return 1; + + first = 1; + seg = copy; + for (;;) { + colon = strchr(seg, ':'); + if (colon) + *colon = '\0'; + name = strrchr(seg, '/'); + if (!first) + printf(" > "); + printf("%s", name ? name + 1 : seg); + first = 0; + if (!colon) + break; + seg = colon + 1; + } + printf("\n"); + free(copy); + return 0; +} + +/* atch attach — strict attach, fail if missing */ +int cmd_attach(int argc, char **argv) +{ + if (atch_parse_options(&argc, &argv)) + return 1; + if (consume_session(&argc, &argv)) + return 1; + if (atch_parse_options(&argc, &argv)) + return 1; + if (argc > 0) { + printf("%s: Invalid number of arguments.\n", progname); + printf("Try '%s --help' for more information.\n", progname); + return 1; + } + save_term(); + if (require_tty()) + return 1; + return attach_main(0); +} + +/* atch new [cmd...] — create session and attach */ +int cmd_new(int argc, char **argv) +{ + if (atch_parse_options(&argc, &argv)) + return 1; + if (consume_session(&argc, &argv)) + return 1; + if (atch_parse_options(&argc, &argv)) + return 1; + argv = use_shell_if_no_cmd(argc, argv); + save_term(); + if (require_tty()) + return 1; + if (master_main(argv, 1, 0) != 0) + return 1; + if (!quiet) + printf("%s: session '%s' created\n", progname, + session_shortname()); + return attach_main(0); +} + +/* atch start [cmd...] — create detached */ +int cmd_start(int argc, char **argv) +{ + if (atch_parse_options(&argc, &argv)) + return 1; + if (consume_session(&argc, &argv)) + return 1; + if (atch_parse_options(&argc, &argv)) + return 1; + argv = use_shell_if_no_cmd(argc, argv); + save_term(); + if (master_main(argv, 0, 0) != 0) + return 1; + if (!quiet) + printf("%s: session '%s' started\n", progname, + session_shortname()); + return 0; +} + +/* atch run [cmd...] — create, master stays in foreground */ +int cmd_run(int argc, char **argv) +{ + if (atch_parse_options(&argc, &argv)) + return 1; + if (consume_session(&argc, &argv)) + return 1; + if (atch_parse_options(&argc, &argv)) + return 1; + argv = use_shell_if_no_cmd(argc, argv); + save_term(); + return master_main(argv, 0, 1); +} + +/* atch push — pipe stdin into session */ +int cmd_push(int argc, char **argv) +{ + if (consume_session(&argc, &argv)) + return 1; + if (argc > 0) { + printf("%s: Invalid number of arguments.\n", progname); + printf("Try '%s --help' for more information.\n", progname); + return 1; + } + return push_main(); +} + +/* atch kill — stop session */ +int cmd_kill(int argc, char **argv) +{ + int force = 0; + + /* Accept -f / --force before or after the session name. */ + while (argc >= 1 && (strcmp(argv[0], "-f") == 0 || + strcmp(argv[0], "--force") == 0)) { + force = 1; + argc--; + argv++; + } + if (consume_session(&argc, &argv)) + return 1; + while (argc >= 1 && (strcmp(argv[0], "-f") == 0 || + strcmp(argv[0], "--force") == 0)) { + force = 1; + argc--; + argv++; + } + if (argc > 0) { + printf("%s: Invalid number of arguments.\n", progname); + printf("Try '%s --help' for more information.\n", progname); + return 1; + } + return kill_main(force); +} + +/* atch clear — truncate the on-disk session log */ +int cmd_clear(int argc, char **argv) +{ + char log_path[600]; + int fd; + + if (argc > 0) { + if (consume_session(&argc, &argv)) + return 1; + } else { + const char *chain = getenv(SESSION_ENVVAR); + const char *last; + + if (!chain || !*chain) { + printf("%s: No session was specified.\n", progname); + printf("Try '%s --help' for more information.\n", + progname); + return 1; + } + last = strrchr(chain, ':'); + sockname = (char *)(last ? last + 1 : chain); + } + if (argc > 0) { + printf("%s: Invalid number of arguments.\n", progname); + printf("Try '%s --help' for more information.\n", progname); + return 1; + } + snprintf(log_path, sizeof(log_path), "%s.log", sockname); + fd = open(log_path, O_WRONLY | O_TRUNC); + if (fd >= 0) { + close(fd); + if (!quiet) + printf("%s: session '%s' log cleared\n", + progname, session_shortname()); + } else if (errno != ENOENT) { + printf("%s: %s: %s\n", progname, log_path, strerror(errno)); + return 1; + } + return 0; +} + +/* Scan log fd backward and return the byte offset to seek to before +** printing the last nlines lines of output. */ +static off_t find_tail_start(int fd, off_t size, int nlines) +{ + char buf[BUFSIZE]; + int count = 0; + off_t pos = size; + + while (pos > 0 && count <= nlines) { + off_t chunk = pos > (off_t)sizeof(buf) ? (off_t)sizeof(buf) : pos; + ssize_t n; + ssize_t i; + + pos -= chunk; + lseek(fd, pos, SEEK_SET); + n = read(fd, buf, (size_t)chunk); + if (n <= 0) + break; + for (i = n - 1; i >= 0; i--) { + if (buf[i] == '\n') { + if (++count > nlines) + return pos + i + 1; + } + } + } + return 0; +} + +/* atch tail [-f] [-n N] — print last N lines of session log */ +int cmd_tail(int argc, char **argv) +{ + int follow = 0, nlines = 10; + char log_path[600]; + unsigned char rbuf[BUFSIZE]; + off_t size, start; + ssize_t n; + int fd; + + /* Parse -f and -n N (also -nN) before the session name */ + while (argc >= 1 && argv[0][0] == '-' && argv[0][1] != '\0') { + if (strcmp(argv[0], "-f") == 0) { + follow = 1; + argc--; + argv++; + } else if (strcmp(argv[0], "-n") == 0) { + if (argc < 2) { + printf("%s: -n requires an argument\n", progname); + printf("Try '%s --help' for more information.\n", + progname); + return 1; + } + nlines = atoi(argv[1]); + argc -= 2; + argv += 2; + } else if (strncmp(argv[0], "-n", 2) == 0 && + argv[0][2] != '\0') { + nlines = atoi(argv[0] + 2); + argc--; + argv++; + } else { + printf("%s: Invalid option '%s'\n", progname, argv[0]); + printf("Try '%s --help' for more information.\n", + progname); + return 1; + } + } + if (nlines < 1) + nlines = 1; + + if (consume_session(&argc, &argv)) + return 1; + if (argc > 0) { + printf("%s: Invalid number of arguments.\n", progname); + printf("Try '%s --help' for more information.\n", progname); + return 1; + } + + snprintf(log_path, sizeof(log_path), "%s.log", sockname); + fd = open(log_path, O_RDONLY); + if (fd < 0) { + if (errno == ENOENT) + printf("%s: no log for session '%s'\n", progname, + session_shortname()); + else + printf("%s: %s: %s\n", progname, log_path, + strerror(errno)); + return 1; + } + + size = lseek(fd, 0, SEEK_END); + if (size > 0) { + start = find_tail_start(fd, size, nlines); + lseek(fd, start, SEEK_SET); + while ((n = read(fd, rbuf, sizeof(rbuf))) > 0) + write(1, rbuf, (size_t)n); + } + + if (follow) { + signal(SIGPIPE, SIG_IGN); + for (;;) { + usleep(250000); + while ((n = read(fd, rbuf, sizeof(rbuf))) > 0) + write(1, rbuf, (size_t)n); + } + } + + close(fd); + return 0; +} + +int cmd_rm(int argc, char **argv) +{ + int all = 0; + + if (argc >= 1 && strcmp(argv[0], "-a") == 0) { + all = 1; + argc--; + argv++; + } + + if (all) { + if (argc > 0) { + printf("%s: Invalid number of arguments.\n", progname); + printf("Try '%s --help' for more information.\n", + progname); + return 1; + } + return rm_main(1); + } + + if (consume_session(&argc, &argv)) + return 1; + if (argc > 0) { + printf("%s: Invalid number of arguments.\n", progname); + printf("Try '%s --help' for more information.\n", progname); + return 1; + } + return rm_main(0); +} + +/* Default: atch [cmd...] — attach-or-create */ +int cmd_open(char *session, int argc, char **argv) +{ + sockname = session; + if (expand_sockname()) + return 1; + if (atch_parse_options(&argc, &argv)) + return 1; + argv = use_shell_if_no_cmd(argc, argv); + save_term(); + if (require_tty()) + return 1; + if (attach_main(1) != 0) { + if (errno == ECONNREFUSED || errno == ENOENT) { + int saved_errno = errno; + + replay_session_log(saved_errno); + if (saved_errno == ECONNREFUSED) + unlink(sockname); + if (master_main(argv, 1, 0) != 0) + return 1; + if (!quiet) + printf("%s: session '%s' created\n", progname, + session_shortname()); + } + return attach_main(0); + } + return 0; +} + +void usage(void) +{ + printf(PACKAGE_NAME " - version %s, compiled on %s at %s.\n" + "Usage:\n" + " " PACKAGE_NAME " [ [command...]]" + "\t\tAttach to session or create it\n" + " " PACKAGE_NAME " [options] ...\n" + "\n" + "Commands:\n" + " attach " + "\t\t\tStrict attach (fail if session missing)\n" + " new [command...]" + "\tCreate session and attach\n" + " start [command...]" + "\tCreate session, detached\n" + " run [command...]" + "\tCreate session, master in foreground\n" + " push " + "\t\t\tPipe stdin into session\n" + " kill [-f] " + "\t\tStop session (SIGTERM then SIGKILL)\n" + " -f, --force\t\t\tSkip grace period, send SIGKILL immediately\n" + " clear []" + "\t\t\tTruncate the session log\n" + " tail [-f] [-n N] " + "\tPrint last N lines of session log\n" + " -f\t\t\t\tFollow log output\n" + " -n \t\t\tNumber of lines (default 10)\n" + " list [-a]\t\t\t\tList sessions (-a includes exited)\n" + " rm [-a] []" + "\t\tRemove stale/exited session(s)\n" + " -a\t\t\t\tRemove all stale and exited sessions\n" + " current\t\t\t\tPrint current session name\n" + "\n" + "Options:\n" + " -e \tSet detach character (default: ^\\)\n" + " -E\t\tDisable detach character\n" + " -r \tRedraw method: none | ctrl_l | winch\n" + " -R \tClear method: none | move\n" + " -z\t\tDisable suspend key\n" + " -q\t\tSuppress messages\n" + " -t\t\tDisable VT100 assumptions\n" + " -C \tLog cap: 0=disable, e.g. 128k, 4m (default 1m)\n" + "\nURL: " PACKAGE_URL "\n\n", + PACKAGE_VERSION, __DATE__, __TIME__); + exit(0); +} + diff --git a/atch_cli_runtime.h b/atch_cli_runtime.h new file mode 100644 index 0000000..08ef9e7 --- /dev/null +++ b/atch_cli_runtime.h @@ -0,0 +1,25 @@ +#ifndef ATCH_CLI_RUNTIME_H +#define ATCH_CLI_RUNTIME_H + +int expand_sockname(void); +char **use_shell_if_no_cmd(int argc, char **argv); +void save_term(void); +int require_tty(void); +int consume_session(int *argc, char ***argv); + +int cmd_list(int argc, char **argv); +int cmd_current(void); +int cmd_attach(int argc, char **argv); +int cmd_new(int argc, char **argv); +int cmd_start(int argc, char **argv); +int cmd_run(int argc, char **argv); +int cmd_push(int argc, char **argv); +int cmd_kill(int argc, char **argv); +int cmd_clear(int argc, char **argv); +int cmd_tail(int argc, char **argv); +int cmd_rm(int argc, char **argv); +int cmd_open(char *session, int argc, char **argv); + +void usage(void); + +#endif diff --git a/atch_cmd.c b/atch_cmd.c new file mode 100644 index 0000000..2912907 --- /dev/null +++ b/atch_cmd.c @@ -0,0 +1,36 @@ +#include + +#include "atch_cmd.h" + +static int is_cmd(const char *arg, const char *a, const char *b, const char *c) +{ + return (a && strcmp(arg, a) == 0) || (b && strcmp(arg, b) == 0) || + (c && strcmp(arg, c) == 0); +} + +enum atch_command atch_resolve_command(const char *cmd) +{ + if (is_cmd(cmd, "list", "l", "ls")) + return ATCH_CMD_LIST; + if (is_cmd(cmd, "current", NULL, NULL)) + return ATCH_CMD_CURRENT; + if (is_cmd(cmd, "attach", "a", NULL)) + return ATCH_CMD_ATTACH; + if (is_cmd(cmd, "new", "n", NULL)) + return ATCH_CMD_NEW; + if (is_cmd(cmd, "start", "s", NULL)) + return ATCH_CMD_START; + if (is_cmd(cmd, "run", NULL, NULL)) + return ATCH_CMD_RUN; + if (is_cmd(cmd, "push", "p", NULL)) + return ATCH_CMD_PUSH; + if (is_cmd(cmd, "kill", "k", NULL)) + return ATCH_CMD_KILL; + if (is_cmd(cmd, "clear", NULL, NULL)) + return ATCH_CMD_CLEAR; + if (is_cmd(cmd, "tail", NULL, NULL)) + return ATCH_CMD_TAIL; + if (is_cmd(cmd, "rm", NULL, NULL)) + return ATCH_CMD_RM; + return ATCH_CMD_OPEN; +} diff --git a/atch_cmd.h b/atch_cmd.h new file mode 100644 index 0000000..7550b43 --- /dev/null +++ b/atch_cmd.h @@ -0,0 +1,21 @@ +#ifndef ATCH_CMD_H +#define ATCH_CMD_H + +enum atch_command { + ATCH_CMD_OPEN = 0, + ATCH_CMD_LIST, + ATCH_CMD_CURRENT, + ATCH_CMD_ATTACH, + ATCH_CMD_NEW, + ATCH_CMD_START, + ATCH_CMD_RUN, + ATCH_CMD_PUSH, + ATCH_CMD_KILL, + ATCH_CMD_CLEAR, + ATCH_CMD_TAIL, + ATCH_CMD_RM, +}; + +enum atch_command atch_resolve_command(const char *cmd); + +#endif diff --git a/atch_paths.c b/atch_paths.c new file mode 100644 index 0000000..7ea2414 --- /dev/null +++ b/atch_paths.c @@ -0,0 +1,60 @@ +#include "atch.h" +#include "atch_paths.h" + +/* Returns the basename of the current session socket path. */ +const char *session_shortname(void) +{ + const char *p = strrchr(sockname, '/'); + return p ? p + 1 : sockname; +} + +/* Returns the directory where session sockets are stored. */ +void get_session_dir(char *buf, size_t size) +{ + const char *home = getenv("HOME"); + const char *base = strrchr(progname, '/'); + struct passwd *pw; + + base = base ? base + 1 : progname; + + /* If $HOME is unset or empty, try the passwd database. */ + if (!home || !*home) { + pw = getpwuid(getuid()); + if (pw && pw->pw_dir && *pw->pw_dir) + home = pw->pw_dir; + } + + /* Use $HOME only if it is set and not the root directory. */ + if (home && *home && strcmp(home, "/") != 0) + snprintf(buf, size, "%s/.cache/%s", home, base); + else + snprintf(buf, size, "/tmp/.%s-%d", base, (int)getuid()); +} + +/* +** Long-path helper for Unix socket operations: saves cwd, chdirs into the +** directory part of path, calls fn(basename), then restores cwd. +** Used by connect_socket and create_socket to handle paths > sun_path limit. +*/ +int socket_with_chdir(char *path, int (*fn)(char *)) +{ + char *slash = strrchr(path, '/'); + int dirfd, s; + + if (!slash) { + errno = ENAMETOOLONG; + return -1; + } + dirfd = open(".", O_RDONLY); + if (dirfd < 0) + return -1; + *slash = '\0'; + s = chdir(path) >= 0 ? fn(slash + 1) : -1; + *slash = '/'; + if (s >= 0 && fchdir(dirfd) < 0) { + close(s); + s = -1; + } + close(dirfd); + return s; +} diff --git a/atch_paths.h b/atch_paths.h new file mode 100644 index 0000000..3a96390 --- /dev/null +++ b/atch_paths.h @@ -0,0 +1,8 @@ +#ifndef ATCH_PATHS_H +#define ATCH_PATHS_H + +const char *session_shortname(void); +void get_session_dir(char *buf, size_t size); +int socket_with_chdir(char *path, int (*fn)(char *)); + +#endif diff --git a/atch_session.c b/atch_session.c new file mode 100644 index 0000000..073a3e9 --- /dev/null +++ b/atch_session.c @@ -0,0 +1,30 @@ +#include "atch.h" +#include "atch_session.h" + +char *atch_expand_session_name_dup(const char *name) +{ + char dir[512]; + size_t fulllen; + char *full; + char *slash; + + if (!name || !*name) + return NULL; + if (strchr(name, '/') != NULL) + return strdup(name); + + get_session_dir(dir, sizeof(dir)); + slash = strrchr(dir, '/'); + if (slash) { + *slash = '\0'; + mkdir(dir, 0700); + *slash = '/'; + } + mkdir(dir, 0700); + fulllen = strlen(dir) + 1 + strlen(name); + full = malloc(fulllen + 1); + if (!full) + return NULL; + snprintf(full, fulllen + 1, "%s/%s", dir, name); + return full; +} diff --git a/atch_session.h b/atch_session.h new file mode 100644 index 0000000..749fa68 --- /dev/null +++ b/atch_session.h @@ -0,0 +1,11 @@ +#ifndef ATCH_SESSION_H +#define ATCH_SESSION_H + +/* Resolve a user-provided session name to full socket path. + * - If name contains '/', returns strdup(name) + * - Otherwise returns "/" + * Caller owns returned memory. + */ +char *atch_expand_session_name_dup(const char *name); + +#endif diff --git a/main.c b/main.c new file mode 100644 index 0000000..9d3a8ff --- /dev/null +++ b/main.c @@ -0,0 +1,6 @@ +#include "atch_cli.h" + +int main(int argc, char **argv) +{ + return atch_cli_main(argc, argv); +} diff --git a/makefile b/makefile index eb43a61..4e31459 100644 --- a/makefile +++ b/makefile @@ -11,8 +11,9 @@ else STATIC_FLAG = -static endif -OBJ = attach.o master.o atch.o -SRC = attach.c master.c atch.c +LIBOBJ = attach.o master.o atch.o atch_paths.o atch_session.o atch_cmd.o atch_cli_opts.o atch_cli_runtime.o +OBJ = main.o $(LIBOBJ) +SRC = main.c attach.c master.c atch.c atch_paths.c atch_session.c atch_cmd.c atch_cli_opts.c atch_cli_runtime.c IMAGE = atch-builder BUILDDIR ?= . @@ -20,8 +21,13 @@ BUILDDIR ?= . archs = amd64 arm64 arch ?= $(shell arch) -atch: $(OBJ) - $(CC) -o $(BUILDDIR)/$@ $(STATIC_FLAG) $(LDFLAGS) $(OBJ) $(LIBS) +.DEFAULT_GOAL := atch + +libatch.a: $(LIBOBJ) + ar rcs $@ $(LIBOBJ) + +atch: main.o libatch.a + $(CC) -o $(BUILDDIR)/$@ $(STATIC_FLAG) $(LDFLAGS) main.o libatch.a $(LIBS) atch.1.md: README.md scripts/readme2man.sh bash scripts/readme2man.sh $< > $@ @@ -32,7 +38,7 @@ atch.1: atch.1.md man: atch.1 clean: - rm -f atch $(OBJ) *.1.md *.c~ + rm -f atch libatch.a $(OBJ) *.1.md *.c~ .PHONY: fmt fmt: @@ -43,9 +49,15 @@ fmt-all: $(MAKE) fmt SRCS="*.c" +main.o: ./main.c ./atch_cli.h attach.o: ./attach.c ./atch.h config.h master.o: ./master.c ./atch.h config.h -atch.o: ./atch.c ./atch.h config.h +atch.o: ./atch.c ./atch.h config.h ./atch_cli.h ./atch_paths.h ./atch_session.h ./atch_cmd.h ./atch_cli_opts.h ./atch_cli_runtime.h +atch_paths.o: ./atch_paths.c ./atch.h config.h ./atch_paths.h +atch_session.o: ./atch_session.c ./atch.h config.h ./atch_session.h +atch_cmd.o: ./atch_cmd.c ./atch_cmd.h +atch_cli_opts.o: ./atch_cli_opts.c ./atch.h config.h ./atch_cli_opts.h +atch_cli_runtime.o: ./atch_cli_runtime.c ./atch.h config.h ./atch_cli_opts.h ./atch_session.h .PHONY: build-image build-image: