/* * Copyright (c) 2024 Jonas 'Sortie' Termansen. * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. * * service.c * Start and stop services. */ #include #include #include #include #include #include #include #include #include #include #include #include #include static bool array_add(void*** array_ptr, size_t* used_ptr, size_t* length_ptr, void* value) { void** array; memcpy(&array, array_ptr, sizeof(array)); // Strict aliasing. if ( *used_ptr == *length_ptr ) { size_t length = *length_ptr; if ( !length ) length = 4; void** new_array = reallocarray(array, length, 2 * sizeof(void*)); if ( !new_array ) return false; array = new_array; memcpy(array_ptr, &array, sizeof(array)); // Strict aliasing. *length_ptr = length * 2; } memcpy(array + (*used_ptr)++, &value, sizeof(value)); // Strict aliasing. return true; } static char** tokenize(size_t* out_tokens_used, const char* string) { size_t tokens_used = 0; size_t tokens_length = 0; char** tokens = malloc(sizeof(char*)); if ( !tokens ) return NULL; bool failed = false; bool invalid = false; while ( *string ) { if ( isspace((unsigned char) *string) ) { string++; continue; } if ( *string == '#' ) break; char* token; size_t token_size; FILE* fp = open_memstream(&token, &token_size); if ( !fp ) { failed = true; break; } bool singly = false; bool doubly = false; bool escaped = false; for ( char c = *string++; c; c = *string++ ) { if ( !escaped && !singly && !doubly && isspace((unsigned char) c) ) break; if ( !escaped && !doubly && c == '\'' ) { singly = !singly; continue; } if ( !escaped && !singly && c == '"' ) { doubly = !doubly; continue; } if ( !singly && !escaped && c == '\\' ) { escaped = true; continue; } if ( escaped ) { switch ( c ) { case 'a': c = '\a'; break; case 'b': c = '\b'; break; case 'e': c = '\e'; break; case 'f': c = '\f'; break; case 'n': c = '\n'; break; case 'r': c = '\r'; break; case 't': c = '\t'; break; case 'v': c = '\v'; break; default: break; }; } escaped = false; if ( fputc((unsigned char) c, fp) == EOF ) { failed = true; break; } } if ( singly || doubly || escaped ) { fclose(fp); free(token); invalid = true; break; } if ( fflush(fp) == EOF ) { fclose(fp); free(token); failed = true; break; } fclose(fp); if ( !array_add((void***) &tokens, &tokens_used, &tokens_length, token) ) { free(token); failed = true; break; } } if ( failed || invalid ) { for ( size_t i = 0; i < tokens_used; i++ ) free(tokens[i]); free(tokens); if ( invalid ) errno = 0; return NULL; } char** new_tokens = reallocarray(tokens, tokens_used, sizeof(char*)); if ( new_tokens ) tokens = new_tokens; *out_tokens_used = tokens_used; return tokens; } static int open_local_client_socket(const char* path, int flags) { size_t path_length = strlen(path); size_t addr_size = offsetof(struct sockaddr_un, sun_path) + path_length + 1; struct sockaddr_un* sockaddr = malloc(addr_size); if ( !sockaddr ) return -1; sockaddr->sun_family = AF_LOCAL; strcpy(sockaddr->sun_path, path); int fd = socket(AF_LOCAL, SOCK_STREAM | flags, 0); if ( fd < 0 ) return free(sockaddr), -1; if ( connect(fd, (const struct sockaddr*) sockaddr, addr_size) < 0 ) return close(fd), free(sockaddr), -1; free(sockaddr); return fd; } static void rewrite(const char* path, const char* daemon, const char* flags) { FILE* fp = fopen(path, "r"); if ( !fp ) { if ( errno != ENOENT ) err(1, "%s", path); } char* out_path; if ( asprintf(&out_path, "%s.XXXXXX", path) < 0 ) err(1, "malloc"); int out_fd = mkstemp(out_path); if ( out_fd < 0 ) err(1, "mkstemp: %s.XXXXXX", path); FILE* out = fdopen(out_fd, "w"); if ( !out ) { unlink(out_path); err(1, "fdopen"); } bool found = false; char* line = NULL; size_t line_size = 0; ssize_t line_length; off_t line_number = 0; while ( fp && 0 < (line_length = getline(&line, &line_size, fp)) ) { line_number++; size_t tokenc; char** tokens = tokenize(&tokenc, line); if ( !tokens ) { unlink(out_path); if ( errno ) err(1, "%s", path); else errx(1, "%s:%ji: Syntax error", path, (intmax_t) line_number); } if ( 2 <= tokenc && !strcmp(tokens[0], "require") && !strcmp(tokens[1], daemon) ) { found = true; if ( flags ) fprintf(out, "require %s%s\n", daemon, flags); } else fputs(line, out); } free(line); if ( !found && flags ) fprintf(out, "require %s%s\n", daemon, flags); if ( (fp && ferror(fp)) || ferror(out) || fflush(out) == EOF ) { unlink(out_path); err(1, "%s", path); } if ( fp ) { struct stat st; fstat(fileno(fp), &st); fchmod(out_fd, st.st_mode & 07777); fchown(out_fd, st.st_uid, st.st_gid); fclose(fp); } else fchmod(out_fd, 0666 & ~getumask()); if ( rename(out_path, path) < 0 ) { unlink(out_path); err(1, "rename: %s -> %s", out_path, path); } fclose(out); } static bool check_daemon_exists_in_dir(const char* dir, const char* daemon) { char* path; if ( asprintf(&path, "%s/%s", dir, daemon) < 0 ) err(1, "malloc"); bool result = !access(path, F_OK); free(path); return result; } static void check_daemon_exists(const char* daemon) { if ( !check_daemon_exists_in_dir("/etc/init", daemon) && !check_daemon_exists_in_dir("/share/init", daemon) ) errx(1, "%s: Daemon does not exist", daemon); } int main(int argc, char* argv[]) { const char* init_socket = getenv("INIT_SOCKET"); if ( !init_socket ) init_socket = "/var/run/init"; bool exit_code = false; bool no_await = false; bool optional = true; bool raw = false; const char* source = "local"; const struct option longopts[] = { {"exit-code", no_argument, NULL, 256}, {"no-await", no_argument, NULL, 257}, {"no-optional", no_argument, NULL, 258}, {"source", required_argument, NULL, 't'}, {"raw", no_argument, NULL, 'r'}, {0, 0, 0, 0} }; const char* opts = "rs:"; int opt; while ( (opt = getopt_long(argc, argv, opts, longopts, NULL)) != -1 ) { switch ( opt ) { case 'r': raw = true; break; case 's': source = optarg; break; case 256: exit_code = true; break; case 257: no_await = true; break; case 258: optional = false; break; default: return 2; } } int fd = open_local_client_socket(init_socket, 0); if ( fd < 0 ) err(1, "%s", init_socket); if ( raw ) { for ( int i = optind; i < argc; i++ ) { if ( dprintf(fd, "%s%c", argv[i], i + 1 == argc ? '\n' : ' ') < 0 ) err(1, "%s", init_socket); } return 0; } if ( argc - optind < 2 ) errx(1, "usage: "); const char* daemon = argv[optind++]; const char* command = argv[optind++]; char flags[sizeof(" optional no-await exit-code")]; snprintf(flags, sizeof(flags), "%s%s%s", optional ? " optional" : "", no_await ? " no-await" : "", exit_code ? " exit-code" : ""); char* source_path; if ( asprintf(&source_path, "/etc/init/%s", source) < 0 ) err(1, "malloc"); if ( !strcmp(command, "enable") ) { check_daemon_exists(daemon); rewrite(source_path, daemon, flags); if ( dprintf(fd, "require %s %s start\n", source, daemon) < 0 ) err(1, "%s", init_socket); } else if ( !strcmp(command, "disable") ) { rewrite(source_path, daemon, NULL); if ( dprintf(fd, "unrequire %s %s\n", source, daemon) < 0 ) err(1, "%s", init_socket); } else if ( !strcmp(command, "start") ) { if ( dprintf(fd, "require %s %s start\n", source, daemon) < 0 ) err(1, "%s", init_socket); } else if ( !strcmp(command, "stop") ) { if ( dprintf(fd, "unrequire %s %s\n", source, daemon) < 0 ) err(1, "%s", init_socket); } else if ( !strcmp(command, "restart") ) { if ( dprintf(fd, "restart %s\n", daemon) < 0 ) err(1, "%s", init_socket); } else if ( !strcmp(command, "reload") ) { if ( dprintf(fd, "reload %s\n", daemon) < 0 ) err(1, "%s", init_socket); } else if ( !strcmp(command, "reconfigure") ) { if ( dprintf(fd, "reconfigure %s\n", daemon) < 0 ) err(1, "%s", init_socket); } else if ( !strcmp(command, "terminate") ) { if ( dprintf(fd, "terminate %s\n", daemon) < 0 ) err(1, "%s", init_socket); } else if ( !strcmp(command, "kill") ) { if ( dprintf(fd, "kill %s\n", daemon) < 0 ) err(1, "%s", init_socket); } // TODO: --list // TODO: status // TODO: signal // TODO: pid // TODO: requirements // TODO: log else errx(1, "unknown command: %s", command); return 0; }