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