1 /*        $NetBSD: ex_write.c,v 1.3 2014/01/26 21:43:45 christos Exp $ */
2 /*-
3  * Copyright (c) 1992, 1993, 1994
4  *        The Regents of the University of California.  All rights reserved.
5  * Copyright (c) 1992, 1993, 1994, 1995, 1996
6  *        Keith Bostic.  All rights reserved.
7  *
8  * See the LICENSE file for redistribution information.
9  */
10 
11 #include "config.h"
12 
13 #include <sys/cdefs.h>
14 #if 0
15 #ifndef lint
16 static const char sccsid[] = "Id: ex_write.c,v 10.38 2001/06/25 15:19:22 skimo Exp  (Berkeley) Date: 2001/06/25 15:19:22 ";
17 #endif /* not lint */
18 #else
19 __RCSID("$NetBSD: ex_write.c,v 1.3 2014/01/26 21:43:45 christos Exp $");
20 #endif
21 
22 #include <sys/types.h>
23 #include <sys/queue.h>
24 #include <sys/stat.h>
25 
26 #include <bitstring.h>
27 #include <ctype.h>
28 #include <errno.h>
29 #include <fcntl.h>
30 #include <limits.h>
31 #include <stdio.h>
32 #include <stdlib.h>
33 #include <string.h>
34 #include <unistd.h>
35 
36 #include "../common/common.h"
37 
38 enum which {WN, WQ, WRITE, XIT};
39 static int exwr __P((SCR *, EXCMD *, enum which));
40 
41 /*
42  * ex_wn --         :wn[!] [>>] [file]
43  *        Write to a file and switch to the next one.
44  *
45  * PUBLIC: int ex_wn __P((SCR *, EXCMD *));
46  */
47 int
ex_wn(SCR * sp,EXCMD * cmdp)48 ex_wn(SCR *sp, EXCMD *cmdp)
49 {
50           if (exwr(sp, cmdp, WN))
51                     return (1);
52           if (file_m3(sp, 0))
53                     return (1);
54 
55           /* The file name isn't a new file to edit. */
56           cmdp->argc = 0;
57 
58           return (ex_next(sp, cmdp));
59 }
60 
61 /*
62  * ex_wq --         :wq[!] [>>] [file]
63  *        Write to a file and quit.
64  *
65  * PUBLIC: int ex_wq __P((SCR *, EXCMD *));
66  */
67 int
ex_wq(SCR * sp,EXCMD * cmdp)68 ex_wq(SCR *sp, EXCMD *cmdp)
69 {
70           int force;
71 
72           if (exwr(sp, cmdp, WQ))
73                     return (1);
74           if (file_m3(sp, 0))
75                     return (1);
76 
77           force = FL_ISSET(cmdp->iflags, E_C_FORCE);
78 
79           if (ex_ncheck(sp, force))
80                     return (1);
81 
82           F_SET(sp, force ? SC_EXIT_FORCE : SC_EXIT);
83           return (0);
84 }
85 
86 /*
87  * ex_write --      :write[!] [>>] [file]
88  *                  :write [!] [cmd]
89  *        Write to a file.
90  *
91  * PUBLIC: int ex_write __P((SCR *, EXCMD *));
92  */
93 int
ex_write(SCR * sp,EXCMD * cmdp)94 ex_write(SCR *sp, EXCMD *cmdp)
95 {
96           return (exwr(sp, cmdp, WRITE));
97 }
98 
99 
100 /*
101  * ex_xit -- :x[it]! [file]
102  *        Write out any modifications and quit.
103  *
104  * PUBLIC: int ex_xit __P((SCR *, EXCMD *));
105  */
106 int
ex_xit(SCR * sp,EXCMD * cmdp)107 ex_xit(SCR *sp, EXCMD *cmdp)
108 {
109           int force;
110 
111           NEEDFILE(sp, cmdp);
112 
113           if (F_ISSET(sp->ep, F_MODIFIED) && exwr(sp, cmdp, XIT))
114                     return (1);
115           if (file_m3(sp, 0))
116                     return (1);
117 
118           force = FL_ISSET(cmdp->iflags, E_C_FORCE);
119 
120           if (ex_ncheck(sp, force))
121                     return (1);
122 
123           F_SET(sp, force ? SC_EXIT_FORCE : SC_EXIT);
124           return (0);
125 }
126 
127 /*
128  * exwr --
129  *        The guts of the ex write commands.
130  */
131 static int
exwr(SCR * sp,EXCMD * cmdp,enum which cmd)132 exwr(SCR *sp, EXCMD *cmdp, enum which cmd)
133 {
134           MARK rm;
135           int flags;
136           char *name;
137           CHAR_T *p = NULL;
138           size_t nlen;
139           const char *n;
140           int rc;
141           EX_PRIVATE *exp;
142 
143           NEEDFILE(sp, cmdp);
144 
145           /* All write commands can have an associated '!'. */
146           LF_INIT(FS_POSSIBLE);
147           if (FL_ISSET(cmdp->iflags, E_C_FORCE))
148                     LF_SET(FS_FORCE);
149 
150           /* Skip any leading whitespace. */
151           if (cmdp->argc != 0)
152                     for (p = cmdp->argv[0]->bp; *p != '\0' && ISBLANK((UCHAR_T)*p); ++p);
153 
154           /* If "write !" it's a pipe to a utility. */
155           if (cmdp->argc != 0 && cmd == WRITE && *p == '!') {
156                     /* Secure means no shell access. */
157                     if (O_ISSET(sp, O_SECURE)) {
158                               ex_wemsg(sp, cmdp->cmd->name, EXM_SECURE_F);
159                               return (1);
160                     }
161 
162                     /* Expand the argument. */
163                     for (++p; *p && ISBLANK((UCHAR_T)*p); ++p);
164                     if (*p == '\0') {
165                               ex_emsg(sp, cmdp->cmd->usage, EXM_USAGE);
166                               return (1);
167                     }
168                     if (argv_exp1(sp, cmdp, p, STRLEN(p), 1))
169                               return (1);
170 
171                     /* Set the last bang command */
172                     exp = EXP(sp);
173                     free(exp->lastbcomm);
174                     exp->lastbcomm = v_wstrdup(sp, cmdp->argv[1]->bp,
175                         cmdp->argv[1]->len);
176 
177                     /*
178                      * Historically, vi waited after a write filter even if there
179                      * wasn't any output from the command.  People complained when
180                      * nvi waited only if there was output, wanting the visual cue
181                      * that the program hadn't written anything.
182                      */
183                     F_SET(sp, SC_EX_WAIT_YES);
184 
185                     /*
186                      * !!!
187                      * Ignore the return cursor position, the cursor doesn't
188                      * move.
189                      */
190                     if (ex_filter(sp, cmdp, &cmdp->addr1,
191                         &cmdp->addr2, &rm, cmdp->argv[1]->bp, FILTER_WRITE))
192                               return (1);
193 
194                     /* Ex terminates with a bang, even if the command fails. */
195                     if (!F_ISSET(sp, SC_VI) && !F_ISSET(sp, SC_EX_SILENT))
196                               (void)ex_puts(sp, "!\n");
197 
198                     return (0);
199           }
200 
201           /* Set the FS_ALL flag if we're writing the entire file. */
202           if (cmdp->addr1.lno <= 1 && !db_exist(sp, cmdp->addr2.lno + 1))
203                     LF_SET(FS_ALL);
204 
205           /* If "write >>" it's an append to a file. */
206           if (cmdp->argc != 0 && cmd != XIT && p[0] == '>' && p[1] == '>') {
207                     LF_SET(FS_APPEND);
208 
209                     /* Skip ">>" and whitespace. */
210                     for (p += 2; *p && ISBLANK((UCHAR_T)*p); ++p);
211           }
212 
213           /* If no other arguments, just write the file back. */
214           if (cmdp->argc == 0 || *p == '\0')
215                     return (file_write(sp,
216                         &cmdp->addr1, &cmdp->addr2, NULL, flags));
217 
218           /* Build an argv so we get an argument count and file expansion. */
219           if (argv_exp2(sp, cmdp, p, STRLEN(p)))
220                     return (1);
221 
222           /*
223            *  0 args: impossible.
224            *  1 args: impossible (I hope).
225            *  2 args: read it.
226            * >2 args: object, too many args.
227            *
228            * The 1 args case depends on the argv_sexp() function refusing
229            * to return success without at least one non-blank character.
230            */
231           switch (cmdp->argc) {
232           case 0:
233           case 1:
234                     abort();
235                     /* NOTREACHED */
236           case 2:
237                     INT2CHAR(sp, cmdp->argv[1]->bp, cmdp->argv[1]->len+1,
238                                n, nlen);
239                     name = v_strdup(sp, n, nlen - 1);
240 
241                     /*
242                      * !!!
243                      * Historically, the read and write commands renamed
244                      * "unnamed" files, or, if the file had a name, set
245                      * the alternate file name.
246                      */
247                     if (F_ISSET(sp->frp, FR_TMPFILE) &&
248                         !F_ISSET(sp->frp, FR_EXNAMED)) {
249                               char *q;
250                               if ((q = v_strdup(sp, name, nlen - 1)) != NULL) {
251                                         free(sp->frp->name);
252                                         sp->frp->name = q;
253                               }
254                               /*
255                                * The file has a real name, it's no longer a
256                                * temporary, clear the temporary file flags.
257                                *
258                                * !!!
259                                * If we're writing the whole file, FR_NAMECHANGE
260                                * will be cleared by the write routine -- this is
261                                * historic practice.
262                                */
263                               F_CLR(sp->frp, FR_TMPEXIT | FR_TMPFILE);
264                               F_SET(sp->frp, FR_NAMECHANGE | FR_EXNAMED);
265 
266                               /* Notify the screen. */
267                               (void)sp->gp->scr_rename(sp, sp->frp->name, 1);
268                     } else
269                               set_alt_name(sp, name);
270                     break;
271           default:
272                     INT2CHAR(sp, p, STRLEN(p) + 1, n, nlen);
273                     ex_emsg(sp, n, EXM_FILECOUNT);
274                     return (1);
275           }
276 
277           rc = file_write(sp, &cmdp->addr1, &cmdp->addr2, name, flags);
278 
279           free(name);
280 
281           return rc;
282 }
283 
284 /*
285  * ex_writefp --
286  *        Write a range of lines to a FILE *.
287  *
288  * PUBLIC: int ex_writefp __P((SCR *,
289  * PUBLIC:    const char *, FILE *, MARK *, MARK *, u_long *, u_long *, int));
290  */
291 int
ex_writefp(SCR * sp,const char * name,FILE * fp,MARK * fm,MARK * tm,u_long * nlno,u_long * nch,int silent)292 ex_writefp(SCR *sp, const char *name, FILE *fp, MARK *fm, MARK *tm, u_long *nlno, u_long *nch, int silent)
293 {
294           struct stat sb;
295           GS *gp;
296           u_long ccnt;                            /* XXX: can't print off_t portably. */
297           db_recno_t fline, tline, lcnt;
298           size_t len;
299           int rval;
300           const char *msg;
301           CHAR_T *p;
302           const char *f;
303           size_t flen;
304 
305           gp = sp->gp;
306           fline = fm->lno;
307           tline = tm->lno;
308 
309           if (nlno != NULL) {
310                     *nch = 0;
311                     *nlno = 0;
312           }
313 
314           /*
315            * The vi filter code has multiple processes running simultaneously,
316            * and one of them calls ex_writefp().  The "unsafe" function calls
317            * in this code are to db_get() and msgq().  Db_get() is safe, see
318            * the comment in ex_filter.c:ex_filter() for details.  We don't call
319            * msgq if the multiple process bit in the EXF is set.
320            *
321            * !!!
322            * Historic vi permitted files of 0 length to be written.  However,
323            * since the way vi got around dealing with "empty" files was to
324            * always have a line in the file no matter what, it wrote them as
325            * files of a single, empty line.  We write empty files.
326            *
327            * "Alex, I'll take vi trivia for $1000."
328            */
329           ccnt = 0;
330           lcnt = 0;
331           msg = "253|Writing...";
332           if (tline != 0)
333                     for (; fline <= tline; ++fline, ++lcnt) {
334                               /* Caller has to provide any interrupt message. */
335                               if ((lcnt + 1) % INTERRUPT_CHECK == 0) {
336                                         if (INTERRUPTED(sp))
337                                                   break;
338                                         if (!silent) {
339                                                   gp->scr_busy(sp, msg, msg == NULL ?
340                                                       BUSY_UPDATE : BUSY_ON);
341                                                   msg = NULL;
342                                         }
343                               }
344                               if (db_get(sp, fline, DBG_FATAL, &p, &len))
345                                         goto err;
346                               INT2FILE(sp, p, len, f, flen);
347                               if (fwrite(f, 1, flen, fp) != flen)
348                                         goto err;
349                               ccnt += len;
350                               if (putc('\n', fp) != '\n')
351                                         break;
352                               ++ccnt;
353                     }
354 
355           if (fflush(fp))
356                     goto err;
357           /*
358            * XXX
359            * I don't trust NFS -- check to make sure that we're talking to
360            * a regular file and sync so that NFS is forced to flush.
361            */
362           if (!fstat(fileno(fp), &sb) &&
363               S_ISREG(sb.st_mode) && fsync(fileno(fp)))
364                     goto err;
365 
366           if (fclose(fp))
367                     goto err;
368 
369           rval = 0;
370           if (0) {
371 err:                if (!F_ISSET(sp->ep, F_MULTILOCK))
372                               msgq_str(sp, M_SYSERR, name, "%s");
373                     (void)fclose(fp);
374                     rval = 1;
375           }
376 
377           if (!silent)
378                     gp->scr_busy(sp, NULL, BUSY_OFF);
379 
380           /* Report the possibly partial transfer. */
381           if (nlno != NULL) {
382                     *nch = ccnt;
383                     *nlno = lcnt;
384           }
385           return (rval);
386 }
387