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