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