1 /***********************************************************************
2 * *
3 * This software is part of the BSD package *
4 *Copyright (c) 1978-2010 The Regents of the University of California an*
5 * *
6 * Redistribution and use in source and binary forms, with or *
7 * without modification, are permitted provided that the following *
8 * conditions are met: *
9 * *
10 * 1. Redistributions of source code must retain the above *
11 * copyright notice, this list of conditions and the *
12 * following disclaimer. *
13 * *
14 * 2. Redistributions in binary form must reproduce the above *
15 * copyright notice, this list of conditions and the *
16 * following disclaimer in the documentation and/or other *
17 * materials provided with the distribution. *
18 * *
19 * 3. Neither the name of The Regents of the University of California*
20 * names of its contributors may be used to endorse or *
21 * promote products derived from this software without *
22 * specific prior written permission. *
23 * *
24 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND *
25 * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, *
26 * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF *
27 * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE *
28 * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS *
29 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, *
30 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED *
31 * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, *
32 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON *
33 * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, *
34 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY *
35 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE *
36 * POSSIBILITY OF SUCH DAMAGE. *
37 * *
38 * Redistribution and use in source and binary forms, with or without *
39 * modification, are permitted provided that the following conditions *
40 * are met: *
41 * 1. Redistributions of source code must retain the above copyright *
42 * notice, this list of conditions and the following disclaimer. *
43 * 2. Redistributions in binary form must reproduce the above copyright *
44 * notice, this list of conditions and the following disclaimer in *
45 * the documentation and/or other materials provided with the *
46 * distribution. *
47 * 3. Neither the name of the University nor the names of its *
48 * contributors may be used to endorse or promote products derived *
49 * from this software without specific prior written permission. *
50 * *
51 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS "AS IS" *
52 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED *
53 * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A *
54 * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS *
55 * OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, *
56 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT *
57 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF *
58 * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND *
59 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, *
60 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT *
61 * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF *
62 * SUCH DAMAGE. *
63 * *
64 * Kurt Shoens (UCB) *
65 * gsf *
66 * *
67 ***********************************************************************/
68 #pragma prototyped
69 /*
70 * Mail -- a mail program
71 *
72 * Perform message editing functions.
73 */
74
75 #include "mailx.h"
76
77 /*
78 * Edit a message list.
79 */
80 static int
edit1(struct msg * msgvec,int type)81 edit1(struct msg* msgvec, int type)
82 {
83 register struct msg* ip;
84 register struct msg* mp;
85 FILE* fp;
86 off_t size;
87 sig_t sigint;
88
89 /*
90 * Deal with each message to be edited . . .
91 */
92 for (ip = msgvec; ip->m_index; ip++) {
93 if (ip > msgvec) {
94 char buf[100];
95 char* p;
96
97 note(PROMPT, "Edit message %d [ynq]? ", ip->m_index);
98 if (!fgets(buf, sizeof buf, stdin))
99 break;
100 for (p = buf; isspace(*p); p++);
101 if (*p == 'q' || *p == 'Q')
102 break;
103 if (*p == 'n' || *p == 'N')
104 continue;
105 }
106 state.msg.dot = mp = state.msg.list + ip->m_index - 1;
107 touchmsg(mp);
108 sigint = signal(SIGINT, SIG_IGN);
109 if (fp = run_editor(setinput(mp), mp->m_size, NiL, type, state.readonly)) {
110 if (!state.msg.op)
111 settmp(NiL, 0);
112 fseek(state.msg.op, (off_t)0, SEEK_END);
113 size = ftell(state.msg.op);
114 mp->m_block = blocknumber(size);
115 mp->m_offset = blockoffset(size);
116 msgflags(mp, MODIFY, 0);
117 rewind(fp);
118 filecopy(NiL, fp, state.tmp.dir, state.msg.op, NiL, (off_t)0, &mp->m_lines, &mp->m_size, 0);
119 fileclose(fp);
120 }
121 signal(SIGINT, sigint);
122 }
123 return 0;
124 }
125
126 /*
127 * Edit a message list.
128 */
129 int
editor(struct msg * msgvec)130 editor(struct msg* msgvec)
131 {
132 return edit1(msgvec, 'e');
133 }
134
135 /*
136 * Invoke the visual editor on a message list.
137 */
138 int
visual(struct msg * msgvec)139 visual(struct msg* msgvec)
140 {
141 return edit1(msgvec, 'v');
142 }
143
144 /*
145 * Run an editor on the file at "fp" of "size" bytes,
146 * and return a new file pointer.
147 * Signals must be handled by the caller.
148 * "Type" is 'e' for state.var.editor, 'v' for state.var.visual.
149 */
150 FILE*
run_editor(register FILE * fp,off_t size,struct header * hp,int type,int readonly)151 run_editor(register FILE* fp, off_t size, struct header* hp, int type, int readonly)
152 {
153 FILE* ep;
154 time_t modtime;
155 int lc;
156 int err;
157 char* edit;
158 unsigned long editheaders = 0;
159 struct parse pp;
160
161 /*
162 * Create and copy to the temporary file.
163 */
164 if (!(ep = fileopen(state.tmp.edit, "EMw")))
165 goto ret1;
166 if (size) {
167 if (size < 0)
168 size = 0;
169 if (hp && state.var.editheaders) {
170 editheaders = GEDIT|GRULE;
171 headout(ep, hp, editheaders|GNL);
172 }
173 if (filecopy(NiL, fp, state.tmp.edit, ep, NiL, size, NiL, NiL, 0))
174 goto ret2;
175 }
176 modtime = state.openstat.st_mtime;
177 fileclose(ep);
178 ep = 0;
179 /*
180 * Edit the file.
181 */
182 edit = type == 'e' ? state.var.editor : state.var.visual;
183 err = run_command(edit, 0, -1, -1, state.tmp.edit, NiL, NiL) < 0;
184 /*
185 * If in readonly mode or file unchanged, clean up and return.
186 */
187 if (readonly)
188 goto ret2;
189 if (stat(state.tmp.edit, &state.openstat) < 0)
190 goto ret1;
191 if (modtime == state.openstat.st_mtime)
192 goto ret2;
193 if (err)
194 note(0, "%s did not exit normally but did do some changes -- you may want to re-edit", edit);
195 /*
196 * Now, switch to the temporary file.
197 */
198 if (!(ep = fileopen(state.tmp.edit, "Ea+")))
199 goto ret2;
200 if (editheaders && headset(&pp, NiL, ep, hp, NiL, editheaders|GTO|GMETOO)) {
201 while (headget(&pp));
202 remove(state.tmp.edit);
203 if (!(ep = fileopen(state.tmp.edit, "EMa+")))
204 goto ret1;
205 filecopy(NiL, pp.fp, state.tmp.edit, ep, NiL, (off_t)0, NiL, NiL, 0);
206 fileclose(pp.fp);
207 }
208 /*
209 * Ensure that the tempEdit file ends with two newlines.
210 *
211 * XXX
212 * Probably ought to have a `From' line, as well.
213 *
214 * XXX
215 * If the file is only a single byte long, the seek is going to
216 * fail, but I'm not sure we care. In the case of any error, we
217 * pass back the file descriptor -- if we fail because the disk
218 * is too full, reads should continue to work and I see no reason
219 * to discard the user's work.
220 */
221 if (fseek(ep, (off_t)-2, SEEK_END) < 0)
222 return ep;
223 lc = getc(ep) == '\n' ? 1 : 0;
224 if (getc(ep) == '\n')
225 ++lc;
226 else
227 lc = 0;
228
229 switch (lc) {
230 case 0:
231 if (putc('\n', ep) == EOF)
232 break;
233 /* FALLTHROUGH */
234 case 1:
235 putc('\n', ep);
236 /* FALLTHROUGH */
237 case 2:
238 break;
239 default:
240 abort();
241 break;
242 }
243 /*
244 * XXX: fflush() is necessary, so future stat(2) succeeds.
245 */
246 fflush(ep);
247 remove(state.tmp.edit);
248 return ep;
249
250 ret1: note(SYSTEM, "%s", state.tmp.edit);
251 ret2: remove(state.tmp.edit);
252 if (ep)
253 fileclose(ep);
254 return 0;
255 }
256