dvtm-editor.c (4485B)
1 /* Invoke $EDITOR as a filter. 2 * 3 * Copyright (c) 2016 Dmitry Bogatov <KAction@gnu.org> 4 * Copyright (c) 2017 Marc André Tanner <mat@brain-dump.org> 5 * 6 * Permission to use, copy, modify, and distribute this software for any 7 * purpose with or without fee is hereby granted, provided that the above 8 * copyright notice and this permission notice appear in all copies. 9 * 10 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 11 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 12 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 13 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 14 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 15 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 16 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 17 */ 18 #include <sys/stat.h> 19 #include <sys/types.h> 20 #include <sys/wait.h> 21 #include <errno.h> 22 #include <fcntl.h> 23 #include <stdarg.h> 24 #include <stdio.h> 25 #include <stdlib.h> 26 #include <string.h> 27 #include <unistd.h> 28 29 static void error(const char *msg, ...) { 30 va_list ap; 31 va_start(ap, msg); 32 vfprintf(stderr, msg, ap); 33 va_end(ap); 34 if (errno) 35 fprintf(stderr, ": %s", strerror(errno)); 36 fprintf(stderr, "\n"); 37 } 38 39 int main(int argc, char *argv[]) 40 { 41 int exit_status = EXIT_FAILURE, tmp_write = -1; 42 43 const char *editor = getenv("DVTM_EDITOR"); 44 if (!editor) 45 editor = getenv("VISUAL"); 46 if (!editor) 47 editor = getenv("EDITOR"); 48 if (!editor) 49 editor = "vi"; 50 51 char tempname[] = "/tmp/dvtm-editor.XXXXXX"; 52 if ((tmp_write = mkstemp(tempname)) == -1) { 53 error("failed to open temporary file `%s'", tempname); 54 goto err; 55 } 56 57 /* POSIX does not mandates modes of temporary file. */ 58 if (fchmod(tmp_write, 0600) == -1) { 59 error("failed to change mode of temporary file `%s'", tempname); 60 goto err; 61 } 62 63 char buffer[2048]; 64 ssize_t bytes; 65 while ((bytes = read(STDIN_FILENO, buffer, sizeof(buffer))) > 0) { 66 do { 67 ssize_t written = write(tmp_write, buffer, bytes); 68 if (written == -1) { 69 error("failed to write data to temporary file `%s'", 70 tempname); 71 goto err; 72 } 73 bytes -= written; 74 } while (bytes > 0); 75 } 76 77 if (fsync(tmp_write) == -1) { 78 error("failed to fsync temporary file `%s'", tempname); 79 goto err; 80 } 81 82 struct stat stat_before; 83 if (fstat(tmp_write, &stat_before) == -1) { 84 error("failed to stat newly created temporary file `%s'", tempname); 85 goto err; 86 } 87 88 if (close(tmp_write) == -1) { 89 error("failed to close temporary file `%s'", tempname); 90 goto err; 91 } 92 93 pid_t pid = fork(); 94 if (pid == -1) { 95 error("failed to fork editor process"); 96 goto err; 97 } else if (pid == 0) { 98 int tty = open("/dev/tty", O_RDWR); 99 if (tty == -1) { 100 error("failed to open /dev/tty"); 101 _exit(1); 102 } 103 104 if (dup2(tty, STDIN_FILENO) == -1) { 105 error("failed to set tty as stdin"); 106 _exit(1); 107 } 108 109 if (dup2(tty, STDOUT_FILENO) == -1) { 110 error("failed to set tty as stdout"); 111 _exit(1); 112 } 113 114 if (dup2(tty, STDERR_FILENO) == -1) { 115 error("failed to set tty as stderr"); 116 _exit(1); 117 } 118 119 close(tty); 120 121 const char *editor_argv[argc+2]; 122 editor_argv[0] = editor; 123 for (int i = 1; i < argc; i++) 124 editor_argv[i] = argv[i]; 125 editor_argv[argc] = tempname; 126 editor_argv[argc+1] = NULL; 127 128 execvp(editor, (char* const*)editor_argv); 129 error("failed to exec editor process `%s'", editor); 130 _exit(127); 131 } 132 133 int status; 134 if (waitpid(pid, &status, 0) == -1) { 135 error("waitpid failed"); 136 goto err; 137 } 138 if (!WIFEXITED(status)) { 139 error("editor invocation failed"); 140 goto err; 141 } 142 if ((status = WEXITSTATUS(status)) != 0) { 143 error("editor terminated with exit status: %d", status); 144 goto err; 145 } 146 147 int tmp_read = open(tempname, O_RDONLY); 148 if (tmp_read == -1) { 149 error("failed to open for reading of edited temporary file `%s'", 150 tempname); 151 goto err; 152 } 153 154 struct stat stat_after; 155 if (fstat(tmp_read, &stat_after) == -1) { 156 error("failed to stat edited temporary file `%s'", tempname); 157 goto err; 158 } 159 160 if (stat_before.st_mtime == stat_after.st_mtime) 161 goto ok; /* no modifications */ 162 163 while ((bytes = read(tmp_read, buffer, sizeof(buffer))) > 0) { 164 do { 165 ssize_t written = write(STDOUT_FILENO, buffer, bytes); 166 if (written == -1) { 167 error("failed to write data to stdout"); 168 goto err; 169 } 170 bytes -= written; 171 } while (bytes > 0); 172 } 173 174 ok: 175 exit_status = EXIT_SUCCESS; 176 err: 177 if (tmp_write != -1) 178 unlink(tempname); 179 return exit_status; 180 }