Bug Summary

File:src/usr.bin/rcs/rcsutil.c
Warning:line 265, column 6
Null pointer passed as 2nd argument to string concatenation function

Annotated Source Code

Press '?' to see keyboard shortcuts

clang -cc1 -cc1 -triple amd64-unknown-openbsd7.0 -analyze -disable-free -disable-llvm-verifier -discard-value-names -main-file-name rcsutil.c -analyzer-store=region -analyzer-opt-analyze-nested-blocks -analyzer-checker=core -analyzer-checker=apiModeling -analyzer-checker=unix -analyzer-checker=deadcode -analyzer-checker=security.insecureAPI.UncheckedReturn -analyzer-checker=security.insecureAPI.getpw -analyzer-checker=security.insecureAPI.gets -analyzer-checker=security.insecureAPI.mktemp -analyzer-checker=security.insecureAPI.mkstemp -analyzer-checker=security.insecureAPI.vfork -analyzer-checker=nullability.NullPassedToNonnull -analyzer-checker=nullability.NullReturnedFromNonnull -analyzer-output plist -w -setup-static-analyzer -mrelocation-model pic -pic-level 1 -pic-is-pie -mframe-pointer=all -relaxed-aliasing -fno-rounding-math -mconstructor-aliases -munwind-tables -target-cpu x86-64 -target-feature +retpoline-indirect-calls -target-feature +retpoline-indirect-branches -tune-cpu generic -debugger-tuning=gdb -fcoverage-compilation-dir=/usr/src/usr.bin/rcs/obj -resource-dir /usr/local/lib/clang/13.0.0 -I /usr/src/usr.bin/rcs -internal-isystem /usr/local/lib/clang/13.0.0/include -internal-externc-isystem /usr/include -O2 -fdebug-compilation-dir=/usr/src/usr.bin/rcs/obj -ferror-limit 19 -fwrapv -D_RET_PROTECTOR -ret-protector -fgnuc-version=4.2.1 -vectorize-loops -vectorize-slp -fno-builtin-malloc -fno-builtin-calloc -fno-builtin-realloc -fno-builtin-valloc -fno-builtin-free -fno-builtin-strdup -fno-builtin-strndup -analyzer-output=html -faddrsig -D__GCC_HAVE_DWARF2_CFI_ASM=1 -o /home/ben/Projects/vmm/scan-build/2022-01-12-194120-40624-1 -x c /usr/src/usr.bin/rcs/rcsutil.c
1/* $OpenBSD: rcsutil.c,v 1.47 2020/10/14 20:07:19 naddy 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 */
49time_t
50rcs_get_mtime(RCSFILE *file)
51{
52 struct stat st;
53 time_t mtime;
54
55 if (file->rf_file == NULL((void *)0))
56 return (-1);
57
58 if (fstat(fileno(file->rf_file)(!__isthreaded ? ((file->rf_file)->_file) : (fileno)(file
->rf_file))
, &st) == -1) {
59 warn("%s", file->rf_path);
60 return (-1);
61 }
62
63 mtime = st.st_mtimespecst_mtim.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 */
73void
74rcs_set_mtime(RCSFILE *file, time_t mtime)
75{
76 static struct timeval tv[2];
77
78 if (file->rf_file == NULL((void *)0) || 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)(!__isthreaded ? ((file->rf_file)->_file) : (fileno)(file
->rf_file))
, tv) == -1)
85 err(1, "utimes");
86}
87
88int
89rcs_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 rcs_optarg = NULL((void *)0);
98
99 if (i >= argc)
100 return (-1);
101
102 a = argv[i++];
103 if (*a++ != '-')
104 return (-1);
105
106 ret = 0;
107 opt = *a;
108 for (c = optstr; *c != '\0'; c++) {
109 if (*c == opt) {
110 a++;
111 ret = opt;
112
113 if (*(c + 1) == ':') {
114 if (*(c + 2) == ':') {
115 if (*a != '\0')
116 hasargument = 1;
117 } else {
118 if (*a != '\0') {
119 hasargument = 1;
120 } else {
121 ret = 1;
122 break;
123 }
124 }
125 }
126
127 if (hasargument == 1)
128 rcs_optarg = a;
129
130 if (ret == opt)
131 rcs_optind++;
132 break;
133 }
134 }
135
136 if (ret == 0)
137 warnx("unknown option -%c", opt);
138 else if (ret == 1)
139 warnx("missing argument for option -%c", opt);
140
141 return (ret);
142}
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 */
153int
154rcs_choosefile(const char *filename, char *out, size_t len)
155{
156 int fd;
157 struct stat sb;
158 char *p, *ext, name[PATH_MAX1024], *next, *ptr, rcsdir[PATH_MAX1024],
159 *suffixes, rcspath[PATH_MAX1024];
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 if (strlcpy(rcspath, filename, sizeof(rcspath)) >= sizeof(rcspath))
1
Assuming the condition is false
2
Taking false branch
167 errx(1, "rcs_choosefile: truncation");
168
169 /* If `/' is found, end string after `/'. */
170 if ((ptr = strrchr(rcspath, '/')) != NULL((void *)0))
3
Assuming the condition is false
4
Taking false branch
171 *(++ptr) = '\0';
172 else
173 rcspath[0] = '\0';
174
175 /* Append RCS/ to `rcspath' if it exists. */
176 if (strlcpy(rcsdir, rcspath, sizeof(rcsdir)) >= sizeof(rcsdir) ||
5
Assuming the condition is false
7
Taking false branch
177 strlcat(rcsdir, RCSDIR"RCS", sizeof(rcsdir)) >= sizeof(rcsdir))
6
Assuming the condition is false
178 errx(1, "rcs_choosefile: truncation");
179
180 if (stat(rcsdir, &sb) == 0 && S_ISDIR(sb.st_mode)((sb.st_mode & 0170000) == 0040000))
8
Assuming the condition is false
181 if (strlcpy(rcspath, rcsdir, sizeof(rcspath))
182 >= sizeof(rcspath) ||
183 strlcat(rcspath, "/", sizeof(rcspath)) >= sizeof(rcspath))
184 errx(1, "rcs_choosefile: truncation");
185
186 /* Name of file without path. */
187 if ((ptr = strrchr(filename, '/')) == NULL((void *)0)) {
9
Assuming the condition is true
10
Taking true branch
188 if (strlcpy(name, filename, sizeof(name)) >= sizeof(name))
11
Taking false branch
189 errx(1, "rcs_choosefile: truncation");
190 } else {
191 /* Skip `/'. */
192 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 if (strlcat(rcspath, name, sizeof(rcspath)) >= sizeof(rcspath))
12
Assuming the condition is false
13
Taking false branch
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 if (strcmp(rcs_suffixes, "") == 0) {
14
Assuming the condition is false
15
Taking false branch
205 if (strlcpy(out, rcspath, len) >= len)
206 errx(1, "rcs_choosefile: truncation");
207 fd = open(rcspath, O_RDONLY0x0000);
208 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 suffixes = xstrdup(rcs_suffixes);
16
Value assigned to 'suffixes'
218 for (next = suffixes; (ext = strsep(&next, "/")) != NULL((void *)0);) {
17
Assuming pointer value is null
18
Loop condition is false. Execution continues on line 265
219 char fpath[PATH_MAX1024];
220
221 if ((p = strrchr(rcspath, ',')) != NULL((void *)0)) {
222 if (!strcmp(p, ext)) {
223 if ((fd = open(rcspath, O_RDONLY0x0000)) == -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 continue;
237 }
238
239 /* Construct RCS file path. */
240 if (strlcpy(fpath, rcspath, sizeof(fpath)) >= sizeof(fpath) ||
241 strlcat(fpath, ext, sizeof(fpath)) >= sizeof(fpath))
242 errx(1, "rcs_choosefile: truncation");
243
244 /* Don't use `filename' as RCS file. */
245 if (strcmp(fpath, filename) == 0)
246 continue;
247
248 if ((fd = open(fpath, O_RDONLY0x0000)) == -1)
249 continue;
250
251 if (fstat(fd, &sb) == -1)
252 err(1, "%s", fpath);
253
254 if (strlcpy(out, fpath, len) >= len)
255 errx(1, "rcs_choosefile: truncation");
256
257 free(suffixes);
258 return (fd);
259 }
260
261 /*
262 * `suffixes' should now be NUL separated, so the first
263 * extension can be read just by reading `suffixes'.
264 */
265 if (strlcat(rcspath, suffixes, sizeof(rcspath)) >= sizeof(rcspath))
19
Null pointer passed as 2nd argument to string concatenation function
266 errx(1, "rcs_choosefile: truncation");
267
268 free(suffixes);
269
270 if (strlcpy(out, rcspath, len) >= len)
271 errx(1, "rcs_choosefile: truncation");
272
273 fd = open(rcspath, O_RDONLY0x0000);
274
275 return (fd);
276}
277
278/*
279 * Set <str> to <new_str>. Print warning if <str> is redefined.
280 */
281void
282rcs_setrevstr(char **str, char *new_str)
283{
284 if (new_str == NULL((void *)0))
285 return;
286 if (*str != NULL((void *)0))
287 warnx("redefinition of revision number");
288 *str = new_str;
289}
290
291/*
292 * Set <str1> or <str2> to <new_str>, depending on which is not set.
293 * If both are set, error out.
294 */
295void
296rcs_setrevstr2(char **str1, char **str2, char *new_str)
297{
298 if (new_str == NULL((void *)0))
299 return;
300 if (*str1 == NULL((void *)0))
301 *str1 = new_str;
302 else if (*str2 == NULL((void *)0))
303 *str2 = new_str;
304 else
305 errx(1, "too many revision numbers");
306}
307
308/*
309 * Get revision from file. The revision can be specified as a symbol or
310 * a revision number.
311 */
312RCSNUM *
313rcs_getrevnum(const char *rev_str, RCSFILE *file)
314{
315 RCSNUM *rev;
316
317 /* Search for symbol. */
318 rev = rcs_sym_getrev(file, rev_str);
319
320 /* Search for revision number. */
321 if (rev == NULL((void *)0))
322 rev = rcsnum_parse(rev_str);
323
324 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 */
332char *
333rcs_prompt(const char *prompt, int flags)
334{
335 BUF *bp;
336 size_t len;
337 char *buf;
338
339 if (!(flags & INTERACTIVE(1<<20)) && isatty(STDIN_FILENO0))
340 flags |= INTERACTIVE(1<<20);
341
342 bp = buf_alloc(0);
343 if (flags & INTERACTIVE(1<<20))
344 (void)fprintf(stderr(&__sF[2]), "%s", prompt);
345 if (flags & INTERACTIVE(1<<20))
346 (void)fprintf(stderr(&__sF[2]), ">> ");
347 clearerr(stdin)(!__isthreaded ? ((void)(((&__sF[0]))->_flags &= ~
(0x0040|0x0020))) : (clearerr)((&__sF[0])))
;
348 while ((buf = fgetln(stdin(&__sF[0]), &len)) != NULL((void *)0)) {
349 /* The last line may not be EOL terminated. */
350 if (buf[0] == '.' && (len == 1 || buf[1] == '\n'))
351 break;
352 else
353 buf_append(bp, buf, len);
354
355 if (flags & INTERACTIVE(1<<20))
356 (void)fprintf(stderr(&__sF[2]), ">> ");
357 }
358 buf_putc(bp, '\0');
359
360 return (buf_release(bp));
361}
362
363u_int
364rcs_rev_select(RCSFILE *file, const char *range)
365{
366 int i;
367 u_int nrev;
368 const char *ep;
369 char *lstr, *rstr;
370 struct rcs_delta *rdp;
371 struct rcs_argvector *revargv, *revrange;
372 RCSNUM lnum, rnum;
373
374 nrev = 0;
375 (void)memset(&lnum, 0, sizeof(lnum));
376 (void)memset(&rnum, 0, sizeof(rnum));
377
378 if (range == NULL((void *)0)) {
379 TAILQ_FOREACH(rdp, &file->rf_delta, rd_list)for((rdp) = ((&file->rf_delta)->tqh_first); (rdp) !=
((void *)0); (rdp) = ((rdp)->rd_list.tqe_next))
380 if (rcsnum_cmp(rdp->rd_num, file->rf_head, 0) == 0) {
381 rdp->rd_flags |= RCS_RD_SELECT0x02;
382 return (1);
383 }
384 return (0);
385 }
386
387 revargv = rcs_strsplit(range, ",");
388 for (i = 0; revargv->argv[i] != NULL((void *)0); i++) {
389 revrange = rcs_strsplit(revargv->argv[i], ":");
390 if (revrange->argv[0] == NULL((void *)0))
391 /* should not happen */
392 errx(1, "invalid revision range: %s", revargv->argv[i]);
393 else if (revrange->argv[1] == NULL((void *)0))
394 lstr = rstr = revrange->argv[0];
395 else {
396 if (revrange->argv[2] != NULL((void *)0))
397 errx(1, "invalid revision range: %s",
398 revargv->argv[i]);
399 lstr = revrange->argv[0];
400 rstr = revrange->argv[1];
401 if (strcmp(lstr, "") == 0)
402 lstr = NULL((void *)0);
403 if (strcmp(rstr, "") == 0)
404 rstr = NULL((void *)0);
405 }
406
407 if (lstr == NULL((void *)0))
408 lstr = RCS_HEAD_INIT"1.1";
409 if (rcsnum_aton(lstr, &ep, &lnum) == 0 || (*ep != '\0'))
410 errx(1, "invalid revision: %s", lstr);
411
412 if (rstr != NULL((void *)0)) {
413 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 rcs_argv_destroy(revrange);
419
420 TAILQ_FOREACH(rdp, &file->rf_delta, rd_list)for((rdp) = ((&file->rf_delta)->tqh_first); (rdp) !=
((void *)0); (rdp) = ((rdp)->rd_list.tqe_next))
421 if (rcsnum_cmp(rdp->rd_num, &lnum, 0) <= 0 &&
422 rcsnum_cmp(rdp->rd_num, &rnum, 0) >= 0 &&
423 !(rdp->rd_flags & RCS_RD_SELECT0x02)) {
424 rdp->rd_flags |= RCS_RD_SELECT0x02;
425 nrev++;
426 }
427 }
428 rcs_argv_destroy(revargv);
429
430 free(lnum.rn_id);
431 free(rnum.rn_id);
432
433 return (nrev);
434}
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 */
443int
444rcs_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 if (in != NULL((void *)0) && *in != '-') {
454 if ((bp = buf_load(in)) == NULL((void *)0))
455 return (-1);
456 buf_putc(bp, '\0');
457 content = buf_release(bp);
458 /* Description is in <in>. */
459 } else if (in != NULL((void *)0))
460 /* Skip leading `-'. */
461 content = xstrdup(in + 1);
462 /* Get description from stdin. */
463 else
464 content = rcs_prompt(prompt, flags);
465
466 rcs_desc_set(file, content);
467 free(content);
468 return (0);
469}
470
471/*
472 * Split the contents of a file into a list of lines.
473 */
474struct rcs_lines *
475rcs_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 lines = xcalloc(1, sizeof(*lines));
483 TAILQ_INIT(&(lines->l_lines))do { (&(lines->l_lines))->tqh_first = ((void *)0); (
&(lines->l_lines))->tqh_last = &(&(lines->
l_lines))->tqh_first; } while (0)
;
484
485 lp = xcalloc(1, sizeof(*lp));
486 TAILQ_INSERT_TAIL(&(lines->l_lines), lp, l_list)do { (lp)->l_list.tqe_next = ((void *)0); (lp)->l_list.
tqe_prev = (&(lines->l_lines))->tqh_last; *(&(lines
->l_lines))->tqh_last = (lp); (&(lines->l_lines)
)->tqh_last = &(lp)->l_list.tqe_next; } while (0)
;
487
488
489 p = c = data;
490 for (i = 0; i < len; i++) {
491 if (*p == '\n' || (i == len - 1)) {
492 tlen = p - c + 1;
493 lp = xmalloc(sizeof(*lp));
494 lp->l_line = c;
495 lp->l_len = tlen;
496 lp->l_lineno = ++(lines->l_nblines);
497 TAILQ_INSERT_TAIL(&(lines->l_lines), lp, l_list)do { (lp)->l_list.tqe_next = ((void *)0); (lp)->l_list.
tqe_prev = (&(lines->l_lines))->tqh_last; *(&(lines
->l_lines))->tqh_last = (lp); (&(lines->l_lines)
)->tqh_last = &(lp)->l_list.tqe_next; } while (0)
;
498 c = p + 1;
499 }
500 p++;
501 }
502
503 return (lines);
504}
505
506void
507rcs_freelines(struct rcs_lines *lines)
508{
509 struct rcs_line *lp;
510
511 while ((lp = TAILQ_FIRST(&(lines->l_lines))((&(lines->l_lines))->tqh_first)) != NULL((void *)0)) {
512 TAILQ_REMOVE(&(lines->l_lines), lp, l_list)do { if (((lp)->l_list.tqe_next) != ((void *)0)) (lp)->
l_list.tqe_next->l_list.tqe_prev = (lp)->l_list.tqe_prev
; else (&(lines->l_lines))->tqh_last = (lp)->l_list
.tqe_prev; *(lp)->l_list.tqe_prev = (lp)->l_list.tqe_next
; ; ; } while (0)
;
513 free(lp);
514 }
515
516 free(lines);
517}
518
519BUF *
520rcs_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 dlines = rcs_splitlines(data, dlen);
528 plines = rcs_splitlines(patch, plen);
529
530 if (p(dlines, plines) < 0) {
531 rcs_freelines(dlines);
532 rcs_freelines(plines);
533 return (NULL((void *)0));
534 }
535
536 res = buf_alloc(1024);
537 TAILQ_FOREACH(lp, &dlines->l_lines, l_list)for((lp) = ((&dlines->l_lines)->tqh_first); (lp) !=
((void *)0); (lp) = ((lp)->l_list.tqe_next))
{
538 if (lp->l_line == NULL((void *)0))
539 continue;
540 buf_append(res, lp->l_line, lp->l_len);
541 }
542
543 rcs_freelines(dlines);
544 rcs_freelines(plines);
545 return (res);
546}
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 */
555int
556rcs_yesno(int defc)
557{
558 int c, ret;
559
560 fflush(stderr(&__sF[2]));
561 fflush(stdout(&__sF[1]));
562
563 clearerr(stdin)(!__isthreaded ? ((void)(((&__sF[0]))->_flags &= ~
(0x0040|0x0020))) : (clearerr)((&__sF[0])))
;
564 if (isalpha(c = getchar()(!__isthreaded ? (--((&__sF[0]))->_r < 0 ? __srget(
(&__sF[0])) : (int)(*((&__sF[0]))->_p++)) : (getc)
((&__sF[0])))
))
565 c = tolower(c);
566 if (c == defc || c == '\n' || (c == EOF(-1) && feof(stdin)(!__isthreaded ? ((((&__sF[0]))->_flags & 0x0020) !=
0) : (feof)((&__sF[0])))
))
567 ret = defc;
568 else
569 ret = c;
570
571 while (c != EOF(-1) && c != '\n')
572 c = getchar()(!__isthreaded ? (--((&__sF[0]))->_r < 0 ? __srget(
(&__sF[0])) : (int)(*((&__sF[0]))->_p++)) : (getc)
((&__sF[0])))
;
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 */
583struct rcs_argvector *
584rcs_strsplit(const char *str, const char *sep)
585{
586 struct rcs_argvector *av;
587 size_t i = 0;
588 char *cp, *p;
589
590 cp = xstrdup(str);
591 av = xmalloc(sizeof(*av));
592 av->str = cp;
593 av->argv = xmalloc(sizeof(*(av->argv)));
594
595 while ((p = strsep(&cp, sep)) != NULL((void *)0)) {
596 av->argv[i++] = p;
597 av->argv = xreallocarray(av->argv,
598 i + 1, sizeof(*(av->argv)));
599 }
600 av->argv[i] = NULL((void *)0);
601
602 return (av);
603}
604
605/*
606 * rcs_argv_destroy()
607 *
608 * Free an argument vector previously allocated by rcs_strsplit().
609 */
610void
611rcs_argv_destroy(struct rcs_argvector *av)
612{
613 free(av->str);
614 free(av->argv);
615 free(av);
616}
617
618/*
619 * Strip suffix from filename.
620 */
621void
622rcs_strip_suffix(char *filename)
623{
624 char *p, *suffixes, *next, *ext;
625
626 if ((p = strrchr(filename, ',')) != NULL((void *)0)) {
627 suffixes = xstrdup(rcs_suffixes);
628 for (next = suffixes; (ext = strsep(&next, "/")) != NULL((void *)0);) {
629 if (!strcmp(p, ext)) {
630 *p = '\0';
631 break;
632 }
633 }
634 free(suffixes);
635 }
636}