xref: /illumos-gate/usr/src/cmd/vi/port/expreserve.c (revision 71269a2275bf5a143dad6461eee2710a344e7261)
1 /*
2  * CDDL HEADER START
3  *
4  * The contents of this file are subject to the terms of the
5  * Common Development and Distribution License, Version 1.0 only
6  * (the "License").  You may not use this file except in compliance
7  * with the License.
8  *
9  * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
10  * or http://www.opensolaris.org/os/licensing.
11  * See the License for the specific language governing permissions
12  * and limitations under the License.
13  *
14  * When distributing Covered Code, include this CDDL HEADER in each
15  * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
16  * If applicable, add the following below this CDDL HEADER, with the
17  * fields enclosed by brackets "[]" replaced with your own identifying
18  * information: Portions Copyright [yyyy] [name of copyright owner]
19  *
20  * CDDL HEADER END
21  */
22 /*
23  * Copyright 2005 Sun Microsystems, Inc.  All rights reserved.
24  * Use is subject to license terms.
25  */
26 
27 /*	Copyright (c) 1984, 1986, 1987, 1988, 1989 AT&T	*/
28 /*	  All Rights Reserved  	*/
29 
30 
31 /* Copyright (c) 1981 Regents of the University of California */
32 
33 #pragma ident	"%Z%%M%	%I%	%E% SMI"
34 
35 #include <ctype.h>
36 #include <sys/types.h>
37 #include <sys/stat.h>
38 #include <sys/fcntl.h>
39 #include <errno.h>
40 #include <dirent.h>
41 #include <pwd.h>
42 #include <locale.h>
43 #include <limits.h>
44 #include <unistd.h>
45 
46 #define	BUFSIZE	(LINE_MAX*2)	/* This should agree with what's in ex.h */
47 
48 #include "ex_tune.h"
49 
50 #define	FTYPE(A)	(A.st_mode)
51 #define	FMODE(A)	(A.st_mode)
52 #define	IDENTICAL(A, B)	(A.st_dev == B.st_dev && A.st_ino == B.st_ino)
53 #define	ISBLK(A)	((A.st_mode & S_IFMT) == S_IFBLK)
54 #define	ISCHR(A)	((A.st_mode & S_IFMT) == S_IFCHR)
55 #define	ISDIR(A)	((A.st_mode & S_IFMT) == S_IFDIR)
56 #define	ISFIFO(A)	((A.st_mode & S_IFMT) == S_IFIFO)
57 #define	ISREG(A)	((A.st_mode & S_IFMT) == S_IFREG)
58 
59 /*
60  * Expreserve - preserve a file in usrpath(preserve)
61  *
62  * This routine is very naive - it doesn't remove anything from
63  * usrpath(preserve)... this may mean that we  * stuff there...
64  * the danger in doing anything with usrpath(preserve)
65  * is that the clock may be messed up and we may get confused.
66  *
67  * We are called in two ways - first from the editor with no arguments
68  * and the standard input open on the temp file. Second with an argument
69  * to preserve the entire contents of /var/tmp (root only).
70  *
71  * BUG: should do something about preserving Rx... (register contents)
72  *      temporaries.
73  */
74 
75 struct 	header {
76 	time_t	Time;			/* Time temp file last updated */
77 	int	Uid;			/* This user's identity */
78 #ifndef VMUNIX
79 	short	Flines;			/* Number of lines in file */
80 #else
81 	int	Flines;
82 #endif
83 	unsigned char	Savedfile[FNSIZE];	/* The current file name */
84 	short	Blocks[LBLKS];		/* Blocks where line pointers stashed */
85 	short	encrypted;		/* Encrypted temp file flag */
86 } H;
87 
88 #define	eq(a, b) strcmp(a, b) == 0
89 
90 void notify(int, unsigned char *, int, int);
91 void mkdigits(unsigned char *);
92 
93 int
94 main(argc)
95 	int argc;
96 {
97 	DIR *tf;
98 	struct dirent64 *direntry;
99 	unsigned char *filname;
100 	struct stat64 stbuf;
101 
102 	(void) setlocale(LC_ALL, "");
103 #if !defined(TEXT_DOMAIN)
104 #define	TEXT_DOMAIN "SYS_TEST"
105 #endif
106 	(void) textdomain(TEXT_DOMAIN);
107 	/*
108 	 * If only one argument, then preserve the standard input.
109 	 */
110 	if (argc == 1) {
111 		if (copyout((unsigned char *) 0))
112 			return (1);
113 		return (0);
114 	}
115 
116 	/*
117 	 * If not super user, then can only preserve standard input.
118 	 */
119 	if (getuid()) {
120 		fprintf(stderr, gettext("NOT super user\n"));
121 		return (1);
122 	}
123 
124 	/*
125 	 * ... else preserve all the stuff in /var/tmp, removing
126 	 * it as we go.
127 	 */
128 	if (chdir(TMPDIR) < 0) {
129 		perror(TMPDIR);
130 		return (1);
131 	}
132 
133 	if ((tf = opendir(".")) == NULL)
134 	{
135 		perror(TMPDIR);
136 		return (1);
137 	}
138 	while ((direntry = readdir64(tf)) != NULL)
139 	{
140 		filname = (unsigned char *)direntry->d_name;
141 		/*
142 		 * Ex temporaries must begin with Ex;
143 		 * we check that the 12th character of the name is null
144 		 * so we won't have to worry about non-null terminated names
145 		 * later on.
146 		 */
147 		if (filname[0] != 'E' || filname[1] != 'x' || filname[12])
148 			continue;
149 		if (stat64((char *)filname, &stbuf))
150 			continue;
151 		if (!ISREG(stbuf))
152 			continue;
153 		/*
154 		 * Save the file.
155 		 */
156 		(void) copyout(filname);
157 	}
158 	closedir(tf);
159 	return (0);
160 }
161 
162 unsigned char	mydir[] =	USRPRESERVE;
163 unsigned char	pattern[] =	"/Exaa`XXXXXXXXXX";
164 
165 /*
166  * Copy file name into usrpath(preserve)/...
167  * If name is (char *) 0, then do the standard input.
168  * We make some checks on the input to make sure it is
169  * really an editor temporary, generate a name for the
170  * file (this is the slowest thing since we must stat
171  * to find a unique name), and finally copy the file.
172  */
173 int
174 copyout(unsigned char *name)
175 {
176 	int i;
177 	static int reenter;
178 	unsigned char buf[BUFSIZE];
179 	unsigned char	savdir[PATH_MAX+1];
180 	unsigned char	savfil[PATH_MAX+1];
181 	struct passwd *pp;
182 	struct stat64	stbuf;
183 	int savfild;
184 
185 	/*
186 	 * The first time we put in the digits of our
187 	 * process number at the end of the pattern.
188 	 */
189 	if (reenter == 0) {
190 		mkdigits(pattern);
191 		reenter++;
192 	}
193 
194 	/*
195 	 * If a file name was given, make it the standard
196 	 * input if possible.
197 	 */
198 	if (name != 0) {
199 		(void) close(0);
200 		/*
201 		 * Need read/write access for arcane reasons
202 		 * (see below).
203 		 */
204 		if (open(name, O_RDWR) < 0)
205 			return (-1);
206 	}
207 
208 	/*
209 	 * Get the header block.
210 	 */
211 	(void) lseek(0, 0l, 0);
212 	if (read(0, (char *)&H, sizeof (H)) != sizeof (H)) {
213 format:
214 		if (name == 0)
215 			fprintf(stderr, gettext("Buffer format error\t"));
216 		else {
217 			/*
218 			 * avoid having a bunch of NULL Ex* files
219 			 * hanging around
220 			 */
221 			struct stat64 stbuf;
222 
223 			if (stat64((char *)name, &stbuf) == 0)
224 			if (stbuf.st_size == 0)
225 				(void) unlink((char *)name);
226 		}
227 		return (-1);
228 	}
229 
230 	/*
231 	 * Consistency checks so we don't copy out garbage.
232 	 */
233 	if (H.Flines < 0) {
234 #ifdef DEBUG
235 		fprintf(stderr, "Negative number of lines\n");
236 #endif
237 		goto format;
238 	}
239 	if (H.Blocks[0] != HBLKS || H.Blocks[1] != HBLKS+1) {
240 #ifdef DEBUG
241 		fprintf(stderr, "Blocks %d %d\n", H.Blocks[0], H.Blocks[1]);
242 #endif
243 		goto format;
244 	}
245 	if (name == 0 && H.Uid != getuid()) {
246 #ifdef DEBUG
247 		fprintf(stderr, "Wrong user-id\n");
248 #endif
249 		goto format;
250 	}
251 	if (lseek(0, 0l, 0)) {
252 #ifdef DEBUG
253 		fprintf(stderr, gettext("Negative number of lines\n"));
254 #endif
255 		goto format;
256 	}
257 
258 	/*
259 	 * If no name was assigned to the file, then give it the name
260 	 * LOST, by putting this in the header.
261 	 */
262 	if (H.Savedfile[0] == 0) {
263 		(void) strcpy(H.Savedfile, "LOST");
264 		(void) write(0, (char *) &H, sizeof (H));
265 		H.Savedfile[0] = 0;
266 		(void) lseek(0, 0l, 0);
267 	}
268 
269 	/*
270 	 * See if preservation directory for user exists.
271 	 */
272 
273 	strcpy(savdir, mydir);
274 	pp = getpwuid(H.Uid);
275 	if (pp)
276 		strcat(savdir, pp->pw_name);
277 	else {
278 		fprintf(stderr, gettext("Unable to get uid for user.\n"));
279 		return (-1);
280 	}
281 	if (lstat64((char *)savdir, &stbuf) < 0 || !S_ISDIR(stbuf.st_mode)) {
282 		/* It doesn't exist or it isn't a directory, safe to unlink */
283 		(void) unlink((char *)savdir);
284 		if (mkdir((char *)savdir, 0700) < 0) {
285 			fprintf(stderr,
286 				gettext("Unable to create directory \"%s\"\n"),
287 				savdir);
288 			perror("");
289 			return (-1);
290 		}
291 		(void) chmod((char *)savdir, 0700);
292 		(void) chown((char *)savdir, H.Uid, 2);
293 	}
294 
295 	/*
296 	 * File is good.  Get a name and create a file for the copy.
297 	 */
298 	(void) close(1);
299 	if ((savfild = mknext(savdir, pattern)) < 0) {
300 		if (name == 0)
301 			perror((char *)savfil);
302 		return	(1);
303 	}
304 	strcpy(savfil, savdir);
305 	strcat(savfil, pattern);
306 	/*
307 	 * Make target owned by user.
308 	 */
309 
310 	(void) fchown(savfild, H.Uid, 2);
311 
312 	/*
313 	 * Copy the file.
314 	 */
315 	for (;;) {
316 		i = read(0, buf, BUFSIZE);
317 		if (i < 0) {
318 			if (name)
319 				perror(gettext("Buffer read error"));
320 			(void) unlink((char *)savfil);
321 			return (-1);
322 		}
323 		if (i == 0) {
324 			if (name)
325 				(void) unlink((char *)name);
326 			notify(H.Uid, H.Savedfile, (int) name, H.encrypted);
327 			return (0);
328 		}
329 		if (write(savfild, buf, i) != i) {
330 			if (name == 0)
331 				perror((char *)savfil);
332 			(void) unlink((char *)savfil);
333 			return (-1);
334 		}
335 	}
336 }
337 
338 /*
339  * Blast the last 5 characters of cp to be the process number.
340  */
341 void
342 mkdigits(unsigned char *cp)
343 {
344 	pid_t i;
345 	int j;
346 
347 	for (i = getpid(), j = 10, cp += strlen(cp); j > 0; i /= 10, j--)
348 		*--cp = i % 10 | '0';
349 }
350 
351 /*
352  * Make the name in cp be unique by clobbering up to
353  * three alphabetic characters into a sequence of the form 'aab', 'aac', etc.
354  * Mktemp gets weird names too quickly to be useful here.
355  */
356 int
357 mknext(unsigned char *dir, unsigned char *cp)
358 {
359 	unsigned char *dcp;
360 	struct stat stb;
361 	unsigned char path[PATH_MAX+1];
362 	int fd;
363 
364 	strcpy(path, dir);
365 	strcat(path, cp);
366 	dcp = path + strlen(path) - 1;
367 
368 	while (isdigit(*dcp))
369 		dcp--;
370 
371 	do {
372 		if (dcp[0] == 'z') {
373 			dcp[0] = 'a';
374 			if (dcp[-1] == 'z') {
375 				dcp[-1] = 'a';
376 				if (dcp[-2] == 'z') {
377 					fprintf(stderr,
378 						gettext("Can't find a name\t"));
379 						return (-1);
380 				}
381 				dcp[-2]++;
382 			} else
383 				dcp[-1]++;
384 		} else
385 			dcp[0]++;
386 
387 	} while (((fd = open(path, O_CREAT|O_EXCL|O_WRONLY, 0600)) < 0) &&
388 		errno == EEXIST);
389 	/* copy out patern */
390 	strcpy(cp, path + strlen(dir));
391 	return (fd);
392 }
393 
394 /*
395  * Notify user uid that his file fname has been saved.
396  */
397 void
398 notify(int uid, unsigned char *fname, int flag, int cryflag)
399 {
400 
401 #define MAXHOSTNAMELEN 256
402 
403 	struct passwd *pp = getpwuid(uid);
404 	FILE *mf;
405 	unsigned char cmd[BUFSIZE];
406 
407 	char hostname[MAXHOSTNAMELEN];
408 	int namelen = MAXHOSTNAMELEN ;
409 
410 	if (gethostname((char *)hostname, namelen) == -1)
411 	  return;
412 
413 	if (pp == NULL)
414 		return;
415 	sprintf((char *)cmd, "/usr/bin/mail %s", pp->pw_name);
416 	mf = popen((char *)cmd, "w");
417 	if (mf == NULL)
418 		return;
419 	setbuf(mf, (char *)cmd);
420 	if (fname[0] == 0) {
421 		fprintf(mf, flag ?
422 "A copy of an editor buffer of yours was saved on %s when the system went down.\n" :
423 "A copy of an editor buffer of yours was saved on %s when the editor was killed\nor was unable to save your changes.\n", hostname);
424 		fprintf(mf,
425 "No name was associated with this buffer so it has been named \"LOST\".\n");
426 	} else
427 		fprintf(mf, flag ?
428 "A copy of an editor buffer of your file \"%s\"%s was saved on %s\nwhen the system \
429 went down.\n" :
430 "A copy of an editor buffer of your file \"%s\"%s was saved on %s\nwhen the editor \
431 was killed or was unable to save your changes.\n", fname, (cryflag) ? "[ENCRYPTED]" : "", hostname);
432 		/*
433 		 * "the editor was killed" is perhaps still not an ideal
434 		 * error message.  Usually, either it was forceably terminated
435 		 * or the phone was hung up, but we don't know which.
436 		 */
437 	fprintf(mf,
438 "This buffer can be retrieved using the \"recover\" command of the editor.\n");
439 	fprintf(mf,
440 "An easy way to do this is to give the command \"vi -r %s\".\n",
441 		(fname[0] == 0) ? "LOST" : (char *) fname);
442 	fprintf(mf, "This works for \"edit\" and \"ex\" also.\n");
443 	(void) pclose(mf);
444 }
445