aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--.gitignore14
-rw-r--r--LICENSE15
-rw-r--r--Makefile42
-rw-r--r--config.mk9
-rw-r--r--copier.c95
-rw-r--r--editasroot.c269
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
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000..c44b2d9
--- /dev/null
+++ b/LICENSE
@@ -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;
+}