From 4fed179fac98f9b2b02978b2df13999099fe3bb8 Mon Sep 17 00:00:00 2001 From: daywiss Date: Thu, 26 Mar 2026 16:33:59 +0000 Subject: [PATCH 1/8] refactor: split cli entrypoint and build internal libatch.a --- README.md | 6 ++++++ atch.c | 2 +- atch_cli.h | 6 ++++++ findings.md | 12 ++++++++++++ main.c | 6 ++++++ makefile | 19 +++++++++++++------ progress.md | 14 ++++++++++++++ task_plan.md | 12 ++++++++++++ 8 files changed, 70 insertions(+), 7 deletions(-) create mode 100644 atch_cli.h create mode 100644 findings.md create mode 100644 main.c create mode 100644 progress.md create mode 100644 task_plan.md diff --git a/README.md b/README.md index 3d7910a..6ffb6c6 100644 --- a/README.md +++ b/README.md @@ -95,6 +95,12 @@ is the right default. make ``` +Build just the internal static library artifact: + +```sh +make libatch.a +``` + ## Usage ``` diff --git a/atch.c b/atch.c index 9a516de..9f27b5b 100644 --- a/atch.c +++ b/atch.c @@ -766,7 +766,7 @@ static void usage(void) exit(0); } -int main(int argc, char **argv) +int atch_cli_main(int argc, char **argv) { const char *cmd; 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/findings.md b/findings.md new file mode 100644 index 0000000..dd22ce8 --- /dev/null +++ b/findings.md @@ -0,0 +1,12 @@ +# findings.md + +## Findings +- Lowest-risk path to "library" was to separate entrypoint from runtime/CLI logic without changing behavior. +- Renaming `main` in `atch.c` to `atch_cli_main` and adding a tiny `main.c` wrapper enables reusable linkage. +- Adding `libatch.a` as an explicit build artifact works with existing object layout. +- Introducing `libatch.a` before `atch` in Make changed default target implicitly; fixed via `.DEFAULT_GOAL := atch`. + +## Decisions +- Keep core behavior unchanged; avoid moving large code blocks in first refactor. +- Preserve current CLI parser/dispatcher in `atch.c` for now. +- Treat this as phase 1 internal modularization toward future library split. 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..ab8b089 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 +OBJ = main.o $(LIBOBJ) +SRC = main.c attach.c master.c atch.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,10 @@ 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 .PHONY: build-image build-image: diff --git a/progress.md b/progress.md new file mode 100644 index 0000000..c37f714 --- /dev/null +++ b/progress.md @@ -0,0 +1,14 @@ +# progress.md + +- Branched from `main` to `feature/lib-refactor`. +- Renamed `main` in `atch.c` -> `atch_cli_main`. +- Added `atch_cli.h` declaration and new `main.c` thin frontend. +- Updated Makefile: + - added `libatch.a` target from existing runtime objects + - linked `atch` as `main.o + libatch.a` + - set `.DEFAULT_GOAL := atch` +- Updated README build section with `make libatch.a`. +- Verified build and smoke: + - `make` + - `atch --version` + - `atch list` diff --git a/task_plan.md b/task_plan.md new file mode 100644 index 0000000..ee9a2ca --- /dev/null +++ b/task_plan.md @@ -0,0 +1,12 @@ +# task_plan.md + +## Goal +Refactor `atch` into an internal library-oriented layout while maintaining existing behavior. + +## Phases +| Phase | Description | Status | Success Criteria | +|---|---|---|---| +| 1 | Entrypoint separation + library artifact | complete | `main.c` thin CLI wrapper, `libatch.a` builds, `atch` behavior unchanged in smoke checks | +| 2 | Extract internal modules (path/session/control parsing helpers) | todo | Reduced coupling in `atch.c`; same CLI behavior | +| 3 | Public/internal API shaping | todo | Stable internal headers for reuse by future frontends | +| 4 | Validation/docs | in_progress | README and make usage updated; additional tests pending | From e6c8baa01a8212bc561b716ed07eefb18fd08983 Mon Sep 17 00:00:00 2001 From: daywiss Date: Thu, 26 Mar 2026 16:37:42 +0000 Subject: [PATCH 2/8] refactor: extract session name resolution into internal module --- atch.c | 39 +++++++++++++++------------------------ atch_session.c | 30 ++++++++++++++++++++++++++++++ atch_session.h | 11 +++++++++++ findings.md | 6 +++++- makefile | 7 ++++--- progress.md | 6 ++++++ 6 files changed, 71 insertions(+), 28 deletions(-) create mode 100644 atch_session.c create mode 100644 atch_session.h diff --git a/atch.c b/atch.c index 9f27b5b..8597379 100644 --- a/atch.c +++ b/atch.c @@ -1,4 +1,5 @@ #include "atch.h" +#include "atch_session.h" /* Env-var name string, computed from progname at startup. */ const char *session_envvar; @@ -254,29 +255,16 @@ static int parse_options(int *argc, char ***argv) return 0; } -/* Expand a bare session name to its full socket path in-place. */ -static void expand_sockname(void) +/* Expand session name to full socket path in-place. */ +static int 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); + 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. */ @@ -332,7 +320,8 @@ static int consume_session(int *argc, char ***argv) sockname = **argv; ++(*argv); --(*argc); - expand_sockname(); + if (expand_sockname()) + return 1; return 0; } @@ -693,7 +682,8 @@ static int cmd_rm(int argc, char **argv) static int cmd_open(char *session, int argc, char **argv) { sockname = session; - expand_sockname(); + if (expand_sockname()) + return 1; if (parse_options(&argc, &argv)) return 1; argv = use_shell_if_no_cmd(argc, argv); @@ -841,7 +831,8 @@ int atch_cli_main(int argc, char **argv) sockname = *argv; ++argv; --argc; - expand_sockname(); + if (expand_sockname()) + return 1; if (mode == 'p') { if (argc > 0) { 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/findings.md b/findings.md index dd22ce8..da896a0 100644 --- a/findings.md +++ b/findings.md @@ -5,8 +5,12 @@ - Renaming `main` in `atch.c` to `atch_cli_main` and adding a tiny `main.c` wrapper enables reusable linkage. - Adding `libatch.a` as an explicit build artifact works with existing object layout. - Introducing `libatch.a` before `atch` in Make changed default target implicitly; fixed via `.DEFAULT_GOAL := atch`. +- Session name/path expansion was a good first reusable extraction target because it had clear boundaries and minimal side effects. ## Decisions - Keep core behavior unchanged; avoid moving large code blocks in first refactor. - Preserve current CLI parser/dispatcher in `atch.c` for now. -- Treat this as phase 1 internal modularization toward future library split. +- Treat this as phase 1+2 internal modularization toward future library split. + +## Notes +- `atch_expand_session_name_dup` now centralizes bare-name expansion and cache-dir creation behavior for future frontends. diff --git a/makefile b/makefile index ab8b089..f001406 100644 --- a/makefile +++ b/makefile @@ -11,9 +11,9 @@ else STATIC_FLAG = -static endif -LIBOBJ = attach.o master.o atch.o +LIBOBJ = attach.o master.o atch.o atch_session.o OBJ = main.o $(LIBOBJ) -SRC = main.c attach.c master.c atch.c +SRC = main.c attach.c master.c atch.c atch_session.c IMAGE = atch-builder BUILDDIR ?= . @@ -52,7 +52,8 @@ fmt-all: 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_cli.h +atch.o: ./atch.c ./atch.h config.h ./atch_cli.h ./atch_session.h +atch_session.o: ./atch_session.c ./atch.h config.h ./atch_session.h .PHONY: build-image build-image: diff --git a/progress.md b/progress.md index c37f714..925853a 100644 --- a/progress.md +++ b/progress.md @@ -12,3 +12,9 @@ - `make` - `atch --version` - `atch list` + +## Phase 2 incremental extraction +- Added `atch_session.c/.h` internal module with reusable session-name expansion (`atch_expand_session_name_dup`). +- Replaced in-file session expansion logic in `atch.c` with module call. +- Added `atch_session.o` to `libatch.a` linkage. +- Rebuilt and re-smoke-tested CLI (`--version`, `list`, `current` outside session rc=1). From 5548b903de839f1165bf5302bb903a508b005796 Mon Sep 17 00:00:00 2001 From: daywiss Date: Thu, 26 Mar 2026 16:41:44 +0000 Subject: [PATCH 3/8] refactor: extract command alias resolution into module --- atch.c | 32 ++++++++++++++++++-------------- atch_cmd.c | 36 ++++++++++++++++++++++++++++++++++++ atch_cmd.h | 21 +++++++++++++++++++++ findings.md | 4 +++- makefile | 7 ++++--- progress.md | 6 ++++++ 6 files changed, 88 insertions(+), 18 deletions(-) create mode 100644 atch_cmd.c create mode 100644 atch_cmd.h diff --git a/atch.c b/atch.c index 8597379..86cc6e9 100644 --- a/atch.c +++ b/atch.c @@ -1,4 +1,5 @@ #include "atch.h" +#include "atch_cmd.h" #include "atch_session.h" /* Env-var name string, computed from progname at startup. */ @@ -906,29 +907,32 @@ int atch_cli_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_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/findings.md b/findings.md index da896a0..251ac9c 100644 --- a/findings.md +++ b/findings.md @@ -6,11 +6,13 @@ - Adding `libatch.a` as an explicit build artifact works with existing object layout. - Introducing `libatch.a` before `atch` in Make changed default target implicitly; fixed via `.DEFAULT_GOAL := atch`. - Session name/path expansion was a good first reusable extraction target because it had clear boundaries and minimal side effects. +- Command alias matching/dispatch is another clean extraction point that reduces CLI frontend coupling. ## Decisions - Keep core behavior unchanged; avoid moving large code blocks in first refactor. -- Preserve current CLI parser/dispatcher in `atch.c` for now. +- Preserve current CLI parser/dispatcher in `atch.c` while progressively extracting reusable modules. - Treat this as phase 1+2 internal modularization toward future library split. ## Notes - `atch_expand_session_name_dup` now centralizes bare-name expansion and cache-dir creation behavior for future frontends. +- `atch_resolve_command` centralizes alias policy (`list/l/ls`, `attach/a`, etc.) for reuse outside the CLI binary. diff --git a/makefile b/makefile index f001406..658b113 100644 --- a/makefile +++ b/makefile @@ -11,9 +11,9 @@ else STATIC_FLAG = -static endif -LIBOBJ = attach.o master.o atch.o atch_session.o +LIBOBJ = attach.o master.o atch.o atch_session.o atch_cmd.o OBJ = main.o $(LIBOBJ) -SRC = main.c attach.c master.c atch.c atch_session.c +SRC = main.c attach.c master.c atch.c atch_session.c atch_cmd.c IMAGE = atch-builder BUILDDIR ?= . @@ -52,8 +52,9 @@ fmt-all: 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_cli.h ./atch_session.h +atch.o: ./atch.c ./atch.h config.h ./atch_cli.h ./atch_session.h ./atch_cmd.h atch_session.o: ./atch_session.c ./atch.h config.h ./atch_session.h +atch_cmd.o: ./atch_cmd.c ./atch_cmd.h .PHONY: build-image build-image: diff --git a/progress.md b/progress.md index 925853a..ec35e5c 100644 --- a/progress.md +++ b/progress.md @@ -18,3 +18,9 @@ - Replaced in-file session expansion logic in `atch.c` with module call. - Added `atch_session.o` to `libatch.a` linkage. - Rebuilt and re-smoke-tested CLI (`--version`, `list`, `current` outside session rc=1). + +## Phase 2b incremental extraction +- Added `atch_cmd.c/.h` for command alias resolution (`atch_resolve_command`). +- Replaced command dispatch `if` chain in `atch.c` with enum+switch using resolver. +- Added `atch_cmd.o` to library build. +- Rebuilt and smoke-verified (`--version`, `list`). From c50301457adec2308ec8d0084228d68314443694 Mon Sep 17 00:00:00 2001 From: daywiss Date: Thu, 26 Mar 2026 16:51:43 +0000 Subject: [PATCH 4/8] refactor: extract cli option parsing into module --- atch.c | 176 ++++-------------------------------------------- atch_cli_opts.c | 140 ++++++++++++++++++++++++++++++++++++++ atch_cli_opts.h | 6 ++ findings.md | 4 +- makefile | 7 +- progress.md | 5 ++ 6 files changed, 170 insertions(+), 168 deletions(-) create mode 100644 atch_cli_opts.c create mode 100644 atch_cli_opts.h diff --git a/atch.c b/atch.c index 86cc6e9..3264c89 100644 --- a/atch.c +++ b/atch.c @@ -1,5 +1,6 @@ #include "atch.h" #include "atch_cmd.h" +#include "atch_cli_opts.h" #include "atch_session.h" /* Env-var name string, computed from progname at startup. */ @@ -110,152 +111,6 @@ 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 session name to full socket path in-place. */ static int expand_sockname(void) { @@ -326,13 +181,6 @@ static int consume_session(int *argc, char ***argv) 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) { @@ -386,11 +234,11 @@ static int cmd_current(void) /* atch attach — strict attach, fail if missing */ static int cmd_attach(int argc, char **argv) { - if (parse_options(&argc, &argv)) + if (atch_parse_options(&argc, &argv)) return 1; if (consume_session(&argc, &argv)) return 1; - if (parse_options(&argc, &argv)) + if (atch_parse_options(&argc, &argv)) return 1; if (argc > 0) { printf("%s: Invalid number of arguments.\n", progname); @@ -406,11 +254,11 @@ static int cmd_attach(int argc, char **argv) /* atch new [cmd...] — create session and attach */ static int cmd_new(int argc, char **argv) { - if (parse_options(&argc, &argv)) + if (atch_parse_options(&argc, &argv)) return 1; if (consume_session(&argc, &argv)) return 1; - if (parse_options(&argc, &argv)) + if (atch_parse_options(&argc, &argv)) return 1; argv = use_shell_if_no_cmd(argc, argv); save_term(); @@ -427,11 +275,11 @@ static int cmd_new(int argc, char **argv) /* atch start [cmd...] — create detached */ static int cmd_start(int argc, char **argv) { - if (parse_options(&argc, &argv)) + if (atch_parse_options(&argc, &argv)) return 1; if (consume_session(&argc, &argv)) return 1; - if (parse_options(&argc, &argv)) + if (atch_parse_options(&argc, &argv)) return 1; argv = use_shell_if_no_cmd(argc, argv); save_term(); @@ -446,11 +294,11 @@ static int cmd_start(int argc, char **argv) /* atch run [cmd...] — create, master stays in foreground */ static int cmd_run(int argc, char **argv) { - if (parse_options(&argc, &argv)) + if (atch_parse_options(&argc, &argv)) return 1; if (consume_session(&argc, &argv)) return 1; - if (parse_options(&argc, &argv)) + if (atch_parse_options(&argc, &argv)) return 1; argv = use_shell_if_no_cmd(argc, argv); save_term(); @@ -685,7 +533,7 @@ static int cmd_open(char *session, int argc, char **argv) sockname = session; if (expand_sockname()) return 1; - if (parse_options(&argc, &argv)) + if (atch_parse_options(&argc, &argv)) return 1; argv = use_shell_if_no_cmd(argc, argv); save_term(); @@ -793,7 +641,7 @@ int atch_cli_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) @@ -856,7 +704,7 @@ int atch_cli_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); 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/findings.md b/findings.md index 251ac9c..bc0b2c3 100644 --- a/findings.md +++ b/findings.md @@ -7,6 +7,7 @@ - Introducing `libatch.a` before `atch` in Make changed default target implicitly; fixed via `.DEFAULT_GOAL := atch`. - Session name/path expansion was a good first reusable extraction target because it had clear boundaries and minimal side effects. - Command alias matching/dispatch is another clean extraction point that reduces CLI frontend coupling. +- Option parsing is a third good extraction target because it is self-contained and shared by multiple command paths. ## Decisions - Keep core behavior unchanged; avoid moving large code blocks in first refactor. @@ -14,5 +15,6 @@ - Treat this as phase 1+2 internal modularization toward future library split. ## Notes -- `atch_expand_session_name_dup` now centralizes bare-name expansion and cache-dir creation behavior for future frontends. +- `atch_expand_session_name_dup` centralizes bare-name expansion and cache-dir creation behavior for future frontends. - `atch_resolve_command` centralizes alias policy (`list/l/ls`, `attach/a`, etc.) for reuse outside the CLI binary. +- `atch_parse_options` now centralizes option semantics and validation, reducing direct coupling in `atch.c`. diff --git a/makefile b/makefile index 658b113..5b4301d 100644 --- a/makefile +++ b/makefile @@ -11,9 +11,9 @@ else STATIC_FLAG = -static endif -LIBOBJ = attach.o master.o atch.o atch_session.o atch_cmd.o +LIBOBJ = attach.o master.o atch.o atch_session.o atch_cmd.o atch_cli_opts.o OBJ = main.o $(LIBOBJ) -SRC = main.c attach.c master.c atch.c atch_session.c atch_cmd.c +SRC = main.c attach.c master.c atch.c atch_session.c atch_cmd.c atch_cli_opts.c IMAGE = atch-builder BUILDDIR ?= . @@ -52,9 +52,10 @@ fmt-all: 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_cli.h ./atch_session.h ./atch_cmd.h +atch.o: ./atch.c ./atch.h config.h ./atch_cli.h ./atch_session.h ./atch_cmd.h ./atch_cli_opts.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 .PHONY: build-image build-image: diff --git a/progress.md b/progress.md index ec35e5c..9a8a9cc 100644 --- a/progress.md +++ b/progress.md @@ -23,4 +23,9 @@ - Added `atch_cmd.c/.h` for command alias resolution (`atch_resolve_command`). - Replaced command dispatch `if` chain in `atch.c` with enum+switch using resolver. - Added `atch_cmd.o` to library build. + +## Phase 2c incremental extraction +- Added `atch_cli_opts.c/.h` for option parsing (`atch_parse_options`) and log-size parsing. +- Removed parse-size/parse-options implementation from `atch.c`; now uses module API. +- Added `atch_cli_opts.o` to `libatch.a` linkage. - Rebuilt and smoke-verified (`--version`, `list`). From 4c11a5acc5faf1342ebde4467bc5195137826615 Mon Sep 17 00:00:00 2001 From: daywiss Date: Thu, 26 Mar 2026 16:55:54 +0000 Subject: [PATCH 5/8] refactor: move cli command handlers into runtime module --- atch.c | 495 +------------------------------------------- atch_cli_runtime.c | 498 +++++++++++++++++++++++++++++++++++++++++++++ atch_cli_runtime.h | 25 +++ findings.md | 8 +- makefile | 7 +- progress.md | 7 +- 6 files changed, 539 insertions(+), 501 deletions(-) create mode 100644 atch_cli_runtime.c create mode 100644 atch_cli_runtime.h diff --git a/atch.c b/atch.c index 3264c89..ccf0d0a 100644 --- a/atch.c +++ b/atch.c @@ -1,6 +1,7 @@ #include "atch.h" #include "atch_cmd.h" #include "atch_cli_opts.h" +#include "atch_cli_runtime.h" #include "atch_session.h" /* Env-var name string, computed from progname at startup. */ @@ -111,500 +112,6 @@ int no_ansiterm = 0; struct termios orig_term; int dont_have_tty; -/* Expand session name to full socket path in-place. */ -static 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. */ -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); - if (expand_sockname()) - return 1; - return 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 (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 */ -static 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 */ -static 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 */ -static 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 */ -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; - 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; -} - -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 atch_cli_main(int argc, char **argv) { const char *cmd; 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/findings.md b/findings.md index bc0b2c3..32dc8a3 100644 --- a/findings.md +++ b/findings.md @@ -8,13 +8,15 @@ - Session name/path expansion was a good first reusable extraction target because it had clear boundaries and minimal side effects. - Command alias matching/dispatch is another clean extraction point that reduces CLI frontend coupling. - Option parsing is a third good extraction target because it is self-contained and shared by multiple command paths. +- Moving command handlers into a runtime module gives clearer separation between CLI orchestration (`atch.c`) and command behavior implementation. ## Decisions - Keep core behavior unchanged; avoid moving large code blocks in first refactor. -- Preserve current CLI parser/dispatcher in `atch.c` while progressively extracting reusable modules. -- Treat this as phase 1+2 internal modularization toward future library split. +- Preserve legacy CLI semantics while progressively extracting reusable modules. +- Treat this as phase 1+2 internal modularization toward future library split and richer frontends. ## Notes - `atch_expand_session_name_dup` centralizes bare-name expansion and cache-dir creation behavior for future frontends. - `atch_resolve_command` centralizes alias policy (`list/l/ls`, `attach/a`, etc.) for reuse outside the CLI binary. -- `atch_parse_options` now centralizes option semantics and validation, reducing direct coupling in `atch.c`. +- `atch_parse_options` centralizes option semantics and validation, reducing direct coupling in `atch.c`. +- `atch_cli_runtime` now encapsulates command handlers and shared runtime helpers for potential alternate CLIs/managers. diff --git a/makefile b/makefile index 5b4301d..b1619d4 100644 --- a/makefile +++ b/makefile @@ -11,9 +11,9 @@ else STATIC_FLAG = -static endif -LIBOBJ = attach.o master.o atch.o atch_session.o atch_cmd.o atch_cli_opts.o +LIBOBJ = attach.o master.o atch.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_session.c atch_cmd.c atch_cli_opts.c +SRC = main.c attach.c master.c atch.c atch_session.c atch_cmd.c atch_cli_opts.c atch_cli_runtime.c IMAGE = atch-builder BUILDDIR ?= . @@ -52,10 +52,11 @@ fmt-all: 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_cli.h ./atch_session.h ./atch_cmd.h ./atch_cli_opts.h +atch.o: ./atch.c ./atch.h config.h ./atch_cli.h ./atch_session.h ./atch_cmd.h ./atch_cli_opts.h ./atch_cli_runtime.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: diff --git a/progress.md b/progress.md index 9a8a9cc..3a55cb7 100644 --- a/progress.md +++ b/progress.md @@ -28,4 +28,9 @@ - Added `atch_cli_opts.c/.h` for option parsing (`atch_parse_options`) and log-size parsing. - Removed parse-size/parse-options implementation from `atch.c`; now uses module API. - Added `atch_cli_opts.o` to `libatch.a` linkage. -- Rebuilt and smoke-verified (`--version`, `list`). + +## Phase 2d incremental extraction +- Added `atch_cli_runtime.c/.h` and moved command handlers (`cmd_*`) plus shared CLI runtime helpers (`consume_session`, `save_term`, etc.) out of `atch.c`. +- `atch.c` now focuses on process globals, env/session dir primitives, legacy+new dispatch orchestration. +- Added `atch_cli_runtime.o` to `libatch.a` linkage. +- Rebuilt and smoke-verified (`--version`, `list`, `current` outside session rc=1). From 69aed6efce4ae0848a0ec266887f6e76123c5b62 Mon Sep 17 00:00:00 2001 From: daywiss Date: Thu, 26 Mar 2026 17:00:59 +0000 Subject: [PATCH 6/8] chore: drop planning artifacts from refactor branch --- findings.md | 22 ---------------------- progress.md | 36 ------------------------------------ task_plan.md | 12 ------------ 3 files changed, 70 deletions(-) delete mode 100644 findings.md delete mode 100644 progress.md delete mode 100644 task_plan.md diff --git a/findings.md b/findings.md deleted file mode 100644 index 32dc8a3..0000000 --- a/findings.md +++ /dev/null @@ -1,22 +0,0 @@ -# findings.md - -## Findings -- Lowest-risk path to "library" was to separate entrypoint from runtime/CLI logic without changing behavior. -- Renaming `main` in `atch.c` to `atch_cli_main` and adding a tiny `main.c` wrapper enables reusable linkage. -- Adding `libatch.a` as an explicit build artifact works with existing object layout. -- Introducing `libatch.a` before `atch` in Make changed default target implicitly; fixed via `.DEFAULT_GOAL := atch`. -- Session name/path expansion was a good first reusable extraction target because it had clear boundaries and minimal side effects. -- Command alias matching/dispatch is another clean extraction point that reduces CLI frontend coupling. -- Option parsing is a third good extraction target because it is self-contained and shared by multiple command paths. -- Moving command handlers into a runtime module gives clearer separation between CLI orchestration (`atch.c`) and command behavior implementation. - -## Decisions -- Keep core behavior unchanged; avoid moving large code blocks in first refactor. -- Preserve legacy CLI semantics while progressively extracting reusable modules. -- Treat this as phase 1+2 internal modularization toward future library split and richer frontends. - -## Notes -- `atch_expand_session_name_dup` centralizes bare-name expansion and cache-dir creation behavior for future frontends. -- `atch_resolve_command` centralizes alias policy (`list/l/ls`, `attach/a`, etc.) for reuse outside the CLI binary. -- `atch_parse_options` centralizes option semantics and validation, reducing direct coupling in `atch.c`. -- `atch_cli_runtime` now encapsulates command handlers and shared runtime helpers for potential alternate CLIs/managers. diff --git a/progress.md b/progress.md deleted file mode 100644 index 3a55cb7..0000000 --- a/progress.md +++ /dev/null @@ -1,36 +0,0 @@ -# progress.md - -- Branched from `main` to `feature/lib-refactor`. -- Renamed `main` in `atch.c` -> `atch_cli_main`. -- Added `atch_cli.h` declaration and new `main.c` thin frontend. -- Updated Makefile: - - added `libatch.a` target from existing runtime objects - - linked `atch` as `main.o + libatch.a` - - set `.DEFAULT_GOAL := atch` -- Updated README build section with `make libatch.a`. -- Verified build and smoke: - - `make` - - `atch --version` - - `atch list` - -## Phase 2 incremental extraction -- Added `atch_session.c/.h` internal module with reusable session-name expansion (`atch_expand_session_name_dup`). -- Replaced in-file session expansion logic in `atch.c` with module call. -- Added `atch_session.o` to `libatch.a` linkage. -- Rebuilt and re-smoke-tested CLI (`--version`, `list`, `current` outside session rc=1). - -## Phase 2b incremental extraction -- Added `atch_cmd.c/.h` for command alias resolution (`atch_resolve_command`). -- Replaced command dispatch `if` chain in `atch.c` with enum+switch using resolver. -- Added `atch_cmd.o` to library build. - -## Phase 2c incremental extraction -- Added `atch_cli_opts.c/.h` for option parsing (`atch_parse_options`) and log-size parsing. -- Removed parse-size/parse-options implementation from `atch.c`; now uses module API. -- Added `atch_cli_opts.o` to `libatch.a` linkage. - -## Phase 2d incremental extraction -- Added `atch_cli_runtime.c/.h` and moved command handlers (`cmd_*`) plus shared CLI runtime helpers (`consume_session`, `save_term`, etc.) out of `atch.c`. -- `atch.c` now focuses on process globals, env/session dir primitives, legacy+new dispatch orchestration. -- Added `atch_cli_runtime.o` to `libatch.a` linkage. -- Rebuilt and smoke-verified (`--version`, `list`, `current` outside session rc=1). diff --git a/task_plan.md b/task_plan.md deleted file mode 100644 index ee9a2ca..0000000 --- a/task_plan.md +++ /dev/null @@ -1,12 +0,0 @@ -# task_plan.md - -## Goal -Refactor `atch` into an internal library-oriented layout while maintaining existing behavior. - -## Phases -| Phase | Description | Status | Success Criteria | -|---|---|---|---| -| 1 | Entrypoint separation + library artifact | complete | `main.c` thin CLI wrapper, `libatch.a` builds, `atch` behavior unchanged in smoke checks | -| 2 | Extract internal modules (path/session/control parsing helpers) | todo | Reduced coupling in `atch.c`; same CLI behavior | -| 3 | Public/internal API shaping | todo | Stable internal headers for reuse by future frontends | -| 4 | Validation/docs | in_progress | README and make usage updated; additional tests pending | From 9ef8161eeece15e73f00fb0ec641903f864c67e0 Mon Sep 17 00:00:00 2001 From: daywiss Date: Thu, 26 Mar 2026 17:08:34 +0000 Subject: [PATCH 7/8] refactor: move path/session-dir helpers into module --- atch.c | 59 +-------------------------------------------------- atch_paths.c | 60 ++++++++++++++++++++++++++++++++++++++++++++++++++++ atch_paths.h | 8 +++++++ makefile | 7 +++--- 4 files changed, 73 insertions(+), 61 deletions(-) create mode 100644 atch_paths.c create mode 100644 atch_paths.h diff --git a/atch.c b/atch.c index ccf0d0a..3e45f09 100644 --- a/atch.c +++ b/atch.c @@ -2,6 +2,7 @@ #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. */ @@ -30,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. */ 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/makefile b/makefile index b1619d4..4e31459 100644 --- a/makefile +++ b/makefile @@ -11,9 +11,9 @@ else STATIC_FLAG = -static endif -LIBOBJ = attach.o master.o atch.o atch_session.o atch_cmd.o atch_cli_opts.o atch_cli_runtime.o +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_session.c atch_cmd.c atch_cli_opts.c atch_cli_runtime.c +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 ?= . @@ -52,7 +52,8 @@ fmt-all: 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_cli.h ./atch_session.h ./atch_cmd.h ./atch_cli_opts.h ./atch_cli_runtime.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 From 42d9658a9b973ef5a1f1a146d2e2dbc55870e51a Mon Sep 17 00:00:00 2001 From: daywiss Date: Thu, 26 Mar 2026 17:10:34 +0000 Subject: [PATCH 8/8] docs: clarify libatch.a is internal artifact --- README.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/README.md b/README.md index 6ffb6c6..0f4b63b 100644 --- a/README.md +++ b/README.md @@ -101,6 +101,9 @@ Build just the internal static library artifact: 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 ```