Add dvtm-editor(1) utility to make $EDITOR work as a filter
[dvtm.git] / dvtm-editor.c
blobb59aa3fa6edc69bdb98dbc4a19fae7526b9ddaca
1 /* edit-stream.c --- invoke $EDITOR as filter */
2 /* Copyright (C) 2016 Dmitry Bogatov <KAction@gnu.org> ISC licensed */
4 #define _POSIX_C_SOURCE 200809L
5 #include <stdlib.h>
6 #include <sys/types.h>
7 #include <sys/stat.h>
8 #include <fcntl.h>
9 #include <unistd.h>
10 #include <sys/wait.h>
12 /* stdio.h would be overkill */
13 #define write2(str) write(2, str, sizeof(str))
15 int
16 main(int argc, char **argv)
18 int return_value = 1;
19 const char *tty = ttyname(2); /* POSIX.1-2001, did you knew? */
20 if (!tty) {
21 write2("stderr is not tty");
22 return 1;
24 int in = open(tty, O_RDONLY);
25 if (in == -1) {
26 write2("failed to open tty for reading");
27 return 1;
29 char tempname[] = "/tmp/edit-stream.XXXXXX";
30 int tempfd = mkstemp(tempname);
31 if (tempfd == -1) {
32 write2("failed to open temporary file");
33 goto err_close_tty;
35 /* POSIX does not mandates modes of temporary file. */
36 if (fchmod(tempfd, 0600) == -1) {
37 write2("failed to change mode of temporary file");
38 goto err_remove_tempfile;
40 char buffer[2048];
41 ssize_t bytes;
42 while ((bytes = read(0, buffer, sizeof(buffer))) > 0) {
43 /* Here actually must be loop, since write(2) does not guarates
44 * that it will be able to write everything. But I am reckless.
46 if (write(tempfd, buffer, bytes) != bytes) {
47 write2("failed to write data to temporary file");
48 goto err_remove_tempfile;
51 if (fsync(tempfd) == -1) {
52 write2("failed to fsync temporary file");
53 goto err_remove_tempfile;
55 if (close(tempfd) == -1) {
56 write2("failed to close temporary file");
57 goto err_remove_tempfile;
59 if (dup2(in, 0) == -1) {
60 write2("failed to set tty as stdin");
61 goto err_remove_tempfile;
63 int stdout = dup(1);
64 if (stdout == -1) {
65 write2("failed to create copy of stdout");
66 goto err_remove_tempfile;
68 /* Descriptor 2 (stderr) still points to tty */
69 if (dup2(2, 1) == -1) {
70 write2("failed to set tty as stdout");
71 goto err_close_stdout;
73 const char *editor = getenv("EDITOR");
74 if (!editor) {
75 write2("EDITOR is not set");
76 goto err_close_stdout;
78 pid_t pid = fork();
79 if (pid == 0) {
80 close(stdout);
81 close(tempfd);
82 close(in);
83 execlp(editor, editor, tempname, NULL);
84 _exit(129);
86 int status;
87 if (wait(&status) == -1) {
88 write2("wait failed");
89 goto err_close_stdout;
91 if (!(WIFEXITED(status) && WEXITSTATUS(status) == 0)) {
92 write2("editor invocation failed");
93 goto err_close_stdout;
95 int tempfd_r = open(tempname, O_RDONLY);
96 if (tempfd_r == -1) {
97 write2("failed to open for reading edited temporary file");
98 goto err_close_stdout;
100 while ((bytes = read(tempfd_r, buffer, sizeof(buffer))) > 0) {
101 if (write(stdout, buffer, bytes) != bytes) {
102 write2("failed to write data to stdout");
103 goto err_close_tempfile_read;
107 return_value = 0;
109 /* Clean up on error is best efford. Descriptors are closed, files
110 are unlinked, but nothing is checked. */
111 err_close_tempfile_read:
112 close(tempfd_r);
113 err_close_stdout:
114 close(stdout);
115 err_remove_tempfile:
116 close(tempfd);
117 unlink(tempname);
118 err_close_tty:
119 close(in);
121 close(0);
122 close(1);
123 close(2);
125 return return_value;