1 |
|
|
/* $OpenBSD: rcsutil.c,v 1.46 2017/08/29 16:47:33 otto Exp $ */ |
2 |
|
|
/* |
3 |
|
|
* Copyright (c) 2005, 2006 Joris Vink <joris@openbsd.org> |
4 |
|
|
* Copyright (c) 2006 Xavier Santolaria <xsa@openbsd.org> |
5 |
|
|
* Copyright (c) 2006 Niall O'Higgins <niallo@openbsd.org> |
6 |
|
|
* Copyright (c) 2006 Ray Lai <ray@openbsd.org> |
7 |
|
|
* All rights reserved. |
8 |
|
|
* |
9 |
|
|
* Redistribution and use in source and binary forms, with or without |
10 |
|
|
* modification, are permitted provided that the following conditions |
11 |
|
|
* are met: |
12 |
|
|
* |
13 |
|
|
* 1. Redistributions of source code must retain the above copyright |
14 |
|
|
* notice, this list of conditions and the following disclaimer. |
15 |
|
|
* 2. The name of the author may not be used to endorse or promote products |
16 |
|
|
* derived from this software without specific prior written permission. |
17 |
|
|
* |
18 |
|
|
* THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, |
19 |
|
|
* INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY |
20 |
|
|
* AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL |
21 |
|
|
* THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, |
22 |
|
|
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, |
23 |
|
|
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; |
24 |
|
|
* OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, |
25 |
|
|
* WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR |
26 |
|
|
* OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF |
27 |
|
|
* ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
28 |
|
|
*/ |
29 |
|
|
|
30 |
|
|
#include <sys/stat.h> |
31 |
|
|
#include <sys/time.h> |
32 |
|
|
|
33 |
|
|
#include <ctype.h> |
34 |
|
|
#include <err.h> |
35 |
|
|
#include <fcntl.h> |
36 |
|
|
#include <stdio.h> |
37 |
|
|
#include <stdlib.h> |
38 |
|
|
#include <string.h> |
39 |
|
|
#include <unistd.h> |
40 |
|
|
|
41 |
|
|
#include "rcsprog.h" |
42 |
|
|
|
43 |
|
|
/* |
44 |
|
|
* rcs_get_mtime() |
45 |
|
|
* |
46 |
|
|
* Get <filename> last modified time. |
47 |
|
|
* Returns last modified time on success, or -1 on failure. |
48 |
|
|
*/ |
49 |
|
|
time_t |
50 |
|
|
rcs_get_mtime(RCSFILE *file) |
51 |
|
|
{ |
52 |
|
|
struct stat st; |
53 |
|
|
time_t mtime; |
54 |
|
|
|
55 |
|
|
if (file->rf_file == NULL) |
56 |
|
|
return (-1); |
57 |
|
|
|
58 |
|
|
if (fstat(fileno(file->rf_file), &st) == -1) { |
59 |
|
|
warn("%s", file->rf_path); |
60 |
|
|
return (-1); |
61 |
|
|
} |
62 |
|
|
|
63 |
|
|
mtime = st.st_mtimespec.tv_sec; |
64 |
|
|
|
65 |
|
|
return (mtime); |
66 |
|
|
} |
67 |
|
|
|
68 |
|
|
/* |
69 |
|
|
* rcs_set_mtime() |
70 |
|
|
* |
71 |
|
|
* Set <filename> last modified time to <mtime> if it's not set to -1. |
72 |
|
|
*/ |
73 |
|
|
void |
74 |
|
|
rcs_set_mtime(RCSFILE *file, time_t mtime) |
75 |
|
|
{ |
76 |
|
|
static struct timeval tv[2]; |
77 |
|
|
|
78 |
|
|
if (file->rf_file == NULL || mtime == -1) |
79 |
|
|
return; |
80 |
|
|
|
81 |
|
|
tv[0].tv_sec = mtime; |
82 |
|
|
tv[1].tv_sec = tv[0].tv_sec; |
83 |
|
|
|
84 |
|
|
if (futimes(fileno(file->rf_file), tv) == -1) |
85 |
|
|
err(1, "utimes"); |
86 |
|
|
} |
87 |
|
|
|
88 |
|
|
int |
89 |
|
|
rcs_getopt(int argc, char **argv, const char *optstr) |
90 |
|
|
{ |
91 |
|
|
char *a; |
92 |
|
|
const char *c; |
93 |
|
|
static int i = 1; |
94 |
|
|
int opt, hasargument, ret; |
95 |
|
|
|
96 |
|
|
hasargument = 0; |
97 |
|
1000 |
rcs_optarg = NULL; |
98 |
|
|
|
99 |
✗✓ |
500 |
if (i >= argc) |
100 |
|
|
return (-1); |
101 |
|
|
|
102 |
|
500 |
a = argv[i++]; |
103 |
✓✓ |
500 |
if (*a++ != '-') |
104 |
|
167 |
return (-1); |
105 |
|
|
|
106 |
|
|
ret = 0; |
107 |
|
333 |
opt = *a; |
108 |
✓✗ |
15098 |
for (c = optstr; *c != '\0'; c++) { |
109 |
✓✓ |
7549 |
if (*c == opt) { |
110 |
|
333 |
a++; |
111 |
|
|
ret = opt; |
112 |
|
|
|
113 |
✓✓ |
333 |
if (*(c + 1) == ':') { |
114 |
✓✓ |
225 |
if (*(c + 2) == ':') { |
115 |
✓✓ |
200 |
if (*a != '\0') |
116 |
|
52 |
hasargument = 1; |
117 |
|
|
} else { |
118 |
✓✗ |
25 |
if (*a != '\0') { |
119 |
|
|
hasargument = 1; |
120 |
|
|
} else { |
121 |
|
|
ret = 1; |
122 |
|
|
break; |
123 |
|
|
} |
124 |
|
|
} |
125 |
|
|
} |
126 |
|
|
|
127 |
✓✓ |
333 |
if (hasargument == 1) |
128 |
|
77 |
rcs_optarg = a; |
129 |
|
|
|
130 |
✓✗ |
333 |
if (ret == opt) |
131 |
|
333 |
rcs_optind++; |
132 |
|
|
break; |
133 |
|
|
} |
134 |
|
|
} |
135 |
|
|
|
136 |
✗✓ |
333 |
if (ret == 0) |
137 |
|
|
warnx("unknown option -%c", opt); |
138 |
✗✓ |
333 |
else if (ret == 1) |
139 |
|
|
warnx("missing argument for option -%c", opt); |
140 |
|
|
|
141 |
|
333 |
return (ret); |
142 |
|
500 |
} |
143 |
|
|
|
144 |
|
|
/* |
145 |
|
|
* rcs_choosefile() |
146 |
|
|
* |
147 |
|
|
* Given a relative filename, decide where the corresponding RCS file |
148 |
|
|
* should be. Tries each extension until a file is found. If no file |
149 |
|
|
* was found, returns a path with the first extension. |
150 |
|
|
* |
151 |
|
|
* Opens and returns file descriptor to RCS file. |
152 |
|
|
*/ |
153 |
|
|
int |
154 |
|
|
rcs_choosefile(const char *filename, char *out, size_t len) |
155 |
|
|
{ |
156 |
|
|
int fd; |
157 |
|
336 |
struct stat sb; |
158 |
|
168 |
char *p, *ext, name[PATH_MAX], *next, *ptr, rcsdir[PATH_MAX], |
159 |
|
|
*suffixes, rcspath[PATH_MAX]; |
160 |
|
|
|
161 |
|
|
/* |
162 |
|
|
* If `filename' contains a directory, `rcspath' contains that |
163 |
|
|
* directory, including a trailing slash. Otherwise `rcspath' |
164 |
|
|
* contains an empty string. |
165 |
|
|
*/ |
166 |
✗✓ |
168 |
if (strlcpy(rcspath, filename, sizeof(rcspath)) >= sizeof(rcspath)) |
167 |
|
|
errx(1, "rcs_choosefile: truncation"); |
168 |
|
|
|
169 |
|
|
/* If `/' is found, end string after `/'. */ |
170 |
✓✓ |
168 |
if ((ptr = strrchr(rcspath, '/')) != NULL) |
171 |
|
1 |
*(++ptr) = '\0'; |
172 |
|
|
else |
173 |
|
167 |
rcspath[0] = '\0'; |
174 |
|
|
|
175 |
|
|
/* Append RCS/ to `rcspath' if it exists. */ |
176 |
✓✗✗✓
|
336 |
if (strlcpy(rcsdir, rcspath, sizeof(rcsdir)) >= sizeof(rcsdir) || |
177 |
|
168 |
strlcat(rcsdir, RCSDIR, sizeof(rcsdir)) >= sizeof(rcsdir)) |
178 |
|
|
errx(1, "rcs_choosefile: truncation"); |
179 |
|
|
|
180 |
✓✓✓✗
|
246 |
if (stat(rcsdir, &sb) == 0 && S_ISDIR(sb.st_mode)) |
181 |
✗✓ |
156 |
if (strlcpy(rcspath, rcsdir, sizeof(rcspath)) |
182 |
✓✗ |
78 |
>= sizeof(rcspath) || |
183 |
|
78 |
strlcat(rcspath, "/", sizeof(rcspath)) >= sizeof(rcspath)) |
184 |
|
|
errx(1, "rcs_choosefile: truncation"); |
185 |
|
|
|
186 |
|
|
/* Name of file without path. */ |
187 |
✓✓ |
168 |
if ((ptr = strrchr(filename, '/')) == NULL) { |
188 |
✗✓ |
167 |
if (strlcpy(name, filename, sizeof(name)) >= sizeof(name)) |
189 |
|
|
errx(1, "rcs_choosefile: truncation"); |
190 |
|
|
} else { |
191 |
|
|
/* Skip `/'. */ |
192 |
✗✓ |
1 |
if (strlcpy(name, ptr + 1, sizeof(name)) >= sizeof(name)) |
193 |
|
|
errx(1, "rcs_choosefile: truncation"); |
194 |
|
|
} |
195 |
|
|
|
196 |
|
|
/* Name of RCS file without an extension. */ |
197 |
✗✓ |
168 |
if (strlcat(rcspath, name, sizeof(rcspath)) >= sizeof(rcspath)) |
198 |
|
|
errx(1, "rcs_choosefile: truncation"); |
199 |
|
|
|
200 |
|
|
/* |
201 |
|
|
* If only the empty suffix was given, use existing rcspath. |
202 |
|
|
* This ensures that there is at least one suffix for strsep(). |
203 |
|
|
*/ |
204 |
✓✓ |
168 |
if (strcmp(rcs_suffixes, "") == 0) { |
205 |
✗✓ |
1 |
if (strlcpy(out, rcspath, len) >= len) |
206 |
|
|
errx(1, "rcs_choosefile: truncation"); |
207 |
|
1 |
fd = open(rcspath, O_RDONLY); |
208 |
|
1 |
return (fd); |
209 |
|
|
} |
210 |
|
|
|
211 |
|
|
/* |
212 |
|
|
* Cycle through slash-separated `rcs_suffixes', appending each |
213 |
|
|
* extension to `rcspath' and testing if the file exists. If it |
214 |
|
|
* does, return that string. Otherwise return path with first |
215 |
|
|
* extension. |
216 |
|
|
*/ |
217 |
|
167 |
suffixes = xstrdup(rcs_suffixes); |
218 |
✓✓ |
419 |
for (next = suffixes; (ext = strsep(&next, "/")) != NULL;) { |
219 |
|
210 |
char fpath[PATH_MAX]; |
220 |
|
|
|
221 |
✓✓ |
210 |
if ((p = strrchr(rcspath, ',')) != NULL) { |
222 |
✗✓ |
2 |
if (!strcmp(p, ext)) { |
223 |
|
|
if ((fd = open(rcspath, O_RDONLY)) == -1) |
224 |
|
|
continue; |
225 |
|
|
|
226 |
|
|
if (fstat(fd, &sb) == -1) |
227 |
|
|
err(1, "%s", rcspath); |
228 |
|
|
|
229 |
|
|
if (strlcpy(out, rcspath, len) >= len) |
230 |
|
|
errx(1, "rcs_choosefile; truncation"); |
231 |
|
|
|
232 |
|
|
free(suffixes); |
233 |
|
|
return (fd); |
234 |
|
|
} |
235 |
|
|
|
236 |
|
2 |
continue; |
237 |
|
|
} |
238 |
|
|
|
239 |
|
|
/* Construct RCS file path. */ |
240 |
✓✗✗✓
|
416 |
if (strlcpy(fpath, rcspath, sizeof(fpath)) >= sizeof(fpath) || |
241 |
|
208 |
strlcat(fpath, ext, sizeof(fpath)) >= sizeof(fpath)) |
242 |
|
|
errx(1, "rcs_choosefile: truncation"); |
243 |
|
|
|
244 |
|
|
/* Don't use `filename' as RCS file. */ |
245 |
✓✓ |
208 |
if (strcmp(fpath, filename) == 0) |
246 |
|
24 |
continue; |
247 |
|
|
|
248 |
✓✓ |
184 |
if ((fd = open(fpath, O_RDONLY)) == -1) |
249 |
|
59 |
continue; |
250 |
|
|
|
251 |
✗✓ |
125 |
if (fstat(fd, &sb) == -1) |
252 |
|
|
err(1, "%s", fpath); |
253 |
|
|
|
254 |
✗✓ |
125 |
if (strlcpy(out, fpath, len) >= len) |
255 |
|
|
errx(1, "rcs_choosefile: truncation"); |
256 |
|
|
|
257 |
|
125 |
free(suffixes); |
258 |
|
125 |
return (fd); |
259 |
✓✓ |
210 |
} |
260 |
|
|
|
261 |
|
|
/* |
262 |
|
|
* `suffixes' should now be NUL separated, so the first |
263 |
|
|
* extension can be read just by reading `suffixes'. |
264 |
|
|
*/ |
265 |
✗✓ |
42 |
if (strlcat(rcspath, suffixes, sizeof(rcspath)) >= sizeof(rcspath)) |
266 |
|
|
errx(1, "rcs_choosefile: truncation"); |
267 |
|
|
|
268 |
|
42 |
free(suffixes); |
269 |
|
|
|
270 |
✗✓ |
42 |
if (strlcpy(out, rcspath, len) >= len) |
271 |
|
|
errx(1, "rcs_choosefile: truncation"); |
272 |
|
|
|
273 |
|
42 |
fd = open(rcspath, O_RDONLY); |
274 |
|
|
|
275 |
|
42 |
return (fd); |
276 |
|
168 |
} |
277 |
|
|
|
278 |
|
|
/* |
279 |
|
|
* Set <str> to <new_str>. Print warning if <str> is redefined. |
280 |
|
|
*/ |
281 |
|
|
void |
282 |
|
|
rcs_setrevstr(char **str, char *new_str) |
283 |
|
|
{ |
284 |
✓✓ |
276 |
if (new_str == NULL) |
285 |
|
|
return; |
286 |
✗✓ |
5 |
if (*str != NULL) |
287 |
|
|
warnx("redefinition of revision number"); |
288 |
|
5 |
*str = new_str; |
289 |
|
143 |
} |
290 |
|
|
|
291 |
|
|
/* |
292 |
|
|
* Set <str1> or <str2> to <new_str>, depending on which is not set. |
293 |
|
|
* If both are set, error out. |
294 |
|
|
*/ |
295 |
|
|
void |
296 |
|
|
rcs_setrevstr2(char **str1, char **str2, char *new_str) |
297 |
|
|
{ |
298 |
✓✓ |
26 |
if (new_str == NULL) |
299 |
|
|
return; |
300 |
✓✓ |
9 |
if (*str1 == NULL) |
301 |
|
5 |
*str1 = new_str; |
302 |
✓✗ |
4 |
else if (*str2 == NULL) |
303 |
|
4 |
*str2 = new_str; |
304 |
|
|
else |
305 |
|
|
errx(1, "too many revision numbers"); |
306 |
|
13 |
} |
307 |
|
|
|
308 |
|
|
/* |
309 |
|
|
* Get revision from file. The revision can be specified as a symbol or |
310 |
|
|
* a revision number. |
311 |
|
|
*/ |
312 |
|
|
RCSNUM * |
313 |
|
|
rcs_getrevnum(const char *rev_str, RCSFILE *file) |
314 |
|
|
{ |
315 |
|
|
RCSNUM *rev; |
316 |
|
|
|
317 |
|
|
/* Search for symbol. */ |
318 |
|
24 |
rev = rcs_sym_getrev(file, rev_str); |
319 |
|
|
|
320 |
|
|
/* Search for revision number. */ |
321 |
✓✓ |
12 |
if (rev == NULL) |
322 |
|
7 |
rev = rcsnum_parse(rev_str); |
323 |
|
|
|
324 |
|
12 |
return (rev); |
325 |
|
|
} |
326 |
|
|
|
327 |
|
|
/* |
328 |
|
|
* Prompt for and store user's input in an allocated string. |
329 |
|
|
* |
330 |
|
|
* Returns the string's pointer. |
331 |
|
|
*/ |
332 |
|
|
char * |
333 |
|
|
rcs_prompt(const char *prompt, int flags) |
334 |
|
|
{ |
335 |
|
|
BUF *bp; |
336 |
|
88 |
size_t len; |
337 |
|
|
char *buf; |
338 |
|
|
|
339 |
✓✓✗✓
|
58 |
if (!(flags & INTERACTIVE) && isatty(STDIN_FILENO)) |
340 |
|
|
flags |= INTERACTIVE; |
341 |
|
|
|
342 |
|
44 |
bp = buf_alloc(0); |
343 |
✓✓ |
44 |
if (flags & INTERACTIVE) |
344 |
|
30 |
(void)fprintf(stderr, "%s", prompt); |
345 |
✓✓ |
44 |
if (flags & INTERACTIVE) |
346 |
|
30 |
(void)fprintf(stderr, ">> "); |
347 |
✓✗ |
88 |
clearerr(stdin); |
348 |
✓✓ |
70 |
while ((buf = fgetln(stdin, &len)) != NULL) { |
349 |
|
|
/* The last line may not be EOL terminated. */ |
350 |
✓✓✓✗ ✓✓ |
85 |
if (buf[0] == '.' && (len == 1 || buf[1] == '\n')) |
351 |
|
|
break; |
352 |
|
|
else |
353 |
|
26 |
buf_append(bp, buf, len); |
354 |
|
|
|
355 |
✓✓ |
26 |
if (flags & INTERACTIVE) |
356 |
|
18 |
(void)fprintf(stderr, ">> "); |
357 |
|
|
} |
358 |
|
44 |
buf_putc(bp, '\0'); |
359 |
|
|
|
360 |
|
88 |
return (buf_release(bp)); |
361 |
|
44 |
} |
362 |
|
|
|
363 |
|
|
u_int |
364 |
|
|
rcs_rev_select(RCSFILE *file, const char *range) |
365 |
|
|
{ |
366 |
|
|
int i; |
367 |
|
|
u_int nrev; |
368 |
|
2 |
const char *ep; |
369 |
|
|
char *lstr, *rstr; |
370 |
|
|
struct rcs_delta *rdp; |
371 |
|
|
struct rcs_argvector *revargv, *revrange; |
372 |
|
1 |
RCSNUM lnum, rnum; |
373 |
|
|
|
374 |
|
|
nrev = 0; |
375 |
|
1 |
(void)memset(&lnum, 0, sizeof(lnum)); |
376 |
|
1 |
(void)memset(&rnum, 0, sizeof(rnum)); |
377 |
|
|
|
378 |
✗✓ |
1 |
if (range == NULL) { |
379 |
|
|
TAILQ_FOREACH(rdp, &file->rf_delta, rd_list) |
380 |
|
|
if (rcsnum_cmp(rdp->rd_num, file->rf_head, 0) == 0) { |
381 |
|
|
rdp->rd_flags |= RCS_RD_SELECT; |
382 |
|
|
return (1); |
383 |
|
|
} |
384 |
|
|
return (0); |
385 |
|
|
} |
386 |
|
|
|
387 |
|
1 |
revargv = rcs_strsplit(range, ","); |
388 |
✓✓ |
4 |
for (i = 0; revargv->argv[i] != NULL; i++) { |
389 |
|
1 |
revrange = rcs_strsplit(revargv->argv[i], ":"); |
390 |
✗✓ |
1 |
if (revrange->argv[0] == NULL) |
391 |
|
|
/* should not happen */ |
392 |
|
|
errx(1, "invalid revision range: %s", revargv->argv[i]); |
393 |
✗✓ |
1 |
else if (revrange->argv[1] == NULL) |
394 |
|
|
lstr = rstr = revrange->argv[0]; |
395 |
|
|
else { |
396 |
✗✓ |
1 |
if (revrange->argv[2] != NULL) |
397 |
|
|
errx(1, "invalid revision range: %s", |
398 |
|
|
revargv->argv[i]); |
399 |
|
|
lstr = revrange->argv[0]; |
400 |
|
|
rstr = revrange->argv[1]; |
401 |
✗✓ |
1 |
if (strcmp(lstr, "") == 0) |
402 |
|
|
lstr = NULL; |
403 |
✗✓ |
1 |
if (strcmp(rstr, "") == 0) |
404 |
|
|
rstr = NULL; |
405 |
|
|
} |
406 |
|
|
|
407 |
✗✓ |
1 |
if (lstr == NULL) |
408 |
|
|
lstr = RCS_HEAD_INIT; |
409 |
✓✗✗✓
|
2 |
if (rcsnum_aton(lstr, &ep, &lnum) == 0 || (*ep != '\0')) |
410 |
|
|
errx(1, "invalid revision: %s", lstr); |
411 |
|
|
|
412 |
✓✗ |
1 |
if (rstr != NULL) { |
413 |
✓✗✗✓
|
2 |
if (rcsnum_aton(rstr, &ep, &rnum) == 0 || (*ep != '\0')) |
414 |
|
|
errx(1, "invalid revision: %s", rstr); |
415 |
|
|
} else |
416 |
|
|
rcsnum_cpy(file->rf_head, &rnum, 0); |
417 |
|
|
|
418 |
|
1 |
rcs_argv_destroy(revrange); |
419 |
|
|
|
420 |
✓✓ |
14 |
TAILQ_FOREACH(rdp, &file->rf_delta, rd_list) |
421 |
✓✓✓✗
|
9 |
if (rcsnum_cmp(rdp->rd_num, &lnum, 0) <= 0 && |
422 |
✓✓ |
4 |
rcsnum_cmp(rdp->rd_num, &rnum, 0) >= 0 && |
423 |
|
3 |
!(rdp->rd_flags & RCS_RD_SELECT)) { |
424 |
|
3 |
rdp->rd_flags |= RCS_RD_SELECT; |
425 |
|
3 |
nrev++; |
426 |
|
3 |
} |
427 |
|
|
} |
428 |
|
1 |
rcs_argv_destroy(revargv); |
429 |
|
|
|
430 |
|
1 |
free(lnum.rn_id); |
431 |
|
1 |
free(rnum.rn_id); |
432 |
|
|
|
433 |
|
1 |
return (nrev); |
434 |
|
1 |
} |
435 |
|
|
|
436 |
|
|
/* |
437 |
|
|
* Load description from <in> to <file>. |
438 |
|
|
* If <in> starts with a `-', <in> is taken as the description. |
439 |
|
|
* Otherwise <in> is the name of the file containing the description. |
440 |
|
|
* If <in> is NULL, the description is read from stdin. |
441 |
|
|
* Returns 0 on success, -1 on failure, setting errno. |
442 |
|
|
*/ |
443 |
|
|
int |
444 |
|
|
rcs_set_description(RCSFILE *file, const char *in, int flags) |
445 |
|
|
{ |
446 |
|
|
BUF *bp; |
447 |
|
|
char *content; |
448 |
|
|
const char *prompt = |
449 |
|
|
"enter description, terminated with single '.' or end of file:\n" |
450 |
|
|
"NOTE: This is NOT the log message!\n"; |
451 |
|
|
|
452 |
|
|
/* Description is in file <in>. */ |
453 |
✓✓✓✓
|
87 |
if (in != NULL && *in != '-') { |
454 |
✗✓ |
1 |
if ((bp = buf_load(in)) == NULL) |
455 |
|
|
return (-1); |
456 |
|
1 |
buf_putc(bp, '\0'); |
457 |
|
1 |
content = buf_release(bp); |
458 |
|
|
/* Description is in <in>. */ |
459 |
✓✓ |
41 |
} else if (in != NULL) |
460 |
|
|
/* Skip leading `-'. */ |
461 |
|
4 |
content = xstrdup(in + 1); |
462 |
|
|
/* Get description from stdin. */ |
463 |
|
|
else |
464 |
|
36 |
content = rcs_prompt(prompt, flags); |
465 |
|
|
|
466 |
|
41 |
rcs_desc_set(file, content); |
467 |
|
41 |
free(content); |
468 |
|
41 |
return (0); |
469 |
|
41 |
} |
470 |
|
|
|
471 |
|
|
/* |
472 |
|
|
* Split the contents of a file into a list of lines. |
473 |
|
|
*/ |
474 |
|
|
struct rcs_lines * |
475 |
|
|
rcs_splitlines(u_char *data, size_t len) |
476 |
|
|
{ |
477 |
|
|
u_char *c, *p; |
478 |
|
|
struct rcs_lines *lines; |
479 |
|
|
struct rcs_line *lp; |
480 |
|
|
size_t i, tlen; |
481 |
|
|
|
482 |
|
136 |
lines = xcalloc(1, sizeof(*lines)); |
483 |
|
68 |
TAILQ_INIT(&(lines->l_lines)); |
484 |
|
|
|
485 |
|
68 |
lp = xcalloc(1, sizeof(*lp)); |
486 |
|
68 |
TAILQ_INSERT_TAIL(&(lines->l_lines), lp, l_list); |
487 |
|
|
|
488 |
|
|
|
489 |
|
|
p = c = data; |
490 |
✓✓ |
14596 |
for (i = 0; i < len; i++) { |
491 |
✓✓✗✓
|
14073 |
if (*p == '\n' || (i == len - 1)) { |
492 |
|
387 |
tlen = p - c + 1; |
493 |
|
387 |
lp = xmalloc(sizeof(*lp)); |
494 |
|
387 |
lp->l_line = c; |
495 |
|
387 |
lp->l_len = tlen; |
496 |
|
387 |
lp->l_lineno = ++(lines->l_nblines); |
497 |
|
387 |
TAILQ_INSERT_TAIL(&(lines->l_lines), lp, l_list); |
498 |
|
387 |
c = p + 1; |
499 |
|
387 |
} |
500 |
|
7230 |
p++; |
501 |
|
|
} |
502 |
|
|
|
503 |
|
68 |
return (lines); |
504 |
|
|
} |
505 |
|
|
|
506 |
|
|
void |
507 |
|
|
rcs_freelines(struct rcs_lines *lines) |
508 |
|
|
{ |
509 |
|
|
struct rcs_line *lp; |
510 |
|
|
|
511 |
✓✓ |
1024 |
while ((lp = TAILQ_FIRST(&(lines->l_lines))) != NULL) { |
512 |
✓✓ |
1230 |
TAILQ_REMOVE(&(lines->l_lines), lp, l_list); |
513 |
|
410 |
free(lp); |
514 |
|
|
} |
515 |
|
|
|
516 |
|
68 |
free(lines); |
517 |
|
68 |
} |
518 |
|
|
|
519 |
|
|
BUF * |
520 |
|
|
rcs_patchfile(u_char *data, size_t dlen, u_char *patch, size_t plen, |
521 |
|
|
int (*p)(struct rcs_lines *, struct rcs_lines *)) |
522 |
|
|
{ |
523 |
|
|
struct rcs_lines *dlines, *plines; |
524 |
|
|
struct rcs_line *lp; |
525 |
|
|
BUF *res; |
526 |
|
|
|
527 |
|
62 |
dlines = rcs_splitlines(data, dlen); |
528 |
|
31 |
plines = rcs_splitlines(patch, plen); |
529 |
|
|
|
530 |
✗✓ |
31 |
if (p(dlines, plines) < 0) { |
531 |
|
|
rcs_freelines(dlines); |
532 |
|
|
rcs_freelines(plines); |
533 |
|
|
return (NULL); |
534 |
|
|
} |
535 |
|
|
|
536 |
|
31 |
res = buf_alloc(1024); |
537 |
✓✓ |
742 |
TAILQ_FOREACH(lp, &dlines->l_lines, l_list) { |
538 |
✓✓ |
340 |
if (lp->l_line == NULL) |
539 |
|
|
continue; |
540 |
|
309 |
buf_append(res, lp->l_line, lp->l_len); |
541 |
|
309 |
} |
542 |
|
|
|
543 |
|
31 |
rcs_freelines(dlines); |
544 |
|
31 |
rcs_freelines(plines); |
545 |
|
31 |
return (res); |
546 |
|
31 |
} |
547 |
|
|
|
548 |
|
|
/* |
549 |
|
|
* rcs_yesno() |
550 |
|
|
* |
551 |
|
|
* Read a char from standard input, returns defc if the |
552 |
|
|
* user enters an equivalent to defc, else whatever char |
553 |
|
|
* was entered. Converts input to lower case. |
554 |
|
|
*/ |
555 |
|
|
int |
556 |
|
|
rcs_yesno(int defc) |
557 |
|
|
{ |
558 |
|
|
int c, ret; |
559 |
|
|
|
560 |
|
|
fflush(stderr); |
561 |
|
|
fflush(stdout); |
562 |
|
|
|
563 |
|
|
clearerr(stdin); |
564 |
|
|
if (isalpha(c = getchar())) |
565 |
|
|
c = tolower(c); |
566 |
|
|
if (c == defc || c == '\n' || (c == EOF && feof(stdin))) |
567 |
|
|
ret = defc; |
568 |
|
|
else |
569 |
|
|
ret = c; |
570 |
|
|
|
571 |
|
|
while (c != EOF && c != '\n') |
572 |
|
|
c = getchar(); |
573 |
|
|
|
574 |
|
|
return (ret); |
575 |
|
|
} |
576 |
|
|
|
577 |
|
|
/* |
578 |
|
|
* rcs_strsplit() |
579 |
|
|
* |
580 |
|
|
* Split a string <str> of <sep>-separated values and allocate |
581 |
|
|
* an argument vector for the values found. |
582 |
|
|
*/ |
583 |
|
|
struct rcs_argvector * |
584 |
|
|
rcs_strsplit(const char *str, const char *sep) |
585 |
|
|
{ |
586 |
|
|
struct rcs_argvector *av; |
587 |
|
|
size_t i = 0; |
588 |
|
12 |
char *cp, *p; |
589 |
|
|
|
590 |
|
6 |
cp = xstrdup(str); |
591 |
|
6 |
av = xmalloc(sizeof(*av)); |
592 |
|
6 |
av->str = cp; |
593 |
|
6 |
av->argv = xmalloc(sizeof(*(av->argv))); |
594 |
|
|
|
595 |
✓✓ |
42 |
while ((p = strsep(&cp, sep)) != NULL) { |
596 |
|
15 |
av->argv[i++] = p; |
597 |
|
30 |
av->argv = xreallocarray(av->argv, |
598 |
|
15 |
i + 1, sizeof(*(av->argv))); |
599 |
|
|
} |
600 |
|
6 |
av->argv[i] = NULL; |
601 |
|
|
|
602 |
|
6 |
return (av); |
603 |
|
6 |
} |
604 |
|
|
|
605 |
|
|
/* |
606 |
|
|
* rcs_argv_destroy() |
607 |
|
|
* |
608 |
|
|
* Free an argument vector previously allocated by rcs_strsplit(). |
609 |
|
|
*/ |
610 |
|
|
void |
611 |
|
|
rcs_argv_destroy(struct rcs_argvector *av) |
612 |
|
|
{ |
613 |
|
12 |
free(av->str); |
614 |
|
6 |
free(av->argv); |
615 |
|
6 |
free(av); |
616 |
|
6 |
} |
617 |
|
|
|
618 |
|
|
/* |
619 |
|
|
* Strip suffix from filename. |
620 |
|
|
*/ |
621 |
|
|
void |
622 |
|
|
rcs_strip_suffix(char *filename) |
623 |
|
|
{ |
624 |
|
220 |
char *p, *suffixes, *next, *ext; |
625 |
|
|
|
626 |
✓✓ |
110 |
if ((p = strrchr(filename, ',')) != NULL) { |
627 |
|
1 |
suffixes = xstrdup(rcs_suffixes); |
628 |
✓✓ |
4 |
for (next = suffixes; (ext = strsep(&next, "/")) != NULL;) { |
629 |
✓✗ |
2 |
if (!strcmp(p, ext)) { |
630 |
|
|
*p = '\0'; |
631 |
|
|
break; |
632 |
|
|
} |
633 |
|
|
} |
634 |
|
1 |
free(suffixes); |
635 |
|
1 |
} |
636 |
|
110 |
} |