1 /* $OpenBSD: util.c,v 1.32 2006/03/11 19:41:30 otto Exp $ */ 2 3 /* 4 * patch - a program to apply diffs to original files 5 * 6 * Copyright 1986, Larry Wall 7 * 8 * Redistribution and use in source and binary forms, with or without 9 * modification, are permitted provided that the following condition is met: 10 * 1. Redistributions of source code must retain the above copyright notice, 11 * this condition and the following disclaimer. 12 * 13 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND ANY 14 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 15 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 16 * DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE FOR 17 * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 18 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 19 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 20 * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 21 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 22 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 23 * SUCH DAMAGE. 24 * 25 * -C option added in 1998, original code by Marc Espie, based on FreeBSD 26 * behaviour 27 */ 28 29 #ifndef lint 30 static const char rcsid[] = "$OpenBSD: util.c,v 1.32 2006/03/11 19:41:30 otto Exp $"; 31 #endif /* not lint */ 32 33 #include <sys/param.h> 34 #include <sys/stat.h> 35 36 #include <ctype.h> 37 #include <errno.h> 38 #include <fcntl.h> 39 #include <libgen.h> 40 #include <paths.h> 41 #include <signal.h> 42 #include <stdarg.h> 43 #include <stdlib.h> 44 #include <stdio.h> 45 #include <string.h> 46 #include <unistd.h> 47 48 #include "common.h" 49 #include "util.h" 50 #include "backupfile.h" 51 #include "pathnames.h" 52 53 /* Rename a file, copying it if necessary. */ 54 55 int 56 move_file(const char *from, const char *to) 57 { 58 int fromfd; 59 ssize_t i; 60 61 /* to stdout? */ 62 63 if (strEQ(to, "-")) { 64 #ifdef DEBUGGING 65 if (debug & 4) 66 say("Moving %s to stdout.\n", from); 67 #endif 68 fromfd = open(from, O_RDONLY); 69 if (fromfd < 0) 70 pfatal("internal error, can't reopen %s", from); 71 while ((i = read(fromfd, buf, sizeof buf)) > 0) 72 if (write(STDOUT_FILENO, buf, i) != i) 73 pfatal("write failed"); 74 close(fromfd); 75 return 0; 76 } 77 if (backup_file(to) < 0) { 78 say("Can't backup %s, output is in %s: %s\n", to, from, 79 strerror(errno)); 80 return -1; 81 } 82 #ifdef DEBUGGING 83 if (debug & 4) 84 say("Moving %s to %s.\n", from, to); 85 #endif 86 if (rename(from, to) < 0) { 87 if (errno != EXDEV || copy_file(from, to) < 0) { 88 say("Can't create %s, output is in %s: %s\n", 89 to, from, strerror(errno)); 90 return -1; 91 } 92 } 93 return 0; 94 } 95 96 /* Backup the original file. */ 97 98 int 99 backup_file(const char *orig) 100 { 101 struct stat filestat; 102 char bakname[MAXPATHLEN], *s, *simplename; 103 dev_t orig_device; 104 ino_t orig_inode; 105 106 if (backup_type == none || stat(orig, &filestat) != 0) 107 return 0; /* nothing to do */ 108 orig_device = filestat.st_dev; 109 orig_inode = filestat.st_ino; 110 111 if (origprae) { 112 if (strlcpy(bakname, origprae, sizeof(bakname)) >= sizeof(bakname) || 113 strlcat(bakname, orig, sizeof(bakname)) >= sizeof(bakname)) 114 fatal("filename %s too long for buffer\n", origprae); 115 } else { 116 if ((s = find_backup_file_name(orig)) == NULL) 117 fatal("out of memory\n"); 118 if (strlcpy(bakname, s, sizeof(bakname)) >= sizeof(bakname)) 119 fatal("filename %s too long for buffer\n", s); 120 free(s); 121 } 122 123 if ((simplename = strrchr(bakname, '/')) != NULL) 124 simplename = simplename + 1; 125 else 126 simplename = bakname; 127 128 /* 129 * Find a backup name that is not the same file. Change the 130 * first lowercase char into uppercase; if that isn't 131 * sufficient, chop off the first char and try again. 132 */ 133 while (stat(bakname, &filestat) == 0 && 134 orig_device == filestat.st_dev && orig_inode == filestat.st_ino) { 135 /* Skip initial non-lowercase chars. */ 136 for (s = simplename; *s && !islower(*s); s++) 137 ; 138 if (*s) 139 *s = toupper(*s); 140 else 141 memmove(simplename, simplename + 1, 142 strlen(simplename + 1) + 1); 143 } 144 #ifdef DEBUGGING 145 if (debug & 4) 146 say("Moving %s to %s.\n", orig, bakname); 147 #endif 148 if (rename(orig, bakname) < 0) { 149 if (errno != EXDEV || copy_file(orig, bakname) < 0) 150 return -1; 151 } 152 return 0; 153 } 154 155 /* 156 * Copy a file. 157 */ 158 int 159 copy_file(const char *from, const char *to) 160 { 161 int tofd, fromfd; 162 ssize_t i; 163 164 tofd = open(to, O_CREAT|O_TRUNC|O_WRONLY, 0666); 165 if (tofd < 0) 166 return -1; 167 fromfd = open(from, O_RDONLY, 0); 168 if (fromfd < 0) 169 pfatal("internal error, can't reopen %s", from); 170 while ((i = read(fromfd, buf, sizeof buf)) > 0) 171 if (write(tofd, buf, i) != i) 172 pfatal("write to %s failed", to); 173 close(fromfd); 174 close(tofd); 175 return 0; 176 } 177 178 /* 179 * Allocate a unique area for a string. 180 */ 181 char * 182 savestr(const char *s) 183 { 184 char *rv; 185 186 if (!s) 187 s = "Oops"; 188 rv = strdup(s); 189 if (rv == NULL) { 190 if (using_plan_a) 191 out_of_mem = true; 192 else 193 fatal("out of memory\n"); 194 } 195 return rv; 196 } 197 198 /* 199 * Vanilla terminal output (buffered). 200 */ 201 void 202 say(const char *fmt, ...) 203 { 204 va_list ap; 205 206 va_start(ap, fmt); 207 vfprintf(stderr, fmt, ap); 208 va_end(ap); 209 fflush(stderr); 210 } 211 212 /* 213 * Terminal output, pun intended. 214 */ 215 void 216 fatal(const char *fmt, ...) 217 { 218 va_list ap; 219 220 va_start(ap, fmt); 221 fprintf(stderr, "patch: **** "); 222 vfprintf(stderr, fmt, ap); 223 va_end(ap); 224 my_exit(2); 225 } 226 227 /* 228 * Say something from patch, something from the system, then silence . . . 229 */ 230 void 231 pfatal(const char *fmt, ...) 232 { 233 va_list ap; 234 int errnum = errno; 235 236 fprintf(stderr, "patch: **** "); 237 va_start(ap, fmt); 238 vfprintf(stderr, fmt, ap); 239 va_end(ap); 240 fprintf(stderr, ": %s\n", strerror(errnum)); 241 my_exit(2); 242 } 243 244 /* 245 * Get a response from the user via /dev/tty 246 */ 247 void 248 ask(const char *fmt, ...) 249 { 250 va_list ap; 251 ssize_t nr; 252 static int ttyfd = -1; 253 254 va_start(ap, fmt); 255 vfprintf(stdout, fmt, ap); 256 va_end(ap); 257 fflush(stdout); 258 if (ttyfd < 0) 259 ttyfd = open(_PATH_TTY, O_RDONLY); 260 if (ttyfd >= 0) { 261 if ((nr = read(ttyfd, buf, sizeof(buf))) > 0 && 262 buf[nr - 1] == '\n') 263 buf[nr - 1] = '\0'; 264 } 265 if (ttyfd < 0 || nr <= 0) { 266 /* no tty or error reading, pretend user entered 'return' */ 267 putchar('\n'); 268 buf[0] = '\0'; 269 } 270 } 271 272 /* 273 * How to handle certain events when not in a critical region. 274 */ 275 void 276 set_signals(int reset) 277 { 278 static sig_t hupval, intval; 279 280 if (!reset) { 281 hupval = signal(SIGHUP, SIG_IGN); 282 if (hupval != SIG_IGN) 283 hupval = (sig_t) my_exit; 284 intval = signal(SIGINT, SIG_IGN); 285 if (intval != SIG_IGN) 286 intval = (sig_t) my_exit; 287 } 288 signal(SIGHUP, hupval); 289 signal(SIGINT, intval); 290 } 291 292 /* 293 * How to handle certain events when in a critical region. 294 */ 295 void 296 ignore_signals(void) 297 { 298 signal(SIGHUP, SIG_IGN); 299 signal(SIGINT, SIG_IGN); 300 } 301 302 /* 303 * Make sure we'll have the directories to create a file. If `striplast' is 304 * true, ignore the last element of `filename'. 305 */ 306 307 void 308 makedirs(const char *filename, bool striplast) 309 { 310 char *tmpbuf; 311 312 if ((tmpbuf = strdup(filename)) == NULL) 313 fatal("out of memory\n"); 314 315 if (striplast) { 316 char *s = strrchr(tmpbuf, '/'); 317 if (s == NULL) 318 return; /* nothing to be done */ 319 *s = '\0'; 320 } 321 if (mkpath(tmpbuf) != 0) 322 pfatal("creation of %s failed", tmpbuf); 323 free(tmpbuf); 324 } 325 326 /* 327 * Make filenames more reasonable. 328 */ 329 char * 330 fetchname(const char *at, bool *exists, int strip_leading) 331 { 332 char *fullname, *name, *t; 333 int sleading, tab; 334 struct stat filestat; 335 336 if (at == NULL || *at == '\0') 337 return NULL; 338 while (isspace(*at)) 339 at++; 340 #ifdef DEBUGGING 341 if (debug & 128) 342 say("fetchname %s %d\n", at, strip_leading); 343 #endif 344 /* So files can be created by diffing against /dev/null. */ 345 if (strnEQ(at, _PATH_DEVNULL, sizeof(_PATH_DEVNULL) - 1)) 346 return NULL; 347 name = fullname = t = savestr(at); 348 349 tab = strchr(t, '\t') != NULL; 350 /* Strip off up to `strip_leading' path components and NUL terminate. */ 351 for (sleading = strip_leading; *t != '\0' && ((tab && *t != '\t') || 352 !isspace(*t)); t++) { 353 if (t[0] == '/' && t[1] != '/' && t[1] != '\0') 354 if (--sleading >= 0) 355 name = t + 1; 356 } 357 *t = '\0'; 358 359 /* 360 * If no -p option was given (957 is the default value!), we were 361 * given a relative pathname, and the leading directories that we 362 * just stripped off all exist, put them back on. 363 */ 364 if (strip_leading == 957 && name != fullname && *fullname != '/') { 365 name[-1] = '\0'; 366 if (stat(fullname, &filestat) == 0 && S_ISDIR(filestat.st_mode)) { 367 name[-1] = '/'; 368 name = fullname; 369 } 370 } 371 name = savestr(name); 372 free(fullname); 373 374 *exists = stat(name, &filestat) == 0; 375 return name; 376 } 377 378 /* 379 * Takes the name returned by fetchname and looks in RCS/SCCS directories 380 * for a checked in version. 381 */ 382 char * 383 checked_in(char *file) 384 { 385 char *filebase, *filedir, tmpbuf[MAXPATHLEN]; 386 struct stat filestat; 387 388 filebase = basename(file); 389 filedir = dirname(file); 390 391 #define try(f, a1, a2, a3) \ 392 (snprintf(tmpbuf, sizeof tmpbuf, f, a1, a2, a3), stat(tmpbuf, &filestat) == 0) 393 394 if (try("%s/RCS/%s%s", filedir, filebase, RCSSUFFIX) || 395 try("%s/RCS/%s%s", filedir, filebase, "") || 396 try("%s/%s%s", filedir, filebase, RCSSUFFIX) || 397 try("%s/SCCS/%s%s", filedir, SCCSPREFIX, filebase) || 398 try("%s/%s%s", filedir, SCCSPREFIX, filebase)) 399 return file; 400 401 return NULL; 402 } 403 404 void 405 version(void) 406 { 407 fprintf(stderr, "Patch version 2.0-12u8-OpenBSD\n"); 408 my_exit(EXIT_SUCCESS); 409 } 410 411 /* 412 * Exit with cleanup. 413 */ 414 void 415 my_exit(int status) 416 { 417 unlink(TMPINNAME); 418 if (!toutkeep) 419 unlink(TMPOUTNAME); 420 if (!trejkeep) 421 unlink(TMPREJNAME); 422 unlink(TMPPATNAME); 423 exit(status); 424 } 425