1 /*
2  * t_editor.c
3  *
4  * editor routines for tagutil.
5  */
6 #include <sys/types.h>
7 #include <sys/stat.h>
8 #include <sys/wait.h>
9 #include <sys/time.h>
10 #include <sys/resource.h>
11 
12 #include "t_config.h"
13 #include "t_format.h"
14 #include "t_editor.h"
15 #include "t_loader.h"
16 
17 
18 int
t_edit(struct t_tune * tune)19 t_edit(struct t_tune *tune)
20 {
21 	FILE *fp = NULL;
22 	struct t_taglist *tlist = NULL;
23 	char *tmp = NULL, *fmtdata = NULL;
24 	const char *editor, *tmpdir;
25 	pid_t editpid; /* child process */
26 	int status, success = 0;
27 	struct stat before, after;
28 	extern const struct t_format *Fflag;
29 
30 	assert(tune != NULL);
31 
32 	/* convert the tags into the requested format */
33 	tlist = t_tune_tags(tune);
34 	if (tlist == NULL)
35 		goto out;
36 	fmtdata = Fflag->tags2fmt(tlist, t_tune_path(tune));
37 	t_taglist_delete(tlist);
38 	tlist = NULL;
39 	if (fmtdata == NULL)
40 		goto out;
41 
42 	tmpdir = getenv("TMPDIR");
43 	if (tmpdir == NULL)
44 		tmpdir = "/tmp";
45 	/* print the format data into a temp file */
46 	if (asprintf(&tmp, "%s/%s-XXXXXX.%s", tmpdir, getprogname(),
47 	    Fflag->fileext) < 0) {
48 		goto out;
49 	}
50 	if (mkstemps(tmp, strlen(Fflag->fileext) + 1) == -1) {
51 		warn("mkstemps");
52 		goto out;
53 	}
54 	fp = fopen(tmp, "w");
55 	if (fp == NULL) {
56 		warn("%s: fopen", tmp);
57 		goto out;
58 	}
59 	if (fprintf(fp, "%s", fmtdata) < 0) {
60 		warn("%s: fprintf", tmp);
61 		goto out;
62 	}
63 	if (fclose(fp) != 0) {
64 		warn("%s: fclose", tmp);
65 		goto out;
66 	}
67 	fp = NULL;
68 
69 	/* call the user's editor to edit the temp file */
70 	editor = getenv("EDITOR");
71 	if (editor == NULL) {
72 		warnx("please set the $EDITOR environment variable.");
73 		goto out;
74 	}
75 
76 	/* save the current mtime so we know later if the file has been
77 	   modified */
78 	if (stat(tmp, &before) != 0)
79 		goto out;
80 
81 	/* launch the editor */
82 	switch (editpid = fork()) {
83 	case -1: /* error */
84 		warn("fork");
85 		goto out;
86 		/* NOTREACHED */
87 	case 0: /* child (edit process) */
88 		execlp(editor, /* argv[0] */editor, /* argv[1] */tmp, NULL);
89 		/* if we reach here, execlp(3) has failed */
90 		err(EXIT_FAILURE, "execlp");
91 		/* NOTREACHED */
92 	default: /* parent (tagutil process) */
93 		waitpid(editpid, &status, 0);
94 	}
95 
96 	/* get the mtime now that the editor has been run */
97 	if (stat(tmp, &after) != 0)
98 		goto out;
99 
100 	int modified = (after.st_mtim.tv_sec  > before.st_mtim.tv_sec ||
101 		        after.st_mtim.tv_nsec > before.st_mtim.tv_nsec);
102 
103 	/* we perform the load iff the file has been modified by the edit
104 	   process and that process exited with success */
105 	if (modified && WIFEXITED(status) && WEXITSTATUS(status) == 0) {
106 		if (t_load(tune, tmp) == -1)
107 			goto out;
108 	}
109 
110 	success = 1;
111 	/* FALLTHROUGH */
112 out:
113 	if (fp != NULL)
114 		(void)fclose(fp);
115 	if (tmp != NULL)
116 		(void)unlink(tmp);
117 	free(tmp);
118 	free(fmtdata);
119 	return (success ? 0 : -1);
120 }
121