1 /*
2  * Copyright (c) 1980 Regents of the University of California.
3  * All rights reserved.  The Berkeley software License Agreement
4  * specifies the terms and conditions for redistribution.
5  */
6 
7 #ifndef lint
8 char *copyright =
9 "@(#) Copyright (c) 1980 Regents of the University of California.\n\
10  All rights reserved.\n";
11 #endif not lint
12 
13 #ifndef lint
14 static char *sccsid = "@(#)ex3.7preserve.c	7.16 (Berkeley) 12/29/90";
15 #endif not lint
16 
17 #include <sys/param.h>
18 #include <sys/stat.h>
19 #include <sys/dir.h>
20 #include <pwd.h>
21 #include <stdio.h>
22 #include <ctype.h>
23 #include "pathnames.h"
24 
25 #ifdef VMUNIX
26 #define	HBLKS	2
27 #else
28 #define HBLKS	1
29 #endif
30 
31 char xstr[1];			/* make loader happy */
32 
33 /*
34  * Expreserve - preserve a file in _PATH_PRESERVE.
35  * Bill Joy UCB November 13, 1977
36  *
37  * This routine is very naive - it doesn't remove anything from
38  * _PATH_PRESERVE... this may mean that we leave
39  * stuff there... the danger in doing anything with _PATH_PRESERVE
40  * is that the clock may be screwed up and we may get confused.
41  *
42  * We are called in two ways - first from the editor with no argumentss
43  * and the standard input open on the temp file. Second with an argument
44  * to preserve the entire contents of /tmp (root only).
45  *
46  * BUG: should do something about preserving Rx... (register contents)
47  *      temporaries.
48  */
49 
50 #ifndef VMUNIX
51 #define	LBLKS	125
52 #else
53 #define	LBLKS	900
54 #endif
55 #define	FNSIZE	128
56 
57 struct 	header {
58 	time_t	Time;			/* Time temp file last updated */
59 	int	Uid;			/* This users identity */
60 #ifndef VMUNIX
61 	short	Flines;			/* Number of lines in file */
62 #else
63 	int	Flines;
64 #endif
65 	char	Savedfile[FNSIZE];	/* The current file name */
66 	short	Blocks[LBLKS];		/* Blocks where line pointers stashed */
67 } H;
68 
69 #ifdef	lint
70 #define	ignore(a)	Ignore(a)
71 #define	ignorl(a)	Ignorl(a)
72 #else
73 #define	ignore(a)	a
74 #define	ignorl(a)	a
75 #endif
76 
77 struct	passwd *getpwuid();
78 off_t	lseek();
79 FILE	*popen();
80 
81 #define eq(a, b) strcmp(a, b) == 0
82 
83 main(argc)
84 	int argc;
85 {
86 	register DIR *tf;
87 	struct direct *dirent;
88 	struct stat stbuf;
89 	int err = 0;
90 	char path[MAXPATHLEN];
91 
92 	if (chdir(_PATH_PRESERVE) < 0) {
93 		perror(_PATH_PRESERVE);
94 		exit(1);
95 	}
96 
97 	/*
98 	 * If only one argument, then preserve the standard input.
99 	 */
100 	if (argc == 1) {
101 		if (copyout((char *) 0))
102 			exit(1);
103 		exit(0);
104 	}
105 
106 	/*
107 	 * If not super user, then can only preserve standard input.
108 	 */
109 	if (getuid()) {
110 		fprintf(stderr, "NOT super user\n");
111 		exit(1);
112 	}
113 
114 	/*
115 	 * ... else preserve all the stuff in /var/tmp, removing
116 	 * it as we go.
117 	 */
118 
119 	tf = opendir(_PATH_VARTMP);
120 	if (tf == NULL) {
121 		perror(_PATH_VARTMP);
122 		exit(1);
123 	}
124 	while ((dirent = readdir(tf)) != NULL) {
125 		/* Ex temporaries must begin with Ex. */
126 		if (dirent->d_name[0] != 'E' || dirent->d_name[1] != 'x')
127 			continue;
128 		sprintf(path, "%s/%s", _PATH_VARTMP, dirent->d_name);
129 		if (stat(path, &stbuf))
130 			continue;
131 		if ((stbuf.st_mode & S_IFMT) != S_IFREG)
132 			continue;
133 		/*
134 		 * Save the bastard.
135 		 */
136 		err |= copyout(path);
137 	}
138 	closedir(tf);
139 	exit(err);
140 }
141 
142 char	pattern[] = "Exaa`XXXXX";
143 
144 /*
145  * Copy file name into pattern[].
146  * If name is (char *) 0, then do the standard input.
147  * We make some checks on the input to make sure it is
148  * really an editor temporary, generate a name for the
149  * file (this is the slowest thing since we must stat
150  * to find a unique name), and finally copy the file.
151  */
152 copyout(name)
153 	char *name;
154 {
155 	int i;
156 	static int reenter;
157 	char buf[BUFSIZ];
158 
159 	/*
160 	 * The first time we put in the digits of our
161 	 * process number at the end of the pattern.
162 	 */
163 	if (reenter == 0) {
164 		mkdigits(pattern);
165 		reenter++;
166 	}
167 
168 	/*
169 	 * If a file name was given, make it the standard
170 	 * input if possible.
171 	 */
172 	if (name != 0) {
173 		ignore(close(0));
174 		/*
175 		 * Need read/write access for arcane reasons
176 		 * (see below).
177 		 */
178 		if (open(name, 2) < 0)
179 			return (1);
180 	}
181 
182 	/*
183 	 * Get the header block.
184 	 */
185 	ignorl(lseek(0, 0l, 0));
186 	if (read(0, (char *) &H, sizeof H) != sizeof H) {
187 format:
188 		if (name == 0)
189 			fprintf(stderr, "Buffer format error\t");
190 		return (1);
191 	}
192 
193 	/*
194 	 * Consistency checks so we don't copy out garbage.
195 	 */
196 	if (H.Flines < 0) {
197 #ifdef DEBUG
198 		fprintf(stderr, "Negative number of lines\n");
199 #endif
200 		goto format;
201 	}
202 	if (H.Blocks[0] != HBLKS || H.Blocks[1] != HBLKS+1) {
203 #ifdef DEBUG
204 		fprintf(stderr, "Blocks %d %d\n", H.Blocks[0], H.Blocks[1]);
205 #endif
206 		goto format;
207 	}
208 	if (name == 0 && H.Uid != getuid()) {
209 #ifdef DEBUG
210 		fprintf(stderr, "Wrong user-id\n");
211 #endif
212 		goto format;
213 	}
214 	if (lseek(0, 0l, 0)) {
215 #ifdef DEBUG
216 		fprintf(stderr, "Negative number of lines\n");
217 #endif
218 		goto format;
219 	}
220 
221 	/*
222 	 * If no name was assigned to the file, then give it the name
223 	 * LOST, by putting this in the header.
224 	 */
225 	if (H.Savedfile[0] == 0) {
226 		strcpy(H.Savedfile, "LOST");
227 		ignore(write(0, (char *) &H, sizeof H));
228 		H.Savedfile[0] = 0;
229 		lseek(0, 0l, 0);
230 	}
231 
232 	/*
233 	 * File is good.  Get a name and create a file for the copy.
234 	 */
235 	mknext(pattern);
236 	ignore(close(1));
237 	if (creat(pattern, 0600) < 0) {
238 		if (name == 0)
239 			perror(pattern);
240 		return (1);
241 	}
242 
243 	/*
244 	 * Make the target be owned by the owner of the file.
245 	 */
246 	ignore(chown(pattern, H.Uid, 0));
247 
248 	/*
249 	 * Copy the file.
250 	 */
251 	for (;;) {
252 		i = read(0, buf, BUFSIZ);
253 		if (i < 0) {
254 			if (name)
255 				perror("Buffer read error");
256 			ignore(unlink(pattern));
257 			return (1);
258 		}
259 		if (i == 0) {
260 			if (name)
261 				ignore(unlink(name));
262 			notify(H.Uid, H.Savedfile, (int) name, H.Time);
263 			return (0);
264 		}
265 		if (write(1, buf, i) != i) {
266 			if (name == 0)
267 				perror(pattern);
268 			unlink(pattern);
269 			return (1);
270 		}
271 	}
272 }
273 
274 /*
275  * Blast the last 5 characters of cp to be the process number.
276  */
277 mkdigits(cp)
278 	char *cp;
279 {
280 	register int i, j;
281 
282 	for (i = getpid(), j = 5, cp += strlen(cp); j > 0; i /= 10, j--)
283 		*--cp = i % 10 | '0';
284 }
285 
286 /*
287  * Make the name in cp be unique by clobbering up to
288  * three alphabetic characters into a sequence of the form 'aab', 'aac', etc.
289  * Mktemp gets weird names too quickly to be useful here.
290  */
291 mknext(cp)
292 	char *cp;
293 {
294 	char *dcp;
295 	struct stat stb;
296 
297 	dcp = cp + strlen(cp) - 1;
298 	while (isdigit(*dcp))
299 		dcp--;
300 whoops:
301 	if (dcp[0] == 'z') {
302 		dcp[0] = 'a';
303 		if (dcp[-1] == 'z') {
304 			dcp[-1] = 'a';
305 			if (dcp[-2] == 'z')
306 				fprintf(stderr, "Can't find a name\t");
307 			dcp[-2]++;
308 		} else
309 			dcp[-1]++;
310 	} else
311 		dcp[0]++;
312 	if (stat(cp, &stb) == 0)
313 		goto whoops;
314 }
315 
316 /*
317  * Notify user uid that his file fname has been saved.
318  */
319 notify(uid, fname, flag, time)
320 	int uid;
321 	char *fname;
322 	time_t	time;
323 {
324 	struct passwd *pp = getpwuid(uid);
325 	register FILE *mf;
326 	char	cmd[BUFSIZ];
327 	char	hostname[128];
328 	char	croak[128];
329 	char	*timestamp, *ctime();
330 
331 	if (pp == NULL)
332 		return;
333 	gethostname(hostname, sizeof(hostname));
334 	timestamp = ctime(&time);
335 	timestamp[16] = 0;	/* blast from seconds on */
336 	sprintf(cmd, "%s %s", _PATH_BINMAIL, pp->pw_name);
337 	setuid(getuid());
338 	mf = popen(cmd, "w");
339 	if (mf == NULL)
340 		return;
341 	setbuf(mf, cmd);
342 	/*
343 	 *	flag says how the editor croaked:
344 	 * "the editor was killed" is perhaps still not an ideal
345 	 * error message.  Usually, either it was forcably terminated
346 	 * or the phone was hung up, but we don't know which.
347 	 */
348 	sprintf(croak, flag
349 		? "the system went down"
350 		: "the editor was killed");
351 	if (fname[0] == 0) {
352 		fname = "LOST";
353 		fprintf(mf,
354 "Subject: editor saved ``LOST''\n");
355 		fprintf(mf,
356 "You were editing a file without a name\n");
357 		fprintf(mf,
358 "at <%s> on the machine ``%s'' when %s.\n", timestamp, hostname, croak);
359 		fprintf(mf,
360 "Since the file had no name, it has been named \"LOST\".\n");
361 	} else {
362 		fprintf(mf,
363 "Subject: editor saved ``%s''\n", fname);
364 		fprintf(mf,
365 "You were editing the file \"%s\"\n", fname);
366 		fprintf(mf,
367 "at <%s> on the machine ``%s''\n", timestamp, hostname);
368 		fprintf(mf,
369 "when %s.\n", croak);
370 	}
371 	fprintf(mf,
372 "\nYou can retrieve most of your changes to this file\n");
373 	fprintf(mf,
374 "using the \"recover\" command of the editor.\n");
375 	fprintf(mf,
376 "An easy way to do this is to give the command \"vi -r %s\".\n", fname);
377 	fprintf(mf,
378 "This method also works using \"ex\" and \"edit\".\n");
379 	pclose(mf);
380 }
381 
382 /*
383  *	people making love
384  *	never exactly the same
385  *	just like a snowflake
386  */
387 
388 #ifdef lint
389 Ignore(a)
390 	int a;
391 {
392 
393 	a = a;
394 }
395 
396 Ignorl(a)
397 	long a;
398 {
399 
400 	a = a;
401 }
402 #endif
403