1 /* tmpfile.c */
2
3 /* Author:
4 * Steve Kirkendall
5 * 16820 SW Tallac Way
6 * Beaverton, OR 97006
7 * kirkenda@jove.cs.pdx.edu, or ...uunet!tektronix!psueea!jove!kirkenda
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 #include <sys/stat.h>
20 #endif
21
22 /* The FAIL() macro prints an error message and then exits. */
23 #define FAIL(why,arg) mode = MODE_EX; msg(why, arg); endwin(); HZ_abort(9)
24 #define FAIL2(why,arg1,arg2) mode = MODE_EX; msg(why, arg1, arg2); endwin(); HZ_abort(9)
25
26 /* This is the name of the temp file */
27 static char tmpname[80];
28
29 /* This function creates the temp file and copies the original file into it.
30 * Returns if successful, or stops execution if it fails.
31 */
tmpstart(filename)32 int tmpstart(filename)
33 char *filename; /* name of the original file */
34 {
35 int origfd; /* fd used for reading the original file */
36 struct stat statb; /* stat buffer, used to examine inode */
37 register BLK *this; /* pointer to the current block buffer */
38 register BLK *next; /* pointer to the next block buffer */
39 int inbuf; /* number of characters in a buffer */
40 int nread; /* number of bytes read */
41 #ifdef FASTLOAD
42 int kin; /* number of bytes in kbuf */
43 int kout; /* index into kbuf of where to take bytes */
44 #endif
45 register int j, k;
46 int i;
47
48 /* switching to a different file certainly counts as a change */
49 changes++;
50 redraw(MARK_UNSET, FALSE);
51
52 /* open the original file for reading */
53 *origname = '\0';
54 if (filename && *filename)
55 {
56 strcpy(origname, filename);
57 origfd = open(origname, O_RDONLY);
58 if (origfd < 0 && errno != ENOENT)
59 {
60 FAIL("Can't open \"%s\"", origname);
61 }
62 if (origfd >= 0)
63 {
64 #if TURBOC || TOS
65 if (stat(origname, &statb) < 0)
66 #else
67 if (fstat(origfd, &statb) < 0)
68 #endif
69 {
70 FAIL("Can't stat \"%s\"", origname);
71 }
72 #if TOS
73 if (origfd >= 0 && (statb.st_mode & S_IJDIR))
74 #else
75 if (origfd >= 0 && (statb.st_mode & S_IFMT) != S_IFREG)
76 #endif
77 {
78 FAIL("\"%s\" is not a regular file", origname);
79 }
80 }
81 else
82 {
83 stat(".", &statb);
84 }
85 if (origfd >= 0)
86 {
87 origtime = statb.st_mtime;
88 #if MSDOS
89 if (*o_readonly || !(statb.st_mode & S_IWRITE))
90 #endif
91 #if TOS
92 if (*o_readonly || (statb.st_mode & S_IJRON))
93 #endif
94 #if OS9
95 /* if we don't have write permission... */
96 #endif
97 #if ANY_UNIX
98 if (*o_readonly || !(statb.st_mode &
99 (statb.st_uid != geteuid() ? 0022 : 0200)))
100 #endif
101 {
102 setflag(file, READONLY);
103 }
104 }
105 else
106 {
107 origtime = 0L;
108 }
109 }
110 else
111 {
112 setflag(file, NOFILE);
113 origfd = -1;
114 origtime = 0L;
115 stat(".", &statb);
116 }
117
118 /* make a name for the tmp file */
119 #if MSDOS || TOS
120 /* MS-Dos doesn't allow multiple slashes, but supports drives
121 * with current directories.
122 * This relies on TMPNAME beginning with "%s\\"!!!!
123 */
124 strcpy(tmpname, o_directory);
125 if ((i = strlen(tmpname)) && !strchr(":/\\", tmpname[i-1]))
126 tmpname[i++]=SLASH;
127 sprintf(tmpname+i, TMPNAME+3, statb.st_ino, statb.st_dev);
128 #else
129 #ifdef ORIGINAL
130 sprintf(tmpname, TMPNAME, o_directory, statb.st_ino, statb.st_dev);
131 #else /*ORIGINAL*/
132 { int pid = getpid() ;
133 sprintf(tmpname, TMPNAME, o_directory, pid, pid);
134 }
135 #endif/*ORIGINAL*/
136 #endif
137
138 /* make sure nobody else is editing the same file */
139 if (access(tmpname, 0) == 0)
140 {
141 FAIL("\"%s\" is busy", tmpname);
142 }
143
144 /* create the temp file */
145 #if ANY_UNIX
146 close(creat(tmpname, 0600));
147 #else
148 close(creat(tmpname, 0666));
149 #endif
150 tmpfd = open(tmpname, O_RDWR | O_BINARY);
151 if (tmpfd < 0)
152 {
153 FAIL2("Can't create temporary file (%s), errno=%d", tmpname, errno);
154 return 1;
155 }
156
157 /* allocate space for the header in the file */
158 write(tmpfd, hdr.c, BLKSIZE);
159
160 #ifndef NO_RECYCLE
161 /* initialize the block allocator */
162 /* This must already be done here, before the first attempt
163 * to write to the new file! GB */
164 garbage();
165 #endif
166
167 /* initialize lnum[] */
168 for (i = 1; i < MAXBLKS; i++)
169 {
170 lnum[i] = INFINITY;
171 }
172 lnum[0] = 0;
173
174 /* if there is no original file, then create a 1-line file */
175 if (origfd < 0)
176 {
177 hdr.n[0] = 0; /* invalid inode# denotes new file */
178
179 this = blkget(1); /* get the new text block */
180 strcpy(this->c, "\n"); /* put a line in it */
181
182 lnum[1] = 1; /* block 1 ends with line 1 */
183 nlines = 1; /* there is 1 line in the file */
184
185 if (*origname)
186 {
187 msg("\"%s\" [NEW FILE] 1 line", origname);
188 }
189 else
190 {
191 msg("\"[NO FILE]\" 1 line");
192 }
193 }
194 else /* there is an original file -- read it in */
195 {
196 hdr.n[0] = statb.st_ino;
197 nlines = 0;
198
199 /* preallocate 1 "next" buffer */
200 i = 1;
201 next = blkget(i);
202 inbuf = 0;
203
204 /* loop, moving blocks from orig to tmp */
205 #ifdef FASTLOAD
206 kin = kout = 0;
207 #endif
208 for (;;)
209 {
210 /* "next" buffer becomes "this" buffer */
211 this = next;
212
213 /* read [more] text into this block */
214 do
215 {
216 #ifdef FASTLOAD
217 if (kout >= kin)
218 {
219 kout = 0;
220 kin = tread(origfd, kbuf, KBSIZ);
221 }
222 nread = kin - kout;
223 if (nread > BLKSIZE - 1 - inbuf)
224 nread = BLKSIZE - 1 - inbuf;
225 if (nread > 0)
226 {
227 memcpy(&this->c[inbuf], &kbuf[kout], nread);
228 kout += nread;
229 }
230 #else
231 nread = tread(origfd, &this->c[inbuf], BLKSIZE - 1 - inbuf);
232 #endif
233 if (nread < 0)
234 {
235 close(origfd);
236 close(tmpfd);
237 tmpfd = -1;
238 unlink(tmpname);
239 FAIL("Error reading \"%s\"", origname);
240 }
241
242 /* convert NUL characters to something else */
243 for (k = inbuf; k < inbuf + nread; k++)
244 {
245 if (!this->c[k])
246 {
247 setflag(file, HADNUL);
248 this->c[k] = 0x80;
249 }
250 }
251 inbuf += nread;
252
253 /* if the buffer is empty, quit */
254 if (inbuf == 0)
255 {
256 goto FoundEOF;
257 }
258
259 } while (0 /* nread > 0 && inbuf < BLKSIZE - 2 */ );
260 #if MSDOS || TOS
261 /* BAH! MS text mode read fills inbuf, then compresses eliminating \r
262 but leaving garbage at end of buf. The same is true for TURBOC. GB. */
263
264 memset(this->c + inbuf, '\0', BLKSIZE - inbuf);
265 #endif
266
267 /* search backward for last newline */
268 for (k = inbuf; --k >= 0 && this->c[k] != '\n';)
269 {
270 }
271 if (k++ < 0)
272 {
273 if (inbuf >= BLKSIZE - 1)
274 {
275 k = 80;
276 }
277 else
278 {
279 k = inbuf;
280 }
281 }
282
283 /* allocate next buffer */
284 next = blkget(++i);
285
286 /* move fragmentary last line to next buffer */
287 inbuf -= k;
288 for (j = 0; k < BLKSIZE; j++, k++)
289 {
290 next->c[j] = this->c[k];
291 this->c[k] = 0;
292 }
293
294 /* if necessary, add a newline to this buf */
295 for (k = BLKSIZE - inbuf; --k >= 0 && !this->c[k]; )
296 {
297 }
298 if (this->c[k] != '\n')
299 {
300 setflag(file, ADDEDNL);
301 this->c[k + 1] = '\n';
302 }
303
304 /* count the lines in this block */
305 for (k = 0; k < BLKSIZE && this->c[k]; k++)
306 {
307 if (this->c[k] == '\n')
308 {
309 nlines++;
310 }
311 }
312 lnum[i - 1] = nlines;
313 }
314 FoundEOF:
315
316 /* if this is a zero-length file, add 1 line */
317 if (nlines == 0)
318 {
319 this = blkget(1); /* get the new text block */
320 strcpy(this->c, "\n"); /* put a line in it */
321
322 lnum[1] = 1; /* block 1 ends with line 1 */
323 nlines = 1; /* there is 1 line in the file */
324 }
325
326 /* report the number of lines in the file */
327 msg("\"%s\" %s %ld line%s",
328 origname,
329 (tstflag(file, READONLY) ? "[READONLY]" : ""),
330 nlines,
331 nlines == 1 ? "" : "s");
332 }
333
334 /* initialize the cursor to start of line 1 */
335 cursor = MARK_FIRST;
336
337 /* close the original file */
338 close(origfd);
339
340 return 0;
341 }
342
343
344
345 /* This function copies the temp file back onto an original file.
346 * Returns TRUE if successful, or FALSE if the file could NOT be saved.
347 */
tmpsave(filename,bang)348 tmpsave(filename, bang)
349 char *filename; /* the name to save it to */
350 int bang; /* forced write? */
351 {
352 int fd; /* fd of the file we're writing to */
353 register int len; /* length of a text block */
354 register BLK *this; /* a text block */
355 long bytes; /* byte counter */
356 register int i;
357
358 /* if no filename is given, assume the original file name */
359 if (!filename || !*filename)
360 {
361 filename = origname;
362 }
363
364 /* if readonly variable is set, then we may not write */
365 if (*o_readonly && !bang)
366 {
367 msg("File is read only");
368 return FALSE;
369 }
370
371 /* if still no file name, then fail */
372 if (!*filename)
373 {
374 msg("Don't know a name for this file -- NOT WRITTEN");
375 return FALSE;
376 }
377
378 /* open the file */
379 if (*filename == '>' && filename[1] == '>')
380 {
381 filename += 2;
382 while (*filename == ' ' || *filename == '\t')
383 {
384 filename++;
385 }
386 #ifdef O_APPEND
387 fd = open(filename, O_WRONLY|O_APPEND);
388 #else
389 fd = open(filename, O_WRONLY);
390 lseek(fd, 0L, 2);
391 #endif
392 }
393 else
394 {
395 /* either the file must not exist, or it must be the original
396 * file, or we must have a bang
397 */
398 if (strcmp(filename, origname) && access(filename, 0) == 0 && !bang)
399 {
400 msg("File already exists - Use :w! to overwrite");
401 return FALSE;
402 }
403 fd = creat(filename, 0666);
404 }
405 if (fd < 0)
406 {
407 msg("Can't write to \"%s\" -- NOT WRITTEN", filename);
408 return FALSE;
409 }
410
411 /* write each text block to the file */
412 bytes = 0L;
413 for (i = 1; i < MAXBLKS && (this = blkget(i)) && this->c[0]; i++)
414 {
415 for (len = 0; len < BLKSIZE && this->c[len]; len++)
416 {
417 }
418 twrite(fd, this->c, len);
419 bytes += len;
420 }
421
422 /* reset the "modified" flag */
423 clrflag(file, MODIFIED);
424
425 /* report lines & characters */
426 #if MSDOS || TOS
427 bytes += nlines; /* for the inserted carriage returns */
428 #endif
429 if (strncmp(filename, o_directory, strlen(o_directory)))
430 {
431 msg("Wrote \"%s\" %ld lines, %ld characters", filename, nlines, bytes);
432 }
433
434 /* close the file */
435 close(fd);
436
437 return TRUE;
438 }
439
440
441 /* This function deletes the temporary file. If the file has been modified
442 * and "bang" is FALSE, then it returns FALSE without doing anything; else
443 * it returns TRUE.
444 *
445 * If the "autowrite" option is set, then instead of returning FALSE when
446 * the file has been modified and "bang" is false, it will call tmpend().
447 */
tmpabort(bang)448 tmpabort(bang)
449 int bang;
450 {
451 /* if there is no file, return successfully */
452 if (tmpfd < 0)
453 {
454 return TRUE;
455 }
456
457 /* see if we must return FALSE -- can't quit */
458 if (!bang && tstflag(file, MODIFIED))
459 {
460 /* if "autowrite" is set, then act like tmpend() */
461 if (*o_autowrite)
462 return tmpend(bang);
463 else
464 return FALSE;
465 }
466
467 /* delete the tmp file */
468 cutswitch(tmpname);
469 close(tmpfd);
470 tmpfd = -1;
471 unlink(tmpname);
472 strcpy(prevorig, origname);
473 prevline = markline(cursor);
474 *origname = '\0';
475 origtime = 0L;
476 blkinit();
477 nlines = 0;
478 initflags();
479 return TRUE;
480 }
481
482 /* This function saves the file if it has been modified, and then deletes
483 * the temporary file. Returns TRUE if successful, or FALSE if the file
484 * needs to be saved but can't be. When it returns FALSE, it will not have
485 * deleted the tmp file, either.
486 */
tmpend(bang)487 tmpend(bang)
488 int bang;
489 {
490 /* save the file if it has been modified */
491 if (tstflag(file, MODIFIED) && !tmpsave((char *)0, FALSE) && !bang)
492 {
493 return FALSE;
494 }
495
496 /* delete the tmp file */
497 tmpabort(TRUE);
498
499 return TRUE;
500 }
501
502
503 /* If the tmp file has been changed, then this function will force those
504 * changes to be written to the disk, so that the tmp file will survive a
505 * system crash or power failure.
506 */
507 #if MSDOS || TOS || OS9
sync()508 sync()
509 {
510 # if OS9
511 /* OS-9 doesn't need an explicit sync operation, but the linker
512 * demands something called sync(), so this is a dummy function.
513 */
514 #else
515 /* MS-DOS and TOS don't flush their buffers until the file is closed,
516 * so here we close the tmp file and then immediately reopen it.
517 */
518 close(tmpfd);
519 tmpfd = open(tmpname, O_RDWR | O_BINARY);
520 #endif
521 }
522 #endif
523