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