xref: /netbsd/usr.bin/patch/util.c (revision ef4b3d62)
1 /*	$NetBSD: util.c,v 1.16 2003/07/12 13:47:44 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.16 2003/07/12 13:47:44 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 		Strcpy(bakname, origprae);
73 		Strcat(bakname, to);
74 	} else {
75 #ifndef NODIR
76 		char *backupname = find_backup_file_name(to);
77 		Strcpy(bakname, backupname);
78 		free(backupname);
79 #else /* NODIR */
80 		Strcpy(bakname, to);
81     		Strcat(bakname, simple_backup_suffix);
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 	strcpy(buf, "mkdir");
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 			strcpy(s, tmpbuf);
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 		strncpy(tmpbuf, name, pathlen);
462 
463 #define try(f, a1, a2) \
464     (Sprintf(tmpbuf + pathlen, f, a1, a2), stat(tmpbuf, &filestat) == 0)
465 #define try1(f, a1) \
466     (Sprintf(tmpbuf + pathlen, f, a1), stat(tmpbuf, &filestat) == 0)
467 		if (try("RCS/%s%s", filebase, RCSSUFFIX) ||
468 		    try1("RCS/%s"  , filebase) ||
469 		    try(    "%s%s", filebase, RCSSUFFIX) ||
470 		    try("SCCS/%s%s", SCCSPREFIX, filebase) ||
471 		    try(     "%s%s", SCCSPREFIX, filebase))
472 			return name;
473 		free(name);
474 		name = NULL;
475 	}
476 
477 	return name;
478 }
479