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