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