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