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