xref: /openbsd/usr.bin/patch/util.c (revision b7041c07)
1 /*	$OpenBSD: util.c,v 1.46 2021/10/24 21:24:17 deraadt 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 #include <sys/stat.h>
30 
31 #include <ctype.h>
32 #include <errno.h>
33 #include <fcntl.h>
34 #include <paths.h>
35 #include <signal.h>
36 #include <stdarg.h>
37 #include <stdlib.h>
38 #include <stdio.h>
39 #include <string.h>
40 #include <unistd.h>
41 
42 #include "common.h"
43 #include "util.h"
44 #include "backupfile.h"
45 
46 /* Rename a file, copying it if necessary. */
47 
48 int
move_file(const char * from,const char * to)49 move_file(const char *from, const char *to)
50 {
51 	int	fromfd;
52 	ssize_t	i;
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, O_RDONLY);
62 		if (fromfd == -1)
63 			pfatal("internal error, can't reopen %s", from);
64 		while ((i = read(fromfd, buf, bufsz)) > 0)
65 			if (write(STDOUT_FILENO, buf, i) != i)
66 				pfatal("write failed");
67 		close(fromfd);
68 		return 0;
69 	}
70 	if (backup_file(to) < 0) {
71 		say("Can't backup %s, output is in %s: %s\n", to, from,
72 		    strerror(errno));
73 		return -1;
74 	}
75 #ifdef DEBUGGING
76 	if (debug & 4)
77 		say("Moving %s to %s.\n", from, to);
78 #endif
79 	if (rename(from, to) == -1) {
80 		if (errno != EXDEV || copy_file(from, to) < 0) {
81 			say("Can't create %s, output is in %s: %s\n",
82 			    to, from, strerror(errno));
83 			return -1;
84 		}
85 	}
86 	return 0;
87 }
88 
89 /* Backup the original file.  */
90 
91 int
backup_file(const char * orig)92 backup_file(const char *orig)
93 {
94 	struct stat	filestat;
95 	char		bakname[PATH_MAX], *s, *simplename;
96 	dev_t		orig_device;
97 	ino_t		orig_inode;
98 
99 	if (backup_type == none || stat(orig, &filestat) != 0)
100 		return 0;			/* nothing to do */
101 	orig_device = filestat.st_dev;
102 	orig_inode = filestat.st_ino;
103 
104 	if (origprae) {
105 		if (strlcpy(bakname, origprae, sizeof(bakname)) >= sizeof(bakname) ||
106 		    strlcat(bakname, orig, sizeof(bakname)) >= sizeof(bakname))
107 			fatal("filename %s too long for buffer\n", origprae);
108 	} else {
109 		if ((s = find_backup_file_name(orig)) == NULL)
110 			fatal("out of memory\n");
111 		if (strlcpy(bakname, s, sizeof(bakname)) >= sizeof(bakname))
112 			fatal("filename %s too long for buffer\n", s);
113 		free(s);
114 	}
115 
116 	if ((simplename = strrchr(bakname, '/')) != NULL)
117 		simplename = simplename + 1;
118 	else
119 		simplename = bakname;
120 
121 	/*
122 	 * Find a backup name that is not the same file. Change the
123 	 * first lowercase char into uppercase; if that isn't
124 	 * sufficient, chop off the first char and try again.
125 	 */
126 	while (stat(bakname, &filestat) == 0 &&
127 	    orig_device == filestat.st_dev && orig_inode == filestat.st_ino) {
128 		/* Skip initial non-lowercase chars.  */
129 		for (s = simplename; *s && !islower((unsigned char)*s); s++)
130 			;
131 		if (*s)
132 			*s = toupper((unsigned char)*s);
133 		else
134 			memmove(simplename, simplename + 1,
135 			    strlen(simplename + 1) + 1);
136 	}
137 #ifdef DEBUGGING
138 	if (debug & 4)
139 		say("Moving %s to %s.\n", orig, bakname);
140 #endif
141 	if (rename(orig, bakname) == -1) {
142 		if (errno != EXDEV || copy_file(orig, bakname) < 0)
143 			return -1;
144 	}
145 	return 0;
146 }
147 
148 /*
149  * Copy a file.
150  */
151 int
copy_file(const char * from,const char * to)152 copy_file(const char *from, const char *to)
153 {
154 	int	tofd, fromfd;
155 	ssize_t	i;
156 
157 	tofd = open(to, O_CREAT|O_TRUNC|O_WRONLY, 0666);
158 	if (tofd == -1)
159 		return -1;
160 	fromfd = open(from, O_RDONLY);
161 	if (fromfd == -1)
162 		pfatal("internal error, can't reopen %s", from);
163 	while ((i = read(fromfd, buf, bufsz)) > 0)
164 		if (write(tofd, buf, i) != i)
165 			pfatal("write to %s failed", to);
166 	close(fromfd);
167 	close(tofd);
168 	return 0;
169 }
170 
171 /*
172  * Allocate a unique area for a string.
173  */
174 char *
savestr(const char * s)175 savestr(const char *s)
176 {
177 	char	*rv;
178 
179 	if (!s)
180 		s = "Oops";
181 	rv = strdup(s);
182 	if (rv == NULL) {
183 		if (using_plan_a)
184 			out_of_mem = true;
185 		else
186 			fatal("out of memory\n");
187 	}
188 	return rv;
189 }
190 
191 /*
192  * Allocate a unique area for a string.  Call fatal if out of memory.
193  */
194 char *
xstrdup(const char * s)195 xstrdup(const char *s)
196 {
197 	char	*rv;
198 
199 	if (!s)
200 		s = "Oops";
201 	rv = strdup(s);
202 	if (rv == NULL)
203 		fatal("out of memory\n");
204 	return rv;
205 }
206 
207 /*
208  * Vanilla terminal output (buffered).
209  */
210 void
say(const char * fmt,...)211 say(const char *fmt, ...)
212 {
213 	va_list	ap;
214 
215 	va_start(ap, fmt);
216 	vfprintf(stdout, fmt, ap);
217 	va_end(ap);
218 	fflush(stdout);
219 }
220 
221 /*
222  * Terminal output, pun intended.
223  */
224 void
fatal(const char * fmt,...)225 fatal(const char *fmt, ...)
226 {
227 	va_list	ap;
228 
229 	va_start(ap, fmt);
230 	fprintf(stderr, "patch: **** ");
231 	vfprintf(stderr, fmt, ap);
232 	va_end(ap);
233 	my_exit(2);
234 }
235 
236 /*
237  * Say something from patch, something from the system, then silence . . .
238  */
239 void
pfatal(const char * fmt,...)240 pfatal(const char *fmt, ...)
241 {
242 	va_list	ap;
243 	int	errnum = errno;
244 
245 	fprintf(stderr, "patch: **** ");
246 	va_start(ap, fmt);
247 	vfprintf(stderr, fmt, ap);
248 	va_end(ap);
249 	fprintf(stderr, ": %s\n", strerror(errnum));
250 	my_exit(2);
251 }
252 
253 /*
254  * Get a response from the user via /dev/tty
255  */
256 void
ask(const char * fmt,...)257 ask(const char *fmt, ...)
258 {
259 	va_list	ap;
260 	ssize_t	nr;
261 	static	int ttyfd = -1;
262 
263 	va_start(ap, fmt);
264 	vfprintf(stdout, fmt, ap);
265 	va_end(ap);
266 	fflush(stdout);
267 	if (ttyfd < 0)
268 		ttyfd = open(_PATH_TTY, O_RDONLY);
269 	if (ttyfd >= 0) {
270 		if ((nr = read(ttyfd, buf, bufsz)) > 0 &&
271 		    buf[nr - 1] == '\n')
272 			buf[nr - 1] = '\0';
273 	}
274 	if (ttyfd == -1 || nr <= 0) {
275 		/* no tty or error reading, pretend user entered 'return' */
276 		putchar('\n');
277 		buf[0] = '\0';
278 	}
279 }
280 
281 /*
282  * How to handle certain events when not in a critical region.
283  */
284 void
set_signals(int reset)285 set_signals(int reset)
286 {
287 	static sig_t	hupval, intval;
288 
289 	if (!reset) {
290 		hupval = signal(SIGHUP, SIG_IGN);
291 		if (hupval != SIG_IGN)
292 			hupval = my_sigexit;
293 		intval = signal(SIGINT, SIG_IGN);
294 		if (intval != SIG_IGN)
295 			intval = my_sigexit;
296 	}
297 	signal(SIGHUP, hupval);
298 	signal(SIGINT, intval);
299 }
300 
301 /*
302  * How to handle certain events when in a critical region.
303  */
304 void
ignore_signals(void)305 ignore_signals(void)
306 {
307 	signal(SIGHUP, SIG_IGN);
308 	signal(SIGINT, SIG_IGN);
309 }
310 
311 /*
312  * Make sure we'll have the directories to create a file. If `striplast' is
313  * true, ignore the last element of `filename'.
314  */
315 
316 void
makedirs(const char * filename,bool striplast)317 makedirs(const char *filename, bool striplast)
318 {
319 	char	*tmpbuf;
320 
321 	if ((tmpbuf = strdup(filename)) == NULL)
322 		fatal("out of memory\n");
323 
324 	if (striplast) {
325 		char	*s = strrchr(tmpbuf, '/');
326 		if (s == NULL) {
327 			free(tmpbuf);
328 			return;	/* nothing to be done */
329 		}
330 		*s = '\0';
331 	}
332 	if (mkpath(tmpbuf) != 0)
333 		pfatal("creation of %s failed", tmpbuf);
334 	free(tmpbuf);
335 }
336 
337 /*
338  * Make filenames more reasonable.
339  */
340 char *
fetchname(const char * at,bool * exists,int strip_leading)341 fetchname(const char *at, bool *exists, int strip_leading)
342 {
343 	char		*fullname, *name, *t;
344 	int		sleading, tab;
345 	struct stat	filestat;
346 
347 	if (at == NULL || *at == '\0')
348 		return NULL;
349 	while (isspace((unsigned char)*at))
350 		at++;
351 #ifdef DEBUGGING
352 	if (debug & 128)
353 		say("fetchname %s %d\n", at, strip_leading);
354 #endif
355 	/* So files can be created by diffing against /dev/null.  */
356 	if (strnEQ(at, _PATH_DEVNULL, sizeof(_PATH_DEVNULL) - 1))
357 		return NULL;
358 	name = fullname = t = savestr(at);
359 
360 	tab = strchr(t, '\t') != NULL;
361 	/* Strip off up to `strip_leading' path components and NUL terminate. */
362 	for (sleading = strip_leading; *t != '\0' && ((tab && *t != '\t') ||
363 	    !isspace((unsigned char)*t)); t++) {
364 		if (t[0] == '/' && t[1] != '/' && t[1] != '\0')
365 			if (--sleading >= 0)
366 				name = t + 1;
367 	}
368 	*t = '\0';
369 
370 	/*
371 	 * If no -p option was given (957 is the default value!), we were
372 	 * given a relative pathname, and the leading directories that we
373 	 * just stripped off all exist, put them back on.
374 	 */
375 	if (strip_leading == 957 && name != fullname && *fullname != '/') {
376 		name[-1] = '\0';
377 		if (stat(fullname, &filestat) == 0 && S_ISDIR(filestat.st_mode)) {
378 			name[-1] = '/';
379 			name = fullname;
380 		}
381 	}
382 	name = savestr(name);
383 	free(fullname);
384 
385 	*exists = stat(name, &filestat) == 0;
386 	return name;
387 }
388 
389 void
version(void)390 version(void)
391 {
392 	fprintf(stderr, "Patch version 2.0-12u8-OpenBSD\n");
393 	my_exit(EXIT_SUCCESS);
394 }
395 
396 void
my_cleanup(void)397 my_cleanup(void)
398 {
399 	unlink(TMPINNAME);
400 	if (!toutkeep)
401 		unlink(TMPOUTNAME);
402 	if (!trejkeep)
403 		unlink(TMPREJNAME);
404 	unlink(TMPPATNAME);
405 }
406 
407 /*
408  * Exit with cleanup.
409  */
410 void
my_exit(int status)411 my_exit(int status)
412 {
413 	my_cleanup();
414 	exit(status);
415 }
416 
417 /*
418  * Exit with cleanup, from a signal handler.
419  */
420 void
my_sigexit(int signo)421 my_sigexit(int signo)
422 {
423 	my_cleanup();
424 	_exit(2);
425 }
426