diff options
-rw-r--r-- | .gitignore | 14 | ||||
-rw-r--r-- | LICENSE | 15 | ||||
-rw-r--r-- | Makefile | 42 | ||||
-rw-r--r-- | config.mk | 9 | ||||
-rw-r--r-- | copier.c | 95 | ||||
-rw-r--r-- | editasroot.c | 269 |
6 files changed, 444 insertions, 0 deletions
diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..e07bd4a --- /dev/null +++ b/.gitignore @@ -0,0 +1,14 @@ +*\#* +*~ +*.o +*.a +*.lo +*.su +*.so +*.so.* +*.gch +*.gcov +*.gcno +*.gcda +/editasroot +/copier @@ -0,0 +1,15 @@ +ISC License + +© 2021 Mattias Andrée <maandree@kth.se> + +Permission to use, copy, modify, and/or 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. diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..3b51433 --- /dev/null +++ b/Makefile @@ -0,0 +1,42 @@ +.POSIX: + +CONFIGFILE = config.mk +include $(CONFIGFILE) + +OBJ =\ + editasroot.o\ + copier.o + +all: editasroot copier +$(OBJ): $(@:.o=.c) + +.c.o: + $(CC) -c -o $@ $< $(CFLAGS) $(CPPFLAGS) + +editasroot: editasroot.o + $(CC) -o $@ $@.o $(LDFLAGS) + +copier: copier.o + $(CC) -o $@ $@.o $(LDFLAGS) + +install: editasroot copier + mkdir -p -- "$(DESTDIR)$(PREFIX)/bin" + mkdir -p -- "$(DESTDIR)$(LIBEXECDIR)" + mkdir -p -- "$(DESTDIR)$(MANPREFIX)/man8/" + cp -- editasroot "$(DESTDIR)$(PREFIX)/bin/" + cp -- editasroot.1 "$(DESTDIR)$(MANPREFIX)/man8/" + cp -- copier "$(DESTDIR)$(LIBEXECDIR)/" + +uninstall: + -rm -f -- "$(DESTDIR)$(PREFIX)/bin/editasroot" + -rm -f -- "$(DESTDIR)$(LIBEXECDIR)/editasroot-copier" + -rm -f -- "$(DESTDIR)$(MANPREFIX)/man8/editasroot.8" + +clean: + -rm -f -- *.o *.a *.lo *.su *.so *.so.* *.gch *.gcov *.gcno *.gcda + -rm -f -- editasroot copier + +.SUFFIXES: +.SUFFIXES: .o .c + +.PHONY: all install uninstall clean diff --git a/config.mk b/config.mk new file mode 100644 index 0000000..bcc64e8 --- /dev/null +++ b/config.mk @@ -0,0 +1,9 @@ +PREFIX = /usr +MANPREFIX = $(PREFIX)/share/man +LIBEXECDIR = $(PREFIX)/libexec/editasroot + +CC = cc + +CPPFLAGS = -D_DEFAULT_SOURCE -D_BSD_SOURCE -D_XOPEN_SOURCE=700 -D'LIBEXECDIR="$(LIBEXECDIR)"' +CFLAGS = -std=c99 -Wall -O2 +LDFLAGS = -s diff --git a/copier.c b/copier.c new file mode 100644 index 0000000..231e515 --- /dev/null +++ b/copier.c @@ -0,0 +1,95 @@ +/* See LICENSE file for copyright and license details. */ +#include <sys/socket.h> +#include <errno.h> +#include <fcntl.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> + + +static const char *argv0 = "editasroot/copier"; + + +static void +copy_file(int destfd, const char *destfname, int srcfd, const char *srcfname) +{ + char buf[BUFSIZ]; + ssize_t r, w, p; + + for (;;) { + r = read(srcfd, buf, sizeof(buf)); + if (r <= 0) { + if (!r) + break; + fprintf(stderr, "%s: read %s: %s", argv0, srcfname, strerror(errno)); + exit(1); + } + + for (p = 0; p < r; p += w) { + w = write(destfd, buf, (size_t)(r - p)); + if (r <= 0) { + fprintf(stderr, "%s: write %s: %s", argv0, destfname, strerror(errno)); + exit(1); + } + } + } +} + + +int +main(int argc, char *argv[]) +{ + int filefd, parentfd; + const char *path; + ssize_t r; + char b; + + if (!argc) + exit(1); + argv0 = *argv++; + if (--argc != 2) + exit(1); + + path = argv[0]; + parentfd = atoi(argv[1]); + + filefd = open(path, O_RDWR); /* O_RDWR establishes that we can indeed write to the file */ + if (filefd < 0) { + fprintf(stderr, "%s: open %s O_RDWR: %s\n", argv0, path, strerror(errno)); + exit(1); + } + copy_file(parentfd, "<socket to parent>", filefd, path); + if (close(filefd)) { + fprintf(stderr, "%s: read %s: %s\n", argv0, path, strerror(errno)); + exit(1); + } + if (shutdown(parentfd, SHUT_WR)) { + fprintf(stderr, "%s: shutdown <socket to parent> SHUT_WR: %s\n", argv0, strerror(errno)); + exit(1); + } + + r = read(parentfd, &b, 1); + if (r != 1) { + if (r) + fprintf(stderr, "%s: read %s: %s\n", argv0, path, strerror(errno)); + exit(1); + } + + filefd = open(path, O_WRONLY | O_TRUNC); + if (filefd < 0) { + fprintf(stderr, "%s: open %s O_WRONLY|O_TRUNC: %s\n", argv0, path, strerror(errno)); + exit(1); + } + copy_file(filefd, path, parentfd, "<socket to parent>"); + if (close(filefd)) { + fprintf(stderr, "%s: write %s: %s\n", argv0, path, strerror(errno)); + exit(1); + } + if (close(parentfd)) { + fprintf(stderr, "%s: read <socket to parent>: %s\n", argv0, strerror(errno)); + exit(1); + } + + return 0; +} diff --git a/editasroot.c b/editasroot.c new file mode 100644 index 0000000..0036791 --- /dev/null +++ b/editasroot.c @@ -0,0 +1,269 @@ +/* See LICENSE file for copyright and license details. */ +#include <sys/prctl.h> +#include <sys/socket.h> +#include <sys/wait.h> +#include <errno.h> +#include <fcntl.h> +#include <paths.h> +#include <signal.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> + +#define TEMPFILEPATTERN "tmpXXXXXX" + + +static const char *argv0 = "editasroot"; +static const char *unlink_this = NULL; + + +static void +usage(void) +{ + fprintf(stderr, "usage: %s file\n", argv0); + exit(1); +} + + +static void +cleanup(void) +{ + if (unlink_this) { + unlink(unlink_this); + unlink_this = NULL; + } +} + + +static void +graceful_exit(int signo) +{ + cleanup(); + if (signal(signo, SIG_DFL) != SIG_ERR) + raise(signo); + exit(1); +} + + +static const char * +get_editor(void) +{ + const char *editor = NULL; + int fd, isbg; + + fd = open(_PATH_TTY, O_RDONLY); + if (fd < 0) { + fprintf(stderr, "%s: open %s O_RDONLY: %s", argv0, _PATH_TTY, strerror(errno)); + exit(1); + } + isbg = getpgrp() != tcgetpgrp(fd); + close(fd); + + if (isbg) + editor = getenv("VISUAL"); + if (!editor || !*editor) { + editor = getenv("EDITOR"); + if (!editor || !*editor) { + fprintf(stderr, "%s: warning: EDITOR not set, defaulting to 'vi'\n", argv0); + editor = "vi"; + } + } + + return editor; +} + + +static char * +get_tmpfile_pattern(void) +{ + const char *tmpdir = NULL; + char *path, *p; + + tmpdir = getenv("XDG_RUNTIME_DIR"); + if (!tmpdir || !*tmpdir) + tmpdir = _PATH_TMP; + + path = malloc(strlen(tmpdir) + sizeof("/"TEMPFILEPATTERN)); + if (!path) { + fprintf(stderr, "%s: malloc: %s", argv0, strerror(ENOMEM)); + exit(1); + } + p = stpcpy(path, tmpdir); + p -= p[-1] == '/'; + stpcpy(p, "/"TEMPFILEPATTERN); + + return path; +} + + +static void +copy_file(int destfd, const char *destfname, int srcfd, const char *srcfname) +{ + char buf[BUFSIZ]; + ssize_t r, w, p; + + for (;;) { + r = read(srcfd, buf, sizeof(buf)); + if (r <= 0) { + if (!r) + break; + fprintf(stderr, "%s: read %s: %s", argv0, srcfname, strerror(errno)); + exit(1); + } + + for (p = 0; p < r; p += w) { + w = write(destfd, buf, (size_t)(r - p)); + if (r <= 0) { + fprintf(stderr, "%s: write %s: %s", argv0, destfname, strerror(errno)); + exit(1); + } + } + } +} + + +static pid_t +run_child(const char *file, int fd, int close_this) +{ + char fdstr[3 * sizeof(fd) + 2]; + pid_t pid; + + switch (pid = fork()) { + case -1: + fprintf(stderr, "%s: fork: %s", argv0, strerror(errno)); + exit(1); + + case 0: + close(STDIN_FILENO); + close(STDOUT_FILENO); + close(close_this); + prctl(PR_SET_PDEATHSIG, SIGKILL); + sprintf(fdstr, "%i", fd); + execlp("asroot", "asroot", LIBEXECDIR"/copier", file, fdstr, NULL); + fprintf(stderr, "%s: execlp asroot: %s\n", argv0, strerror(errno)); + exit(1); + + default: + return pid; + } +} + + +static void +run_editor(const char *editor, const char *file, int close_this) +{ + pid_t pid; + int status; + + switch (pid = fork()) { + case -1: + fprintf(stderr, "%s: fork: %s", argv0, strerror(errno)); + exit(1); + + case 0: + close(close_this); + execlp(editor, editor, "--", file, NULL); + fprintf(stderr, "%s: execlp %s: %s\n", argv0, editor, strerror(errno)); + exit(1); + + default: + break; + } + + if (waitpid(pid, &status, 0) != pid) { + fprintf(stderr, "%s: waitpid %s 0: %s", argv0, editor, strerror(errno)); + exit(1); + } + + if (status) + exit(WIFEXITED(status) ? WEXITSTATUS(status) : 1); +} + + +int +main(int argc, char *argv[]) +{ + const char *editor; + char *path; + int fd, fds[2], status, i; + pid_t pid; + + if (!argc) + usage(); + argv0 = *argv++; + if (--argc != 1) + usage(); + + editor = get_editor(); + + /* Start copier, with a bidirectional channel to it for copying the file */ + if (socketpair(PF_LOCAL, SOCK_STREAM, 0, fds)) { + fprintf(stderr, "%s: socketpair PF_LOCAL SOCK_STREAM 0: %s", argv0, strerror(errno)); + exit(1); + } + pid = run_child(argv[0], fds[1], fds[0]); + close(fds[1]); + + /* Make sure (enough) tmpfile is unlinked if killed */ + for (i = 1; i < NSIG; i++) + if (i != SIGCHLD) + signal(i, graceful_exit); + + /* Create tmpfile from file to edit */ + path = get_tmpfile_pattern(); + fd = mkstemp(path); + if (fd < 0) { + fprintf(stderr, "%s: mkstemp %s: %s", argv0, path, strerror(errno)); + exit(1); + } + unlink_this = path; + if (atexit(cleanup)) { + fprintf(stderr, "%s: atexit: %s", argv0, strerror(errno)); + exit(1); + } + copy_file(fd, path, fds[0], "<socket to child>"); + if (close(fd)) { + fprintf(stderr, "%s: write %s: %s", argv0, path, strerror(errno)); + exit(1); + } + if (shutdown(fds[0], SHUT_RD)) { + fprintf(stderr, "%s: shutdown <socket to parent> SHUT_RD: %s\n", argv0, strerror(errno)); + exit(1); + } + + /* Start file editor */ + run_editor(editor, path, fds[0]); + + /* Signal to child that editor exited as expected */ + if (write(fds[0], &(char){1}, 1) != 1) { + fprintf(stderr, "%s: write <socket to child>: %s", argv0, strerror(errno)); + exit(1); + } + + /* Rewrite file from tmpfile and unlink tmpfile */ + fd = open(path, O_RDONLY); + if (fd < 0) { + fprintf(stderr, "%s: open %s O_RDONLY: %s", argv0, path, strerror(errno)); + exit(1); + } + copy_file(fds[0], "<socket to child>", fd, path); + if (close(fd)) { + fprintf(stderr, "%s: read %s: %s", argv0, path, strerror(errno)); + exit(1); + } + if (close(fds[0])) { + fprintf(stderr, "%s: write <socket to child>: %s", argv0, strerror(errno)); + exit(1); + } + unlink(unlink_this); + unlink_this = NULL; + free(path); + + /* Wait for exit copier to exit */ + if (waitpid(pid, &status, 0) != pid) { + fprintf(stderr, "%s: waitpid <child> 0: %s", argv0, strerror(errno)); + exit(1); + } + return WIFEXITED(status) ? WEXITSTATUS(status) : 1; +} |