13979Smark /* Copyright (c) 1981 Regents of the University of California */
2*3995Smark static char *sccsid = "@(#)ex3.7preserve.c	7.2	07/09/81";
3460Smark #include <stdio.h>
4460Smark #include <ctype.h>
5460Smark #include <sys/types.h>
6460Smark #include <sys/stat.h>
7460Smark #include <sys/dir.h>
8460Smark #include <pwd.h>
9460Smark #include "local/uparm.h"
103979Smark 				/* mjm: "/tmp" --> TMP */
11*3995Smark #define TMP	"/tmp"
12460Smark 
13483Smark #ifdef VMUNIX
14483Smark #define	HBLKS	2
15483Smark #endif
16483Smark 
17*3995Smark char xstr[1];			/* make loader happy */
18*3995Smark 
19460Smark /*
20460Smark  * Expreserve - preserve a file in usrpath(preserve)
21460Smark  * Bill Joy UCB November 13, 1977
22460Smark  *
23460Smark  * This routine is very naive - it doesn't remove anything from
24*3995Smark  * usrpath(preserve)... this may mean that we  * stuff there... the danger in doing anything with usrpath(preserve)
25460Smark  * is that the clock may be screwed up and we may get confused.
26460Smark  *
27460Smark  * We are called in two ways - first from the editor with no argumentss
28460Smark  * and the standard input open on the temp file. Second with an argument
29460Smark  * to preserve the entire contents of /tmp (root only).
30460Smark  *
31460Smark  * BUG: should do something about preserving Rx... (register contents)
32460Smark  *      temporaries.
33460Smark  */
34460Smark 
35483Smark #ifndef VMUNIX
36460Smark #define	LBLKS	125
37483Smark #else
38483Smark #define	LBLKS	900
39483Smark #endif
40460Smark #define	FNSIZE	128
41460Smark 
42460Smark struct 	header {
43460Smark 	time_t	Time;			/* Time temp file last updated */
44460Smark 	short	Uid;			/* This users identity */
45483Smark #ifndef VMUNIX
46460Smark 	short	Flines;			/* Number of lines in file */
47483Smark #else
48483Smark 	int	Flines;
49483Smark #endif
50460Smark 	char	Savedfile[FNSIZE];	/* The current file name */
51460Smark 	short	Blocks[LBLKS];		/* Blocks where line pointers stashed */
52460Smark } H;
53460Smark 
54460Smark #ifdef	lint
55460Smark #define	ignore(a)	Ignore(a)
56460Smark #define	ignorl(a)	Ignorl(a)
57460Smark #else
58460Smark #define	ignore(a)	a
59460Smark #define	ignorl(a)	a
60460Smark #endif
61460Smark 
62460Smark struct	passwd *getpwuid();
63460Smark off_t	lseek();
64460Smark FILE	*popen();
65460Smark 
66460Smark #define eq(a, b) strcmp(a, b) == 0
67460Smark 
68460Smark main(argc)
69460Smark 	int argc;
70460Smark {
71460Smark 	register FILE *tf;
72460Smark 	struct direct dirent;
73460Smark 	struct stat stbuf;
74460Smark 
75460Smark 	/*
76460Smark 	 * If only one argument, then preserve the standard input.
77460Smark 	 */
78460Smark 	if (argc == 1) {
79460Smark 		if (copyout((char *) 0))
80460Smark 			exit(1);
81460Smark 		exit(0);
82460Smark 	}
83460Smark 
84460Smark 	/*
85460Smark 	 * If not super user, then can only preserve standard input.
86460Smark 	 */
87460Smark 	if (getuid()) {
88460Smark 		fprintf(stderr, "NOT super user\n");
89460Smark 		exit(1);
90460Smark 	}
91460Smark 
92460Smark 	/*
93460Smark 	 * ... else preserve all the stuff in /tmp, removing
94460Smark 	 * it as we go.
95460Smark 	 */
963979Smark 	if (chdir(TMP) < 0) {
973979Smark 		perror(TMP);
98*3995Smark 		exit(1);
99*3995Smark 	}
100460Smark 
101460Smark 	tf = fopen(".", "r");
102460Smark 	if (tf == NULL) {
103*3995Smark 		perror(TMP);
104460Smark 		exit(1);
105*3995Smark 	}
106460Smark 	while (fread((char *) &dirent, sizeof dirent, 1, tf) == 1) {
107460Smark 		if (dirent.d_ino == 0)
108460Smark 			continue;
109460Smark 		/*
110460Smark 		 * Ex temporaries must begin with Ex;
111460Smark 		 * we check that the 10th character of the name is null
112460Smark 		 * so we won't have to worry about non-null terminated names
113460Smark 		 * later on.
114460Smark 		 */
115460Smark 		if (dirent.d_name[0] != 'E' || dirent.d_name[1] != 'x' || dirent.d_name[10])
116460Smark 			continue;
117460Smark 		if (stat(dirent.d_name, &stbuf))
118460Smark 			continue;
119460Smark 		if ((stbuf.st_mode & S_IFMT) != S_IFREG)
120460Smark 			continue;
121460Smark 		/*
122460Smark 		 * Save the bastard.
123460Smark 		 */
124460Smark 		ignore(copyout(dirent.d_name));
125460Smark 	}
126460Smark 	exit(0);
127460Smark }
128460Smark 
129460Smark char	pattern[] =	usrpath(preserve/Exaa`XXXXX);
130460Smark 
131460Smark /*
132460Smark  * Copy file name into usrpath(preserve)/...
133460Smark  * If name is (char *) 0, then do the standard input.
134460Smark  * We make some checks on the input to make sure it is
135460Smark  * really an editor temporary, generate a name for the
136460Smark  * file (this is the slowest thing since we must stat
137460Smark  * to find a unique name), and finally copy the file.
138460Smark  */
139460Smark copyout(name)
140460Smark 	char *name;
141460Smark {
142460Smark 	int i;
143460Smark 	static int reenter;
144460Smark 	char buf[BUFSIZ];
145460Smark 
146460Smark 	/*
147460Smark 	 * The first time we put in the digits of our
148460Smark 	 * process number at the end of the pattern.
149460Smark 	 */
150460Smark 	if (reenter == 0) {
151460Smark 		mkdigits(pattern);
152460Smark 		reenter++;
153460Smark 	}
154460Smark 
155460Smark 	/*
156460Smark 	 * If a file name was given, make it the standard
157460Smark 	 * input if possible.
158460Smark 	 */
159460Smark 	if (name != 0) {
160460Smark 		ignore(close(0));
161460Smark 		/*
162460Smark 		 * Need read/write access for arcane reasons
163460Smark 		 * (see below).
164460Smark 		 */
165460Smark 		if (open(name, 2) < 0)
166460Smark 			return (-1);
167460Smark 	}
168460Smark 
169460Smark 	/*
170460Smark 	 * Get the header block.
171460Smark 	 */
172460Smark 	ignorl(lseek(0, 0l, 0));
173460Smark 	if (read(0, (char *) &H, sizeof H) != sizeof H) {
174460Smark format:
175460Smark 		if (name == 0)
1761540Smark 			fprintf(stderr, "Buffer format error\t");
177460Smark 		return (-1);
178460Smark 	}
179460Smark 
180460Smark 	/*
181460Smark 	 * Consistency checsks so we don't copy out garbage.
182460Smark 	 */
183460Smark 	if (H.Flines < 0) {
184460Smark #ifdef DEBUG
185460Smark 		fprintf(stderr, "Negative number of lines\n");
186460Smark #endif
187460Smark 		goto format;
188460Smark 	}
189483Smark 	if (H.Blocks[0] != HBLKS || H.Blocks[1] != HBLKS+1) {
190460Smark #ifdef DEBUG
191460Smark 		fprintf(stderr, "Blocks %d %d\n", H.Blocks[0], H.Blocks[1]);
192460Smark #endif
193460Smark 		goto format;
194460Smark 	}
195460Smark 	if (name == 0 && H.Uid != getuid()) {
196460Smark #ifdef DEBUG
197460Smark 		fprintf(stderr, "Wrong user-id\n");
198460Smark #endif
199460Smark 		goto format;
200460Smark 	}
201460Smark 	if (lseek(0, 0l, 0)) {
202460Smark #ifdef DEBUG
203460Smark 		fprintf(stderr, "Negative number of lines\n");
204460Smark #endif
205460Smark 		goto format;
206460Smark 	}
207460Smark 
208460Smark 	/*
209460Smark 	 * If no name was assigned to the file, then give it the name
210460Smark 	 * LOST, by putting this in the header.
211460Smark 	 */
212460Smark 	if (H.Savedfile[0] == 0) {
213460Smark 		strcpy(H.Savedfile, "LOST");
214460Smark 		ignore(write(0, (char *) &H, sizeof H));
215460Smark 		H.Savedfile[0] = 0;
216460Smark 		lseek(0, 0l, 0);
217460Smark 	}
218460Smark 
219460Smark 	/*
220460Smark 	 * File is good.  Get a name and create a file for the copy.
221460Smark 	 */
222460Smark 	mknext(pattern);
223460Smark 	ignore(close(1));
224460Smark 	if (creat(pattern, 0600) < 0) {
225460Smark 		if (name == 0)
226460Smark 			perror(pattern);
227460Smark 		return (1);
228460Smark 	}
229460Smark 
230460Smark 	/*
231460Smark 	 * Make the target be owned by the owner of the file.
232460Smark 	 */
233460Smark 	ignore(chown(pattern, H.Uid, 0));
234460Smark 
235460Smark 	/*
236460Smark 	 * Copy the file.
237460Smark 	 */
238460Smark 	for (;;) {
239460Smark 		i = read(0, buf, BUFSIZ);
240460Smark 		if (i < 0) {
241460Smark 			if (name)
242460Smark 				perror("Buffer read error");
243460Smark 			ignore(unlink(pattern));
244460Smark 			return (-1);
245460Smark 		}
246460Smark 		if (i == 0) {
247460Smark 			if (name)
248460Smark 				ignore(unlink(name));
249460Smark 			notify(H.Uid, H.Savedfile, (int) name);
250460Smark 			return (0);
251460Smark 		}
252460Smark 		if (write(1, buf, i) != i) {
253460Smark 			if (name == 0)
254460Smark 				perror(pattern);
255460Smark 			unlink(pattern);
256460Smark 			return (-1);
257460Smark 		}
258460Smark 	}
259460Smark }
260460Smark 
261460Smark /*
262460Smark  * Blast the last 5 characters of cp to be the process number.
263460Smark  */
264460Smark mkdigits(cp)
265460Smark 	char *cp;
266460Smark {
267460Smark 	register int i, j;
268460Smark 
269460Smark 	for (i = getpid(), j = 5, cp += strlen(cp); j > 0; i /= 10, j--)
270460Smark 		*--cp = i % 10 | '0';
271460Smark }
272460Smark 
273460Smark /*
274460Smark  * Make the name in cp be unique by clobbering up to
275460Smark  * three alphabetic characters into a sequence of the form 'aab', 'aac', etc.
276460Smark  * Mktemp gets weird names too quickly to be useful here.
277460Smark  */
278460Smark mknext(cp)
279460Smark 	char *cp;
280460Smark {
281460Smark 	char *dcp;
282460Smark 	struct stat stb;
283460Smark 
284460Smark 	dcp = cp + strlen(cp) - 1;
285460Smark 	while (isdigit(*dcp))
286460Smark 		dcp--;
287460Smark whoops:
288460Smark 	if (dcp[0] == 'z') {
289460Smark 		dcp[0] = 'a';
290460Smark 		if (dcp[-1] == 'z') {
291460Smark 			dcp[-1] = 'a';
292460Smark 			if (dcp[-2] == 'z')
2931540Smark 				fprintf(stderr, "Can't find a name\t");
294460Smark 			dcp[-2]++;
295460Smark 		} else
296460Smark 			dcp[-1]++;
297460Smark 	} else
298460Smark 		dcp[0]++;
299460Smark 	if (stat(cp, &stb) == 0)
300460Smark 		goto whoops;
301460Smark }
302460Smark 
303460Smark /*
304460Smark  * Notify user uid that his file fname has been saved.
305460Smark  */
306460Smark notify(uid, fname, flag)
307460Smark 	int uid;
308460Smark 	char *fname;
309460Smark {
310460Smark 	struct passwd *pp = getpwuid(uid);
311460Smark 	register FILE *mf;
312460Smark 	char cmd[BUFSIZ];
313460Smark 
314460Smark 	if (pp == NULL)
315460Smark 		return;
316460Smark 	sprintf(cmd, "mail %s", pp->pw_name);
317460Smark 	mf = popen(cmd, "w");
318460Smark 	if (mf == NULL)
319460Smark 		return;
320460Smark 	setbuf(mf, cmd);
321460Smark 	if (fname[0] == 0) {
322460Smark 		fprintf(mf,
323460Smark "A copy of an editor buffer of yours was saved when %s.\n",
3241540Smark 		flag ? "the system went down" : "the editor was killed");
325460Smark 		fprintf(mf,
326460Smark "No name was associated with this buffer so it has been named \"LOST\".\n");
327460Smark 	} else
328460Smark 		fprintf(mf,
329460Smark "A copy of an editor buffer of your file \"%s\"\nwas saved when %s.\n", fname,
3301540Smark 		/*
3311540Smark 		 * "the editor was killed" is perhaps still not an ideal
3321540Smark 		 * error message.  Usually, either it was forcably terminated
3331540Smark 		 * or the phone was hung up, but we don't know which.
3341540Smark 		 */
3351540Smark 		flag ? "the system went down" : "the editor was killed");
336460Smark 	fprintf(mf,
337460Smark "This buffer can be retrieved using the \"recover\" command of the editor.\n");
338460Smark 	fprintf(mf,
339460Smark "An easy way to do this is to give the command \"ex -r %s\".\n",fname);
340460Smark 	fprintf(mf,
341460Smark "This works for \"edit\" and \"vi\" also.\n");
342460Smark 	pclose(mf);
343460Smark }
344460Smark 
345460Smark /*
346460Smark  *	people making love
347460Smark  *	never exactly the same
348460Smark  *	just like a snowflake
349460Smark  */
350460Smark 
351460Smark #ifdef lint
352460Smark Ignore(a)
353460Smark 	int a;
354460Smark {
355460Smark 
356460Smark 	a = a;
357460Smark }
358460Smark 
359460Smark Ignorl(a)
360460Smark 	long a;
361460Smark {
362460Smark 
363460Smark 	a = a;
364460Smark }
365460Smark #endif
366