121704Sdist /*
221704Sdist  * Copyright (c) 1980 Regents of the University of California.
321704Sdist  * All rights reserved.  The Berkeley software License Agreement
421704Sdist  * specifies the terms and conditions for redistribution.
521704Sdist  */
621704Sdist 
721704Sdist #ifndef lint
822752Sbloom char *copyright =
921704Sdist "@(#) Copyright (c) 1980 Regents of the University of California.\n\
1021704Sdist  All rights reserved.\n";
1121704Sdist #endif not lint
1221704Sdist 
1321704Sdist #ifndef lint
14*37883Sbostic static char *sccsid = "@(#)ex3.7preserve.c	7.14 (Berkeley) 05/11/89";
1521704Sdist #endif not lint
1621704Sdist 
1712956Sralph #include <sys/param.h>
18460Smark #include <sys/stat.h>
19460Smark #include <sys/dir.h>
20460Smark #include <pwd.h>
21*37883Sbostic #include <stdio.h>
22*37883Sbostic #include <ctype.h>
23*37883Sbostic #include "pathnames.h"
2413759Ssam 
25483Smark #ifdef VMUNIX
26483Smark #define	HBLKS	2
274001Smark #else
284001Smark #define HBLKS	1
29483Smark #endif
30483Smark 
313995Smark char xstr[1];			/* make loader happy */
323995Smark 
33460Smark /*
34*37883Sbostic  * Expreserve - preserve a file in _PATH_PRESERVE.
35460Smark  * Bill Joy UCB November 13, 1977
36460Smark  *
37460Smark  * This routine is very naive - it doesn't remove anything from
38*37883Sbostic  * _PATH_PRESERVE... this may mean that we leave
39*37883Sbostic  * stuff there... the danger in doing anything with _PATH_PRESERVE
40460Smark  * is that the clock may be screwed up and we may get confused.
41460Smark  *
42460Smark  * We are called in two ways - first from the editor with no argumentss
43460Smark  * and the standard input open on the temp file. Second with an argument
44460Smark  * to preserve the entire contents of /tmp (root only).
45460Smark  *
46460Smark  * BUG: should do something about preserving Rx... (register contents)
47460Smark  *      temporaries.
48460Smark  */
49460Smark 
50483Smark #ifndef VMUNIX
51460Smark #define	LBLKS	125
52483Smark #else
53483Smark #define	LBLKS	900
54483Smark #endif
55460Smark #define	FNSIZE	128
56460Smark 
57460Smark struct 	header {
58460Smark 	time_t	Time;			/* Time temp file last updated */
594051Smark 	int	Uid;			/* This users identity */
60483Smark #ifndef VMUNIX
61460Smark 	short	Flines;			/* Number of lines in file */
62483Smark #else
63483Smark 	int	Flines;
64483Smark #endif
65460Smark 	char	Savedfile[FNSIZE];	/* The current file name */
66460Smark 	short	Blocks[LBLKS];		/* Blocks where line pointers stashed */
67460Smark } H;
68460Smark 
69460Smark #ifdef	lint
70460Smark #define	ignore(a)	Ignore(a)
71460Smark #define	ignorl(a)	Ignorl(a)
72460Smark #else
73460Smark #define	ignore(a)	a
74460Smark #define	ignorl(a)	a
75460Smark #endif
76460Smark 
77460Smark struct	passwd *getpwuid();
78460Smark off_t	lseek();
79460Smark FILE	*popen();
80460Smark 
81460Smark #define eq(a, b) strcmp(a, b) == 0
82460Smark 
83460Smark main(argc)
84460Smark 	int argc;
85460Smark {
8612956Sralph 	register DIR *tf;
8712956Sralph 	struct direct *dirent;
88460Smark 	struct stat stbuf;
89460Smark 
90460Smark 	/*
91460Smark 	 * If only one argument, then preserve the standard input.
92460Smark 	 */
93460Smark 	if (argc == 1) {
94460Smark 		if (copyout((char *) 0))
95460Smark 			exit(1);
96460Smark 		exit(0);
97460Smark 	}
98460Smark 
99460Smark 	/*
100460Smark 	 * If not super user, then can only preserve standard input.
101460Smark 	 */
102460Smark 	if (getuid()) {
103460Smark 		fprintf(stderr, "NOT super user\n");
104460Smark 		exit(1);
105460Smark 	}
106460Smark 
107460Smark 	/*
108460Smark 	 * ... else preserve all the stuff in /tmp, removing
109460Smark 	 * it as we go.
110460Smark 	 */
111*37883Sbostic 	if (chdir(_PATH_TMP) < 0) {
112*37883Sbostic 		perror(_PATH_TMP);
1133995Smark 		exit(1);
1143995Smark 	}
115460Smark 
11612956Sralph 	tf = opendir(".");
117460Smark 	if (tf == NULL) {
118*37883Sbostic 		perror(_PATH_TMP);
119460Smark 		exit(1);
1203995Smark 	}
12112956Sralph 	while ((dirent = readdir(tf)) != NULL) {
12213759Ssam 		/* Ex temporaries must begin with Ex. */
12313759Ssam 		if (dirent->d_name[0] != 'E' || dirent->d_name[1] != 'x')
124460Smark 			continue;
12512956Sralph 		if (stat(dirent->d_name, &stbuf))
126460Smark 			continue;
127460Smark 		if ((stbuf.st_mode & S_IFMT) != S_IFREG)
128460Smark 			continue;
129460Smark 		/*
130460Smark 		 * Save the bastard.
131460Smark 		 */
13212956Sralph 		ignore(copyout(dirent->d_name));
133460Smark 	}
13412956Sralph 	closedir(tf);
135460Smark 	exit(0);
136460Smark }
137460Smark 
138*37883Sbostic char	pattern[MAXPATHLEN];
139460Smark 
140460Smark /*
141*37883Sbostic  * Copy file name into pattern[].
142460Smark  * If name is (char *) 0, then do the standard input.
143460Smark  * We make some checks on the input to make sure it is
144460Smark  * really an editor temporary, generate a name for the
145460Smark  * file (this is the slowest thing since we must stat
146460Smark  * to find a unique name), and finally copy the file.
147460Smark  */
148460Smark copyout(name)
149460Smark 	char *name;
150460Smark {
151460Smark 	int i;
152460Smark 	static int reenter;
153460Smark 	char buf[BUFSIZ];
154460Smark 
155*37883Sbostic 	(void)sprintf(pattern, "%s/Exaa`XXXXX", _PATH_PRESERVE);
156460Smark 	/*
157460Smark 	 * The first time we put in the digits of our
158460Smark 	 * process number at the end of the pattern.
159460Smark 	 */
160460Smark 	if (reenter == 0) {
161460Smark 		mkdigits(pattern);
162460Smark 		reenter++;
163460Smark 	}
164460Smark 
165460Smark 	/*
166460Smark 	 * If a file name was given, make it the standard
167460Smark 	 * input if possible.
168460Smark 	 */
169460Smark 	if (name != 0) {
170460Smark 		ignore(close(0));
171460Smark 		/*
172460Smark 		 * Need read/write access for arcane reasons
173460Smark 		 * (see below).
174460Smark 		 */
175460Smark 		if (open(name, 2) < 0)
176460Smark 			return (-1);
177460Smark 	}
178460Smark 
179460Smark 	/*
180460Smark 	 * Get the header block.
181460Smark 	 */
182460Smark 	ignorl(lseek(0, 0l, 0));
183460Smark 	if (read(0, (char *) &H, sizeof H) != sizeof H) {
184460Smark format:
185460Smark 		if (name == 0)
1861540Smark 			fprintf(stderr, "Buffer format error\t");
187460Smark 		return (-1);
188460Smark 	}
189460Smark 
190460Smark 	/*
191460Smark 	 * Consistency checsks so we don't copy out garbage.
192460Smark 	 */
193460Smark 	if (H.Flines < 0) {
194460Smark #ifdef DEBUG
195460Smark 		fprintf(stderr, "Negative number of lines\n");
196460Smark #endif
197460Smark 		goto format;
198460Smark 	}
199483Smark 	if (H.Blocks[0] != HBLKS || H.Blocks[1] != HBLKS+1) {
200460Smark #ifdef DEBUG
201460Smark 		fprintf(stderr, "Blocks %d %d\n", H.Blocks[0], H.Blocks[1]);
202460Smark #endif
203460Smark 		goto format;
204460Smark 	}
205460Smark 	if (name == 0 && H.Uid != getuid()) {
206460Smark #ifdef DEBUG
207460Smark 		fprintf(stderr, "Wrong user-id\n");
208460Smark #endif
209460Smark 		goto format;
210460Smark 	}
211460Smark 	if (lseek(0, 0l, 0)) {
212460Smark #ifdef DEBUG
213460Smark 		fprintf(stderr, "Negative number of lines\n");
214460Smark #endif
215460Smark 		goto format;
216460Smark 	}
217460Smark 
218460Smark 	/*
219460Smark 	 * If no name was assigned to the file, then give it the name
220460Smark 	 * LOST, by putting this in the header.
221460Smark 	 */
222460Smark 	if (H.Savedfile[0] == 0) {
223460Smark 		strcpy(H.Savedfile, "LOST");
224460Smark 		ignore(write(0, (char *) &H, sizeof H));
225460Smark 		H.Savedfile[0] = 0;
226460Smark 		lseek(0, 0l, 0);
227460Smark 	}
228460Smark 
229460Smark 	/*
230460Smark 	 * File is good.  Get a name and create a file for the copy.
231460Smark 	 */
232460Smark 	mknext(pattern);
233460Smark 	ignore(close(1));
234460Smark 	if (creat(pattern, 0600) < 0) {
235460Smark 		if (name == 0)
236460Smark 			perror(pattern);
237460Smark 		return (1);
238460Smark 	}
239460Smark 
240460Smark 	/*
241460Smark 	 * Make the target be owned by the owner of the file.
242460Smark 	 */
243460Smark 	ignore(chown(pattern, H.Uid, 0));
244460Smark 
245460Smark 	/*
246460Smark 	 * Copy the file.
247460Smark 	 */
248460Smark 	for (;;) {
249460Smark 		i = read(0, buf, BUFSIZ);
250460Smark 		if (i < 0) {
251460Smark 			if (name)
252460Smark 				perror("Buffer read error");
253460Smark 			ignore(unlink(pattern));
254460Smark 			return (-1);
255460Smark 		}
256460Smark 		if (i == 0) {
257460Smark 			if (name)
258460Smark 				ignore(unlink(name));
25916045Srrh 			notify(H.Uid, H.Savedfile, (int) name, H.Time);
260460Smark 			return (0);
261460Smark 		}
262460Smark 		if (write(1, buf, i) != i) {
263460Smark 			if (name == 0)
264460Smark 				perror(pattern);
265460Smark 			unlink(pattern);
266460Smark 			return (-1);
267460Smark 		}
268460Smark 	}
269460Smark }
270460Smark 
271460Smark /*
272460Smark  * Blast the last 5 characters of cp to be the process number.
273460Smark  */
274460Smark mkdigits(cp)
275460Smark 	char *cp;
276460Smark {
277460Smark 	register int i, j;
278460Smark 
279460Smark 	for (i = getpid(), j = 5, cp += strlen(cp); j > 0; i /= 10, j--)
280460Smark 		*--cp = i % 10 | '0';
281460Smark }
282460Smark 
283460Smark /*
284460Smark  * Make the name in cp be unique by clobbering up to
285460Smark  * three alphabetic characters into a sequence of the form 'aab', 'aac', etc.
286460Smark  * Mktemp gets weird names too quickly to be useful here.
287460Smark  */
288460Smark mknext(cp)
289460Smark 	char *cp;
290460Smark {
291460Smark 	char *dcp;
292460Smark 	struct stat stb;
293460Smark 
294460Smark 	dcp = cp + strlen(cp) - 1;
295460Smark 	while (isdigit(*dcp))
296460Smark 		dcp--;
297460Smark whoops:
298460Smark 	if (dcp[0] == 'z') {
299460Smark 		dcp[0] = 'a';
300460Smark 		if (dcp[-1] == 'z') {
301460Smark 			dcp[-1] = 'a';
302460Smark 			if (dcp[-2] == 'z')
3031540Smark 				fprintf(stderr, "Can't find a name\t");
304460Smark 			dcp[-2]++;
305460Smark 		} else
306460Smark 			dcp[-1]++;
307460Smark 	} else
308460Smark 		dcp[0]++;
309460Smark 	if (stat(cp, &stb) == 0)
310460Smark 		goto whoops;
311460Smark }
312460Smark 
313460Smark /*
314460Smark  * Notify user uid that his file fname has been saved.
315460Smark  */
31616045Srrh notify(uid, fname, flag, time)
317460Smark 	int uid;
318460Smark 	char *fname;
31916045Srrh 	time_t	time;
320460Smark {
321460Smark 	struct passwd *pp = getpwuid(uid);
322460Smark 	register FILE *mf;
32316045Srrh 	char	cmd[BUFSIZ];
32416045Srrh 	char	hostname[128];
32516045Srrh 	char	croak[128];
32616045Srrh 	char	*timestamp, *ctime();
327460Smark 
328460Smark 	if (pp == NULL)
329460Smark 		return;
33016045Srrh 	gethostname(hostname, sizeof(hostname));
33116045Srrh 	timestamp = ctime(&time);
33216045Srrh 	timestamp[16] = 0;	/* blast from seconds on */
333*37883Sbostic 	sprintf(cmd, "%s %s", _PATH_BINMAIL, pp->pw_name);
33417462Sralph 	setuid(getuid());
335460Smark 	mf = popen(cmd, "w");
336460Smark 	if (mf == NULL)
337460Smark 		return;
338460Smark 	setbuf(mf, cmd);
33916045Srrh 	/*
34016045Srrh 	 *	flag says how the editor croaked:
34116045Srrh 	 * "the editor was killed" is perhaps still not an ideal
34216045Srrh 	 * error message.  Usually, either it was forcably terminated
34316045Srrh 	 * or the phone was hung up, but we don't know which.
34416045Srrh 	 */
34516045Srrh 	sprintf(croak, flag
34616045Srrh 		? "the system went down"
34716045Srrh 		: "the editor was killed");
348460Smark 	if (fname[0] == 0) {
34925941Sbloom 		fname = "LOST";
350460Smark 		fprintf(mf,
35116045Srrh "Subject: editor saved ``LOST''\n");
352460Smark 		fprintf(mf,
35316045Srrh "You were editing a file without a name\n");
354460Smark 		fprintf(mf,
35516045Srrh "at <%s> on the machine ``%s'' when %s.\n", timestamp, hostname, croak);
35616045Srrh 		fprintf(mf,
35716045Srrh "Since the file had no name, it has been named \"LOST\".\n");
35816045Srrh 	} else {
35916045Srrh 		fprintf(mf,
36016045Srrh "Subject: editor saved ``%s''\n", fname);
36116045Srrh 		fprintf(mf,
36216045Srrh "You were editing the file \"%s\"\n", fname);
36316045Srrh 		fprintf(mf,
36416045Srrh "at <%s> on the machine ``%s''\n", timestamp, hostname);
36516045Srrh 		fprintf(mf,
36616045Srrh "when %s.\n", croak);
36716045Srrh 	}
368460Smark 	fprintf(mf,
36916045Srrh "\nYou can retrieve most of your changes to this file\n");
370460Smark 	fprintf(mf,
37116045Srrh "using the \"recover\" command of the editor.\n");
372460Smark 	fprintf(mf,
37316045Srrh "An easy way to do this is to give the command \"vi -r %s\".\n", fname);
37416045Srrh 	fprintf(mf,
37516045Srrh "This method also works using \"ex\" and \"edit\".\n");
376460Smark 	pclose(mf);
377460Smark }
378460Smark 
379460Smark /*
380460Smark  *	people making love
381460Smark  *	never exactly the same
382460Smark  *	just like a snowflake
383460Smark  */
384460Smark 
385460Smark #ifdef lint
386460Smark Ignore(a)
387460Smark 	int a;
388460Smark {
389460Smark 
390460Smark 	a = a;
391460Smark }
392460Smark 
393460Smark Ignorl(a)
394460Smark 	long a;
395460Smark {
396460Smark 
397460Smark 	a = a;
398460Smark }
399460Smark #endif
400