util.c revision 1.19 1 /* $NetBSD: util.c,v 1.19 2003/07/30 08:51:55 itojun Exp $ */
2
3 /*
4 * Copyright (c) 1988, Larry Wall
5 *
6 * Redistribution and use in source and binary forms, with or without
7 * modification, are permitted provided that the following condition
8 * is met:
9 * 1. Redistributions of source code must retain the above copyright
10 * notice, this condition and the following disclaimer.
11 *
12 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
13 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
14 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
15 * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
16 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
17 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
18 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
19 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
20 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
21 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
22 * SUCH DAMAGE.
23 */
24
25 #include <sys/cdefs.h>
26 #ifndef lint
27 __RCSID("$NetBSD: util.c,v 1.19 2003/07/30 08:51:55 itojun Exp $");
28 #endif /* not lint */
29
30 #include <sys/param.h>
31
32 #include "EXTERN.h"
33 #include "common.h"
34 #include "INTERN.h"
35 #include "util.h"
36 #include "backupfile.h"
37
38 #include <stdarg.h>
39 #include <stdlib.h>
40 #include <unistd.h>
41 #include <fcntl.h>
42
43 /*
44 * Rename a file, copying it if necessary.
45 */
46 int
47 move_file(char *from, char *to)
48 {
49 char bakname[MAXPATHLEN];
50 char *s;
51 size_t i;
52 int fromfd;
53
54 /* to stdout? */
55
56 if (strEQ(to, "-")) {
57 #ifdef DEBUGGING
58 if (debug & 4)
59 say("Moving %s to stdout.\n", from);
60 #endif
61 fromfd = open(from, 0);
62 if (fromfd < 0)
63 pfatal("internal error, can't reopen %s", from);
64 while ((i = read(fromfd, buf, sizeof buf)) > 0)
65 if (write(1, buf, i) != 1)
66 pfatal("write failed");
67 Close(fromfd);
68 return 0;
69 }
70
71 if (origprae) {
72 strlcpy(bakname, origprae, sizeof(bakname));
73 strlcat(bakname, to, sizeof(bakname));
74 } else {
75 #ifndef NODIR
76 char *backupname = find_backup_file_name(to);
77 strlcpy(bakname, backupname, sizeof(bakname));
78 free(backupname);
79 #else /* NODIR */
80 strlcpy(bakname, to, sizeof(bakname));
81 strlcat(bakname, simple_backup_suffix, sizeof(bakname));
82 #endif /* NODIR */
83 }
84
85 if (stat(to, &filestat) == 0) { /* output file exists */
86 dev_t to_device = filestat.st_dev;
87 ino_t to_inode = filestat.st_ino;
88 char *simplename = bakname;
89
90 for (s = bakname; *s; s++) {
91 if (*s == '/')
92 simplename = s + 1;
93 }
94 /*
95 * Find a backup name that is not the same file.
96 * Change the first lowercase char into uppercase;
97 * if that isn't sufficient, chop off the first char
98 * and try again.
99 */
100 while (stat(bakname, &filestat) == 0 &&
101 to_device == filestat.st_dev &&
102 to_inode == filestat.st_ino) {
103 /* Skip initial non-lowercase chars. */
104 for (s = simplename;
105 *s && !islower((unsigned char)*s);
106 s++)
107 ;
108 if (*s)
109 *s = toupper(*s);
110 else
111 strcpy(simplename, simplename + 1);
112 }
113 while (unlink(bakname) >= 0)
114 ; /* while() is for benefit of Eunice */
115 #ifdef DEBUGGING
116 if (debug & 4)
117 say("Moving %s to %s.\n", to, bakname);
118 #endif
119 if (link(to, bakname) < 0) {
120 /*
121 * Maybe `to' is a symlink into a different file
122 * system. Copying replaces the symlink with a file;
123 * using rename would be better.
124 */
125 int tofd;
126 int bakfd;
127
128 bakfd = creat(bakname, 0666);
129 if (bakfd < 0) {
130 say("Can't backup %s, output is in %s: %s\n",
131 to, from, strerror(errno));
132 return -1;
133 }
134 tofd = open(to, 0);
135 if (tofd < 0)
136 pfatal("internal error, can't open %s", to);
137 while ((i = read(tofd, buf, sizeof buf)) > 0)
138 if (write(bakfd, buf, i) != i)
139 pfatal("write failed");
140 Close(tofd);
141 Close(bakfd);
142 }
143 while (unlink(to) >= 0) ;
144 }
145 #ifdef DEBUGGING
146 if (debug & 4)
147 say("Moving %s to %s.\n", from, to);
148 #endif
149 if (link(from, to) < 0) { /* different file system? */
150 int tofd;
151
152 tofd = creat(to, 0666);
153 if (tofd < 0) {
154 say("Can't create %s, output is in %s: %s\n",
155 to, from, strerror(errno));
156 return -1;
157 }
158 fromfd = open(from, 0);
159 if (fromfd < 0)
160 pfatal("internal error, can't reopen %s", from);
161 while ((i = read(fromfd, buf, sizeof buf)) > 0)
162 if (write(tofd, buf, i) != i)
163 pfatal("write failed");
164 Close(fromfd);
165 Close(tofd);
166 }
167 Unlink(from);
168 return 0;
169 }
170
171 /*
172 * Copy a file.
173 */
174 void
175 copy_file(char *from, char *to)
176 {
177 int tofd;
178 int fromfd;
179 size_t i;
180
181 tofd = creat(to, 0666);
182 if (tofd < 0)
183 pfatal("can't create %s", to);
184 fromfd = open(from, 0);
185 if (fromfd < 0)
186 pfatal("internal error, can't reopen %s", from);
187 while ((i = read(fromfd, buf, sizeof buf)) > 0)
188 if (write(tofd, buf, i) != i)
189 pfatal("write to %s failed", to);
190 Close(fromfd);
191 Close(tofd);
192 }
193
194 /*
195 * malloc with result test.
196 */
197 void *
198 xmalloc(size_t size)
199 {
200 void *p;
201
202 if ((p = malloc(size)) == NULL)
203 fatal("out of memory\n");
204 return p;
205 }
206
207 /*
208 * realloc with result test.
209 */
210 void *
211 xrealloc(void *ptr, size_t size)
212 {
213 void *p;
214
215 if ((p = realloc(ptr, size)) == NULL)
216 fatal("out of memory\n");
217 return p;
218 }
219
220 /*
221 * strdup with result test.
222 */
223 char *
224 xstrdup(const char *s)
225 {
226 char *p;
227
228 if ((p = strdup(s)) == NULL)
229 fatal("out of memory\n");
230 return p;
231 }
232
233 /*
234 * Vanilla terminal output.
235 */
236 void
237 say(const char *pat, ...)
238 {
239 va_list ap;
240 va_start(ap, pat);
241
242 vfprintf(stderr, pat, ap);
243 va_end(ap);
244 Fflush(stderr);
245 }
246
247 /*
248 * Terminal output, pun intended.
249 */
250 void /* very void */
251 fatal(const char *pat, ...)
252 {
253 va_list ap;
254 va_start(ap, pat);
255
256 fprintf(stderr, "patch: **** ");
257 vfprintf(stderr, pat, ap);
258 va_end(ap);
259 my_exit(1);
260 }
261
262 /*
263 * Say something from patch, something from the system, then silence...
264 */
265 void /* very void */
266 pfatal(const char *pat, ...)
267 {
268 va_list ap;
269 int errnum = errno;
270 va_start(ap, pat);
271
272 fprintf(stderr, "patch: **** ");
273 vfprintf(stderr, pat, ap);
274 fprintf(stderr, ": %s\n", strerror(errnum));
275 va_end(ap);
276 my_exit(1);
277 }
278
279 /*
280 * Get a response from the user, somehow or other.
281 */
282 void
283 ask(const char *pat, ...)
284 {
285 int ttyfd;
286 int r;
287 bool tty2 = isatty(2);
288 va_list ap;
289 va_start(ap, pat);
290
291 (void)vsprintf(buf, pat, ap);
292 va_end(ap);
293 Fflush(stderr);
294 write(2, buf, strlen(buf));
295 if (tty2) { /* might be redirected to a file */
296 r = read(2, buf, sizeof buf);
297 } else if (isatty(1)) { /* this may be new file output */
298 Fflush(stdout);
299 write(1, buf, strlen(buf));
300 r = read(1, buf, sizeof buf);
301 } else if ((ttyfd = open("/dev/tty", 2)) >= 0 && isatty(ttyfd)) {
302 /* might be deleted or unwritable */
303 write(ttyfd, buf, strlen(buf));
304 r = read(ttyfd, buf, sizeof buf);
305 Close(ttyfd);
306 } else if (isatty(0)) { /* this is probably patch input */
307 Fflush(stdin);
308 write(0, buf, strlen(buf));
309 r = read(0, buf, sizeof buf);
310 } else { /* no terminal at all--default it */
311 buf[0] = '\n';
312 r = 1;
313 }
314 if (r <= 0)
315 buf[0] = 0;
316 else
317 buf[r] = '\0';
318 if (!tty2)
319 say("%s", buf);
320 }
321
322 /*
323 * How to handle certain events when not in a critical region.
324 */
325 void
326 set_signals(int reset)
327 {
328 static void (*hupval)(int);
329 static void (*intval)(int);
330
331 if (!reset) {
332 hupval = signal(SIGHUP, SIG_IGN);
333 if (hupval != SIG_IGN)
334 hupval = my_exit;
335 intval = signal(SIGINT, SIG_IGN);
336 if (intval != SIG_IGN)
337 intval = my_exit;
338 }
339 Signal(SIGHUP, hupval);
340 Signal(SIGINT, intval);
341 }
342
343 /*
344 * How to handle certain events when in a critical region.
345 */
346 void
347 ignore_signals()
348 {
349 Signal(SIGHUP, SIG_IGN);
350 Signal(SIGINT, SIG_IGN);
351 }
352
353 /*
354 * Make sure we'll have the directories to create a file.
355 * If `striplast' is TRUE, ignore the last element of `filename'.
356 */
357 void
358 makedirs(char *filename, bool striplast)
359 {
360 char tmpbuf[MAXPATHLEN];
361 char *s = tmpbuf;
362 char *dirv[MAXPATHLEN]; /* Point to the NULs between elements. */
363 int i;
364 int dirvp = 0; /* Number of finished entries in dirv. */
365
366 /*
367 * Copy `filename' into `tmpbuf' with a NUL instead of a slash
368 * between the directories.
369 */
370 while (*filename) {
371 if (*filename == '/') {
372 filename++;
373 dirv[dirvp++] = s;
374 *s++ = '\0';
375 } else {
376 *s++ = *filename++;
377 }
378 }
379 *s = '\0';
380 dirv[dirvp] = s;
381 if (striplast)
382 dirvp--;
383 if (dirvp < 0)
384 return;
385
386 strlcpy(buf, "mkdir", sizeof(buf));
387 s = buf;
388 for (i = 0; i <= dirvp; i++) {
389 struct stat sbuf;
390
391 if (stat(tmpbuf, &sbuf) && errno == ENOENT) {
392 while (*s)
393 s++;
394 *s++ = ' ';
395 strlcpy(s, tmpbuf, sizeof(buf) - (s - buf));
396 }
397 *dirv[i] = '/';
398 }
399 if (s != buf)
400 system(buf);
401 }
402
403 /*
404 * Make filenames more reasonable.
405 */
406 char *
407 fetchname(char *at, int strip_leading, int assume_exists)
408 {
409 char *fullname;
410 char *name;
411 char *t;
412 char tmpbuf[MAXPATHLEN];
413 int sleading = strip_leading;
414
415 if (!at)
416 return NULL;
417 while (isspace((unsigned char)*at))
418 at++;
419 #ifdef DEBUGGING
420 if (debug & 128)
421 say("fetchname %s %d %d\n", at, strip_leading, assume_exists);
422 #endif
423 filename_is_dev_null = FALSE;
424 if (strnEQ(at, "/dev/null", 9)) {
425 /* So files can be created by diffing against /dev/null. */
426 filename_is_dev_null = TRUE;
427 return NULL;
428 }
429 name = fullname = t = xstrdup(at);
430
431 /* Strip off up to `sleading' leading slashes and null terminate. */
432 for (; *t && !isspace((unsigned char)*t); t++)
433 if (*t == '/')
434 if (--sleading >= 0)
435 name = t + 1;
436 *t = '\0';
437
438 /*
439 * If no -p option was given (957 is the default value!),
440 * we were given a relative pathname,
441 * and the leading directories that we just stripped off all exist,
442 * put them back on.
443 */
444 if (strip_leading == 957 && name != fullname && *fullname != '/') {
445 name[-1] = '\0';
446 if (stat(fullname, &filestat) == 0 &&
447 S_ISDIR(filestat.st_mode)) {
448 name[-1] = '/';
449 name = fullname;
450 }
451 }
452
453 name = xstrdup(name);
454 free(fullname);
455
456 if (stat(name, &filestat) && !assume_exists) {
457 char *filebase = basename(name);
458 size_t pathlen = filebase - name;
459
460 /* Put any leading path into `tmpbuf'. */
461 if (pathlen >= sizeof(tmpbuf))
462 return NULL;
463 strncpy(tmpbuf, name, pathlen);
464 tmpbuf[pathlen] = '\0';
465
466 #define try(f, a1, a2) \
467 (snprintf(tmpbuf + pathlen, sizeof(tmpbuf) - pathlen, f, a1, a2), \
468 stat(tmpbuf, &filestat) == 0)
469 #define try1(f, a1) \
470 (snprintf(tmpbuf + pathlen, sizeof(tmpbuf) - pathlen, f, a1), \
471 stat(tmpbuf, &filestat) == 0)
472 if (try("RCS/%s%s", filebase, RCSSUFFIX) ||
473 try1("RCS/%s" , filebase) ||
474 try( "%s%s", filebase, RCSSUFFIX) ||
475 try("SCCS/%s%s", SCCSPREFIX, filebase) ||
476 try( "%s%s", SCCSPREFIX, filebase))
477 return name;
478 free(name);
479 name = NULL;
480 }
481
482 return name;
483 }
484