Allow a NULL pattern in global commands (i.e., g//) to reuse the pattern
[unix-history] / usr.bin / elvis / tmp.c
CommitLineData
15637ed4
RG
1/* tmp.c */
2
3/* Author:
4 * Steve Kirkendall
5 * 14407 SW Teal Blvd. #C
6 * Beaverton, OR 97005
7 * kirkenda@cs.pdx.edu
15637ed4
RG
8 */
9
10
11/* This file contains functions which create & readback a TMPFILE */
12
13
14#include "config.h"
15#include "vi.h"
16#if TOS
17# include <stat.h>
18#else
19# if OSK
20# include "osk.h"
21# else
22# if AMIGA
23# include "amistat.h"
24# else
25# include <sys/stat.h>
26# endif
27# endif
28#endif
29#if TURBOC
30# include <process.h>
31#endif
32
33#ifndef NO_MODELINES
34static void do_modelines(l, stop)
35 long l; /* line number to start at */
36 long stop; /* line number to stop at */
37{
38 char *str; /* used to scan through the line */
39 char *start; /* points to the start of the line */
40 char buf[80];
41
42 /* if modelines are disabled, then do nothing */
43 if (!*o_modelines)
44 {
45 return;
46 }
47
48 /* for each line... */
49 for (; l <= stop; l++)
50 {
51 /* for each position in the line.. */
52 for (str = fetchline(l); *str; str++)
53 {
54 /* if it is the start of a modeline command... */
55 if ((str[0] == 'e' && str[1] == 'x'
56 || str[0] == 'v' && str[1] == 'i')
57 && str[2] == ':')
58 {
59 start = str += 3;
60
61 /* find the end */
62 for (str = start + strlen(start); *--str != ':'; )
63 {
64 }
65
66 /* if it is a well-formed modeline, execute it */
67 if (str > start && str - start < sizeof buf)
68 {
69 strncpy(buf, start, (int)(str - start));
70 exstring(buf, str - start, '\\');
71 break;
72 }
73 }
74 }
75 }
76}
77#endif
78
79
80/* The FAIL() macro prints an error message and then exits. */
81#define FAIL(why,arg) mode = MODE_EX; msg(why, arg); endwin(); exit(9)
82
83/* This is the name of the temp file */
84static char tmpname[80];
85
86/* This function creates the temp file and copies the original file into it.
87 * Returns if successful, or stops execution if it fails.
88 */
89int tmpstart(filename)
90 char *filename; /* name of the original file */
91{
92 int origfd; /* fd used for reading the original file */
93 struct stat statb; /* stat buffer, used to examine inode */
94 REG BLK *this; /* pointer to the current block buffer */
95 REG BLK *next; /* pointer to the next block buffer */
96 int inbuf; /* number of characters in a buffer */
97 int nread; /* number of bytes read */
98 REG int j, k;
99 int i;
100 long nbytes;
101
102 /* switching to a different file certainly counts as a change */
103 changes++;
104 redraw(MARK_UNSET, FALSE);
105
106 /* open the original file for reading */
107 *origname = '\0';
108 if (filename && *filename)
109 {
110 strcpy(origname, filename);
111 origfd = open(origname, O_RDONLY);
112 if (origfd < 0 && errno != ENOENT)
113 {
114 msg("Can't open \"%s\"", origname);
115 return tmpstart("");
116 }
117 if (origfd >= 0)
118 {
119 if (stat(origname, &statb) < 0)
120 {
121 FAIL("Can't stat \"%s\"", origname);
122 }
123#if TOS
124 if (origfd >= 0 && (statb.st_mode & S_IJDIR))
125#else
126# if OSK
127 if (origfd >= 0 && (statb.st_mode & S_IFDIR))
128# else
129 if (origfd >= 0 && (statb.st_mode & S_IFMT) != S_IFREG)
130# endif
131#endif
132 {
133 msg("\"%s\" is not a regular file", origname);
134 return tmpstart("");
135 }
136 }
137 else
138 {
139 stat(".", &statb);
140 }
141 if (origfd >= 0)
142 {
143 origtime = statb.st_mtime;
144#if OSK
145 if (*o_readonly || !(statb.st_mode &
146 ((getuid() >> 16) == 0 ? S_IOWRITE | S_IWRITE :
147 ((statb.st_gid != (getuid() >> 16) ? S_IOWRITE : S_IWRITE)))))
148#endif
08746e8b 149#if AMIGA || MSDOS
15637ed4
RG
150 if (*o_readonly || !(statb.st_mode & S_IWRITE))
151#endif
08746e8b
AM
152#if TOS
153# ifdef __GNUC__
154 if (*o_readonly || !(statb.st_mode & S_IWRITE))
155# else
15637ed4 156 if (*o_readonly || (statb.st_mode & S_IJRON))
08746e8b 157# endif
15637ed4
RG
158#endif
159#if ANY_UNIX
160 if (*o_readonly || !(statb.st_mode &
161 ((geteuid() == 0) ? 0222 :
162 ((statb.st_uid != geteuid() ? 0022 : 0200)))))
163#endif
164#if VMS
165 if (*o_readonly)
166#endif
167 {
168 setflag(file, READONLY);
169 }
170 }
171 else
172 {
173 origtime = 0L;
174 }
175 }
176 else
177 {
178 setflag(file, NOFILE);
179 origfd = -1;
180 origtime = 0L;
181 stat(".", &statb);
182 }
183
184 /* make a name for the tmp file */
08746e8b
AM
185 do
186 {
187 tmpnum++;
15637ed4 188#if MSDOS || TOS
08746e8b
AM
189 /* MS-Dos doesn't allow multiple slashes, but supports drives
190 * with current directories.
191 * This relies on TMPNAME beginning with "%s\\"!!!!
192 */
193 strcpy(tmpname, o_directory);
194 if ((i = strlen(tmpname)) && !strchr(":/\\", tmpname[i-1]))
195 tmpname[i++]=SLASH;
196 sprintf(tmpname+i, TMPNAME+3, getpid(), tmpnum);
15637ed4 197#else
08746e8b 198 sprintf(tmpname, TMPNAME, o_directory, getpid(), tmpnum);
15637ed4 199#endif
08746e8b
AM
200 } while (access(tmpname, 0) == 0);
201
202 /* !!! RACE CONDITION HERE - some other process with the same PID could
203 * create the temp file between the access() call and the creat() call.
204 * This could happen in a couple of ways:
205 * - different workstation may share the same temp dir via NFS. Each
206 * workstation could have a process with the same number.
207 * - The DOS version may be running multiple times on the same physical
208 * machine in different virtual machines. The DOS pid number will
209 * be the same on all virtual machines.
210 *
211 * This race condition could be fixed by replacing access(tmpname, 0)
212 * with open(tmpname, O_CREAT|O_EXCL, 0600), if we could only be sure
213 * that open() *always* used modern UNIX semantics.
214 */
15637ed4
RG
215
216 /* create the temp file */
217#if ANY_UNIX
218 close(creat(tmpname, 0600)); /* only we can read it */
219#else
220 close(creat(tmpname, FILEPERMS)); /* anybody body can read it, alas */
221#endif
222 tmpfd = open(tmpname, O_RDWR | O_BINARY);
223 if (tmpfd < 0)
224 {
225 FAIL("Can't create temp file... Does directory \"%s\" exist?", o_directory);
226 return 1;
227 }
228
229 /* allocate space for the header in the file */
08746e8b
AM
230 if (write(tmpfd, hdr.c, (unsigned)BLKSIZE) < BLKSIZE
231 || write(tmpfd, tmpblk.c, (unsigned)BLKSIZE) < BLKSIZE)
232 {
233 FAIL("Error writing headers to \"%s\"", tmpname);
234 }
15637ed4
RG
235
236#ifndef NO_RECYCLE
237 /* initialize the block allocator */
238 /* This must already be done here, before the first attempt
239 * to write to the new file! GB */
240 garbage();
241#endif
242
243 /* initialize lnum[] */
244 for (i = 1; i < MAXBLKS; i++)
245 {
246 lnum[i] = INFINITY;
247 }
248 lnum[0] = 0;
249
250 /* if there is no original file, then create a 1-line file */
251 if (origfd < 0)
252 {
253 hdr.n[0] = 0; /* invalid inode# denotes new file */
254
255 this = blkget(1); /* get the new text block */
256 strcpy(this->c, "\n"); /* put a line in it */
257
258 lnum[1] = 1L; /* block 1 ends with line 1 */
259 nlines = 1L; /* there is 1 line in the file */
260 nbytes = 1L;
261
262 if (*origname)
263 {
264 msg("\"%s\" [NEW FILE] 1 line, 1 char", origname);
265 }
266 else
267 {
268 msg("\"[NO FILE]\" 1 line, 1 char");
269 }
270 }
271 else /* there is an original file -- read it in */
272 {
273 nbytes = nlines = 0;
274
275 /* preallocate 1 "next" buffer */
276 i = 1;
277 next = blkget(i);
278 inbuf = 0;
279
280 /* loop, moving blocks from orig to tmp */
281 for (;;)
282 {
283 /* "next" buffer becomes "this" buffer */
284 this = next;
285
286 /* read [more] text into this block */
287 nread = tread(origfd, &this->c[inbuf], BLKSIZE - 1 - inbuf);
288 if (nread < 0)
289 {
290 close(origfd);
291 close(tmpfd);
292 tmpfd = -1;
293 unlink(tmpname);
294 FAIL("Error reading \"%s\"", origname);
295 }
296
297 /* convert NUL characters to something else */
298 for (j = k = inbuf; k < inbuf + nread; k++)
299 {
300 if (!this->c[k])
301 {
302 setflag(file, HADNUL);
303 this->c[j++] = 0x80;
304 }
305#ifndef CRUNCH
08746e8b 306 else if (*o_beautify && this->c[k] < ' ' && this->c[k] >= 1)
15637ed4
RG
307 {
308 if (this->c[k] == '\t'
309 || this->c[k] == '\n'
310 || this->c[k] == '\f')
311 {
312 this->c[j++] = this->c[k];
313 }
314 else if (this->c[k] == '\b')
315 {
316 /* delete '\b', but complain */
317 setflag(file, HADBS);
318 }
319 /* else silently delete control char */
320 }
321#endif
322 else
323 {
324 this->c[j++] = this->c[k];
325 }
326 }
327 inbuf = j;
328
329 /* if the buffer is empty, quit */
330 if (inbuf == 0)
331 {
332 goto FoundEOF;
333 }
334
335#if MSDOS || TOS
336/* BAH! MS text mode read fills inbuf, then compresses eliminating \r
337 but leaving garbage at end of buf. The same is true for TURBOC. GB. */
338
339 memset(this->c + inbuf, '\0', BLKSIZE - inbuf);
340#endif
341
342 /* search backward for last newline */
343 for (k = inbuf; --k >= 0 && this->c[k] != '\n';)
344 {
345 }
346 if (k++ < 0)
347 {
348 if (inbuf >= BLKSIZE - 1)
349 {
350 k = 80;
351 }
352 else
353 {
354 k = inbuf;
355 }
356 }
357
358 /* allocate next buffer */
08746e8b
AM
359 if (i >= MAXBLKS - 2)
360 {
361 FAIL("File too big. Limit is approx %ld kbytes.", MAXBLKS * BLKSIZE / 1024L);
362 }
15637ed4
RG
363 next = blkget(++i);
364
365 /* move fragmentary last line to next buffer */
366 inbuf -= k;
367 for (j = 0; k < BLKSIZE; j++, k++)
368 {
369 next->c[j] = this->c[k];
370 this->c[k] = 0;
371 }
372
373 /* if necessary, add a newline to this buf */
374 for (k = BLKSIZE - inbuf; --k >= 0 && !this->c[k]; )
375 {
376 }
377 if (this->c[k] != '\n')
378 {
379 setflag(file, ADDEDNL);
380 this->c[k + 1] = '\n';
381 }
382
383 /* count the lines in this block */
384 for (k = 0; k < BLKSIZE && this->c[k]; k++)
385 {
386 if (this->c[k] == '\n')
387 {
388 nlines++;
389 }
390 nbytes++;
391 }
392 lnum[i - 1] = nlines;
393 }
394FoundEOF:
395
396 /* if this is a zero-length file, add 1 line */
397 if (nlines == 0)
398 {
399 this = blkget(1); /* get the new text block */
400 strcpy(this->c, "\n"); /* put a line in it */
401
402 lnum[1] = 1; /* block 1 ends with line 1 */
403 nlines = 1; /* there is 1 line in the file */
404 nbytes = 1;
405 }
406
407#if MSDOS || TOS
408 /* each line has an extra CR that we didn't count yet */
409 nbytes += nlines;
410#endif
411
412 /* report the number of lines in the file */
413 msg("\"%s\" %s %ld line%s, %ld char%s",
414 origname,
415 (tstflag(file, READONLY) ? "[READONLY]" : ""),
416 nlines,
417 nlines == 1 ? "" : "s",
418 nbytes,
419 nbytes == 1 ? "" : "s");
420 }
421
422 /* initialize the cursor to start of line 1 */
423 cursor = MARK_FIRST;
424
425 /* close the original file */
426 close(origfd);
427
428 /* any other messages? */
429 if (tstflag(file, HADNUL))
430 {
431 msg("This file contained NULs. They've been changed to \\x80 chars");
432 }
433 if (tstflag(file, ADDEDNL))
434 {
435 msg("Newline characters have been inserted to break up long lines");
436 }
437#ifndef CRUNCH
438 if (tstflag(file, HADBS))
439 {
440 msg("Backspace characters deleted due to ':set beautify'");
441 }
442#endif
443
444 storename(origname);
445
446#ifndef NO_MODELINES
447 if (nlines > 10)
448 {
449 do_modelines(1L, 5L);
450 do_modelines(nlines - 4L, nlines);
451 }
452 else
453 {
454 do_modelines(1L, nlines);
455 }
456#endif
457
458 /* force all blocks out onto the disk, to support file recovery */
459 blksync();
460
461 return 0;
462}
463
464
465
466/* This function copies the temp file back onto an original file.
467 * Returns TRUE if successful, or FALSE if the file could NOT be saved.
468 */
469int tmpsave(filename, bang)
470 char *filename; /* the name to save it to */
471 int bang; /* forced write? */
472{
473 int fd; /* fd of the file we're writing to */
474 REG int len; /* length of a text block */
475 REG BLK *this; /* a text block */
476 long bytes; /* byte counter */
477 REG int i;
478
479 /* if no filename is given, assume the original file name */
480 if (!filename || !*filename)
481 {
482 filename = origname;
483 }
484
485 /* if still no file name, then fail */
486 if (!*filename)
487 {
488 msg("Don't know a name for this file -- NOT WRITTEN");
489 return FALSE;
490 }
491
492 /* can't rewrite a READONLY file */
15637ed4 493 if (!strcmp(filename, origname) && tstflag(file, READONLY) && !bang)
15637ed4
RG
494 {
495 msg("\"%s\" [READONLY] -- NOT WRITTEN", filename);
496 return FALSE;
497 }
498
499 /* open the file */
500 if (*filename == '>' && filename[1] == '>')
501 {
502 filename += 2;
503 while (*filename == ' ' || *filename == '\t')
504 {
505 filename++;
506 }
507#ifdef O_APPEND
508 fd = open(filename, O_WRONLY|O_APPEND);
509#else
510 fd = open(filename, O_WRONLY);
511 lseek(fd, 0L, 2);
512#endif
513 }
514 else
515 {
516 /* either the file must not exist, or it must be the original
517 * file, or we must have a bang, or "writeany" must be set.
518 */
519 if (strcmp(filename, origname) && access(filename, 0) == 0 && !bang
520#ifndef CRUNCH
521 && !*o_writeany
522#endif
523 )
524 {
525 msg("File already exists - Use :w! to overwrite");
526 return FALSE;
527 }
528#if VMS
529 /* Create a new VMS version of this file. */
530 {
531 char *strrchr(), *ptr = strrchr(filename,';');
532 if (ptr) *ptr = '\0'; /* Snip off any ;number in the name */
533 }
534#endif
535 fd = creat(filename, FILEPERMS);
536 }
537 if (fd < 0)
538 {
539 msg("Can't write to \"%s\" -- NOT WRITTEN", filename);
540 return FALSE;
541 }
542
543 /* write each text block to the file */
544 bytes = 0L;
545 for (i = 1; i < MAXBLKS && (this = blkget(i)) && this->c[0]; i++)
546 {
547 for (len = 0; len < BLKSIZE && this->c[len]; len++)
548 {
549 }
550 if (twrite(fd, this->c, len) < len)
551 {
552 msg("Trouble writing to \"%s\"", filename);
553 if (!strcmp(filename, origname))
554 {
555 setflag(file, MODIFIED);
556 }
557 close(fd);
558 return FALSE;
559 }
560 bytes += len;
561 }
562
563 /* reset the "modified" flag, but not the "undoable" flag */
564 clrflag(file, MODIFIED);
565 significant = FALSE;
08746e8b
AM
566 if (!strcmp(origname, filename))
567 {
568 exitcode &= ~1;
569 }
15637ed4
RG
570
571 /* report lines & characters */
572#if MSDOS || TOS
573 bytes += nlines; /* for the inserted carriage returns */
574#endif
575 msg("Wrote \"%s\" %ld lines, %ld characters", filename, nlines, bytes);
576
577 /* close the file */
578 close(fd);
579
580 return TRUE;
581}
582
583
584/* This function deletes the temporary file. If the file has been modified
585 * and "bang" is FALSE, then it returns FALSE without doing anything; else
586 * it returns TRUE.
587 *
588 * If the "autowrite" option is set, then instead of returning FALSE when
589 * the file has been modified and "bang" is false, it will call tmpend().
590 */
591int tmpabort(bang)
592 int bang;
593{
594 /* if there is no file, return successfully */
595 if (tmpfd < 0)
596 {
597 return TRUE;
598 }
599
600 /* see if we must return FALSE -- can't quit */
601 if (!bang && tstflag(file, MODIFIED))
602 {
603 /* if "autowrite" is set, then act like tmpend() */
604 if (*o_autowrite)
605 return tmpend(bang);
606 else
607 return FALSE;
608 }
609
610 /* delete the tmp file */
611 cutswitch();
612 strcpy(prevorig, origname);
613 prevline = markline(cursor);
614 *origname = '\0';
615 origtime = 0L;
616 blkinit();
617 nlines = 0;
618 initflags();
15637ed4
RG
619 return TRUE;
620}
621
622/* This function saves the file if it has been modified, and then deletes
623 * the temporary file. Returns TRUE if successful, or FALSE if the file
624 * needs to be saved but can't be. When it returns FALSE, it will not have
625 * deleted the tmp file, either.
626 */
627int tmpend(bang)
628 int bang;
629{
630 /* save the file if it has been modified */
631 if (tstflag(file, MODIFIED) && !tmpsave((char *)0, FALSE) && !bang)
632 {
633 return FALSE;
634 }
635
636 /* delete the tmp file */
637 tmpabort(TRUE);
638
639 return TRUE;
640}
641
642
643/* If the tmp file has been changed, then this function will force those
644 * changes to be written to the disk, so that the tmp file will survive a
645 * system crash or power failure.
646 */
647#if AMIGA || MSDOS || TOS
648sync()
649{
650 /* MS-DOS and TOS don't flush their buffers until the file is closed,
651 * so here we close the tmp file and then immediately reopen it.
652 */
653 close(tmpfd);
654 tmpfd = open(tmpname, O_RDWR | O_BINARY);
655 return 0;
656}
657#endif
658
659
660/* This function stores the file's name in the second block of the temp file.
661 * SLEAZE ALERT! SLEAZE ALERT! The "tmpblk" buffer is probably being used
662 * to store the arguments to a command, so we can't use it here. Instead,
663 * we'll borrow the buffer that is used for "shift-U".
664 */
08746e8b 665int
15637ed4
RG
666storename(name)
667 char *name; /* the name of the file - normally origname */
668{
669#ifndef CRUNCH
670 int len;
671 char *ptr;
672#endif
673
674 /* we're going to clobber the U_text buffer, so reset U_line */
675 U_line = 0L;
676
677 if (!name)
678 {
679 strncpy(U_text, "", BLKSIZE);
680 U_text[1] = 127;
681 }
682#ifndef CRUNCH
08746e8b
AM
683# if TOS || MINT || MSDOS || AMIGA
684 else if (*name != '/' && *name != '\\' && !(*name && name[1] == ':'))
685# else
15637ed4 686 else if (*name != SLASH)
08746e8b 687# endif
15637ed4
RG
688 {
689 /* get the directory name */
690 ptr = getcwd(U_text, BLKSIZE);
691 if (ptr != U_text)
692 {
693 strcpy(U_text, ptr);
694 }
695
696 /* append a slash to the directory name */
697 len = strlen(U_text);
698 U_text[len++] = SLASH;
699
700 /* append the filename, padded with heaps o' NULs */
701 strncpy(U_text + len, *name ? name : "foo", BLKSIZE - len);
702 }
703#endif
704 else
705 {
706 /* copy the filename into U_text */
707 strncpy(U_text, *name ? name : "foo", BLKSIZE);
708 }
709
710 if (tmpfd >= 0)
711 {
712 /* write the name out to second block of the temp file */
713 lseek(tmpfd, (long)BLKSIZE, 0);
08746e8b
AM
714 if (write(tmpfd, U_text, (unsigned)BLKSIZE) < BLKSIZE)
715 {
716 FAIL("Error stuffing name \"%s\" into temp file", U_text);
717 }
15637ed4
RG
718 }
719 return 0;
720}
721
722
723
724/* This function handles deadly signals. It restores sanity to the terminal
725 * preserves the current temp file, and deletes any old temp files.
726 */
08746e8b 727SIGTYPE deathtrap(sig)
15637ed4
RG
728 int sig; /* the deadly signal that we caught */
729{
730 char *why;
731
732 /* restore the terminal's sanity */
733 endwin();
734
735#ifdef CRUNCH
736 why = "-Elvis died";
737#else
738 /* give a more specific description of how Elvis died */
739 switch (sig)
740 {
741# ifdef SIGHUP
742 case SIGHUP: why = "-the modem lost its carrier"; break;
743# endif
744# ifndef DEBUG
745# ifdef SIGILL
746 case SIGILL: why = "-Elvis hit an illegal instruction"; break;
747# endif
748# ifdef SIGBUS
749 case SIGBUS: why = "-Elvis had a bus error"; break;
750# endif
08746e8b
AM
751# ifdef SIGSEGV
752# if !TOS
15637ed4 753 case SIGSEGV: why = "-Elvis had a segmentation violation"; break;
08746e8b 754# endif
15637ed4
RG
755# endif
756# ifdef SIGSYS
757 case SIGSYS: why = "-Elvis munged a system call"; break;
758# endif
759# endif /* !DEBUG */
760# ifdef SIGPIPE
761 case SIGPIPE: why = "-the pipe reader died"; break;
762# endif
763# ifdef SIGTERM
764 case SIGTERM: why = "-Elvis was terminated"; break;
765# endif
766# if !MINIX
767# ifdef SIGUSR1
768 case SIGUSR1: why = "-Elvis was killed via SIGUSR1"; break;
769# endif
770# ifdef SIGUSR2
771 case SIGUSR2: why = "-Elvis was killed via SIGUSR2"; break;
772# endif
773# endif
774 default: why = "-Elvis died"; break;
775 }
776#endif
777
778 /* if we had a temp file going, then preserve it */
779 if (tmpnum > 0 && tmpfd >= 0)
780 {
781 close(tmpfd);
782 sprintf(tmpblk.c, "%s \"%s\" %s", PRESERVE, why, tmpname);
783 system(tmpblk.c);
784 }
785
786 /* delete any old temp files */
787 cutend();
788
789 /* exit with the proper exit status */
790 exit(sig);
791}