Bug Summary

File:src/gnu/usr.bin/cvs/src/history.c
Warning:line 1538, column 31
Null pointer passed as 1st argument to string length 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 history.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/gnu/usr.bin/cvs/obj/src -resource-dir /usr/local/lib/clang/13.0.0 -D HAVE_CONFIG_H -I . -I /usr/src/gnu/usr.bin/cvs/src -I .. -I . -I /usr/src/gnu/usr.bin/cvs/lib -I /usr/src/gnu/usr.bin/cvs/diff -internal-isystem /usr/local/lib/clang/13.0.0/include -internal-externc-isystem /usr/include -O2 -fdebug-compilation-dir=/usr/src/gnu/usr.bin/cvs/obj/src -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/gnu/usr.bin/cvs/src/history.c
1/*
2 *
3 * You may distribute under the terms of the GNU General Public License
4 * as specified in the README file that comes with the CVS 1.0 kit.
5 *
6 * **************** History of Users and Module ****************
7 *
8 * LOGGING: Append record to "${CVSROOT}/CVSROOTADM/CVSROOTADM_HISTORY".
9 *
10 * On For each Tag, Add, Checkout, Commit, Update or Release command,
11 * one line of text is written to a History log.
12 *
13 * X date | user | CurDir | special | rev(s) | argument '\n'
14 *
15 * where: [The spaces in the example line above are not in the history file.]
16 *
17 * X is a single character showing the type of event:
18 * T "Tag" cmd.
19 * O "Checkout" cmd.
20 * E "Export" cmd.
21 * F "Release" cmd.
22 * W "Update" cmd - No User file, Remove from Entries file.
23 * U "Update" cmd - File was checked out over User file.
24 * G "Update" cmd - File was merged successfully.
25 * C "Update" cmd - File was merged and shows overlaps.
26 * M "Commit" cmd - "Modified" file.
27 * A "Commit" cmd - "Added" file.
28 * R "Commit" cmd - "Removed" file.
29 *
30 * date is a fixed length 8-char hex representation of a Unix time_t.
31 * [Starting here, variable fields are delimited by '|' chars.]
32 *
33 * user is the username of the person who typed the command.
34 *
35 * CurDir The directory where the action occurred. This should be the
36 * absolute path of the directory which is at the same level as
37 * the "Repository" field (for W,U,G,C & M,A,R).
38 *
39 * Repository For record types [W,U,G,C,M,A,R] this field holds the
40 * repository read from the administrative data where the
41 * command was typed.
42 * T "A" --> New Tag, "D" --> Delete Tag
43 * Otherwise it is the Tag or Date to modify.
44 * O,F,E A "" (null field)
45 *
46 * rev(s) Revision number or tag.
47 * T The Tag to apply.
48 * O,E The Tag or Date, if specified, else "" (null field).
49 * F "" (null field)
50 * W The Tag or Date, if specified, else "" (null field).
51 * U The Revision checked out over the User file.
52 * G,C The Revision(s) involved in merge.
53 * M,A,R RCS Revision affected.
54 *
55 * argument The module (for [TOEUF]) or file (for [WUGCMAR]) affected.
56 *
57 *
58 *** Report categories: "User" and "Since" modifiers apply to all reports.
59 * [For "sort" ordering see the "sort_order" routine.]
60 *
61 * Extract list of record types
62 *
63 * -e, -x [TOEFWUGCMAR]
64 *
65 * Extracted records are simply printed, No analysis is performed.
66 * All "field" modifiers apply. -e chooses all types.
67 *
68 * Checked 'O'ut modules
69 *
70 * -o, -w
71 * Checked out modules. 'F' and 'O' records are examined and if
72 * the last record for a repository/file is an 'O', a line is
73 * printed. "-w" forces the "working dir" to be used in the
74 * comparison instead of the repository.
75 *
76 * Committed (Modified) files
77 *
78 * -c, -l, -w
79 * All 'M'odified, 'A'dded and 'R'emoved records are examined.
80 * "Field" modifiers apply. -l forces a sort by file within user
81 * and shows only the last modifier. -w works as in Checkout.
82 *
83 * Warning: Be careful with what you infer from the output of
84 * "cvs hi -c -l". It means the last time *you*
85 * changed the file, not the list of files for which
86 * you were the last changer!!!
87 *
88 * Module history for named modules.
89 * -m module, -l
90 *
91 * This is special. If one or more modules are specified, the
92 * module names are remembered and the files making up the
93 * modules are remembered. Only records matching exactly those
94 * files and repositories are shown. Sorting by "module", then
95 * filename, is implied. If -l ("last modified") is specified,
96 * then "update" records (types WUCG), tag and release records
97 * are ignored and the last (by date) "modified" record.
98 *
99 * TAG history
100 *
101 * -T All Tag records are displayed.
102 *
103 *** Modifiers.
104 *
105 * Since ... [All records contain a timestamp, so any report
106 * category can be limited by date.]
107 *
108 * -D date - The "date" is parsed into a Unix "time_t" and
109 * records with an earlier time stamp are ignored.
110 * -r rev/tag - A "rev" begins with a digit. A "tag" does not. If
111 * you use this option, every file is searched for the
112 * indicated rev/tag.
113 * -t tag - The "tag" is searched for in the history file and no
114 * record is displayed before the tag is found. An
115 * error is printed if the tag is never found.
116 * -b string - Records are printed only back to the last reference
117 * to the string in the "module", "file" or
118 * "repository" fields.
119 *
120 * Field Selections [Simple comparisons on existing fields. All field
121 * selections are repeatable.]
122 *
123 * -a - All users.
124 * -u user - If no user is given and '-a' is not given, only
125 * records for the user typing the command are shown.
126 * ==> If -a or -u is not specified, just use "self".
127 *
128 * -f filematch - Only records in which the "file" field contains the
129 * string "filematch" are considered.
130 *
131 * -p repository - Only records in which the "repository" string is a
132 * prefix of the "repos" field are considered.
133 *
134 * -n modulename - Only records which contain "modulename" in the
135 * "module" field are considered.
136 *
137 *
138 * EXAMPLES: ("cvs history", "cvs his" or "cvs hi")
139 *
140 *** Checked out files for username. (default self, e.g. "dgg")
141 * cvs hi [equivalent to: "cvs hi -o -u dgg"]
142 * cvs hi -u user [equivalent to: "cvs hi -o -u user"]
143 * cvs hi -o [equivalent to: "cvs hi -o -u dgg"]
144 *
145 *** Committed (modified) files from the beginning of the file.
146 * cvs hi -c [-u user]
147 *
148 *** Committed (modified) files since Midnight, January 1, 1990:
149 * cvs hi -c -D 'Jan 1 1990' [-u user]
150 *
151 *** Committed (modified) files since tag "TAG" was stored in the history file:
152 * cvs hi -c -t TAG [-u user]
153 *
154 *** Committed (modified) files since tag "TAG" was placed on the files:
155 * cvs hi -c -r TAG [-u user]
156 *
157 *** Who last committed file/repository X?
158 * cvs hi -c -l -[fp] X
159 *
160 *** Modified files since tag/date/file/repos?
161 * cvs hi -c {-r TAG | -D Date | -b string}
162 *
163 *** Tag history
164 * cvs hi -T
165 *
166 *** History of file/repository/module X.
167 * cvs hi -[fpn] X
168 *
169 *** History of user "user".
170 * cvs hi -e -u user
171 *
172 *** Dump (eXtract) specified record types
173 * cvs hi -x [TOFWUGCMAR]
174 *
175 *
176 * FUTURE: J[Join], I[Import] (Not currently implemented.)
177 *
178 */
179
180#include "cvs.h"
181#include "savecwd.h"
182
183static struct hrec
184{
185 char *type; /* Type of record (In history record) */
186 char *user; /* Username (In history record) */
187 char *dir; /* "Compressed" Working dir (In history record) */
188 char *repos; /* (Tag is special.) Repository (In history record) */
189 char *rev; /* Revision affected (In history record) */
190 char *file; /* Filename (In history record) */
191 char *end; /* Ptr into repository to copy at end of workdir */
192 char *mod; /* The module within which the file is contained */
193 time_t date; /* Calculated from date stored in record */
194 long idx; /* Index of record, for "stable" sort. */
195} *hrec_head;
196static long hrec_idx;
197
198
199static void fill_hrec PROTO((char *line, struct hrec * hr))(char *line, struct hrec * hr);
200static int accept_hrec PROTO((struct hrec * hr, struct hrec * lr))(struct hrec * hr, struct hrec * lr);
201static int select_hrec PROTO((struct hrec * hr))(struct hrec * hr);
202static int sort_order PROTO((const PTR l, const PTR r))(const void * l, const void * r);
203static int within PROTO((char *find, char *string))(char *find, char *string);
204static void expand_modules PROTO((void))(void);
205static void read_hrecs PROTO((char *fname))(char *fname);
206static void report_hrecs PROTO((void))(void);
207static void save_file PROTO((char *dir, char *name, char *module))(char *dir, char *name, char *module);
208static void save_module PROTO((char *module))(char *module);
209static void save_user PROTO((char *name))(char *name);
210
211#define ALL_REC_TYPES"TOEFWUCGMAR" "TOEFWUCGMAR"
212#define USER_INCREMENT2 2
213#define FILE_INCREMENT128 128
214#define MODULE_INCREMENT5 5
215#define HREC_INCREMENT128 128
216
217static short report_count;
218
219static short extract;
220static short v_checkout;
221static short modified;
222static short tag_report;
223static short module_report;
224static short working;
225static short last_entry;
226static short all_users;
227
228static short user_sort;
229static short repos_sort;
230static short file_sort;
231static short module_sort;
232
233static short tz_local;
234static time_t tz_seconds_east_of_GMT;
235static char *tz_name = "+0000";
236
237char *logHistory = ALL_REC_TYPES"TOEFWUCGMAR";
238
239/* -r, -t, or -b options, malloc'd. These are "" if the option in
240 question is not specified or is overridden by another option. The
241 main reason for using "" rather than NULL is historical. Together
242 with since_date, these are a mutually exclusive set; one overrides the
243 others. */
244static char *since_rev;
245static char *since_tag;
246static char *backto;
247/* -D option, or 0 if not specified. RCS format. */
248static char * since_date;
249
250static struct hrec *last_since_tag;
251static struct hrec *last_backto;
252
253/* Record types to look for, malloc'd. Probably could be statically
254 allocated, but only if we wanted to check for duplicates more than
255 we do. */
256static char *rec_types;
257
258static int hrec_count;
259static int hrec_max;
260
261static char **user_list; /* Ptr to array of ptrs to user names */
262static int user_max; /* Number of elements allocated */
263static int user_count; /* Number of elements used */
264
265static struct file_list_str
266{
267 char *l_file;
268 char *l_module;
269} *file_list; /* Ptr to array file name structs */
270static int file_max; /* Number of elements allocated */
271static int file_count; /* Number of elements used */
272
273static char **mod_list; /* Ptr to array of ptrs to module names */
274static int mod_max; /* Number of elements allocated */
275static int mod_count; /* Number of elements used */
276
277static char *histfile; /* Ptr to the history file name */
278
279/* This is pretty unclear. First of all, separating "flags" vs.
280 "options" (I think the distinction is that "options" take arguments)
281 is nonstandard, and not something we do elsewhere in CVS. Second of
282 all, what does "reports" mean? I think it means that you can only
283 supply one of those options, but "reports" hardly has that meaning in
284 a self-explanatory way. */
285static const char *const history_usg[] =
286{
287 "Usage: %s %s [-report] [-flags] [-options args] [files...]\n\n",
288 " Reports:\n",
289 " -T Produce report on all TAGs\n",
290 " -c Committed (Modified) files\n",
291 " -o Checked out modules\n",
292 " -m <module> Look for specified module (repeatable)\n",
293 " -x [TOEFWUCGMAR] Extract by record type\n",
294 " -e Everything (same as -x, but all record types)\n",
295 " Flags:\n",
296 " -a All users (Default is self)\n",
297 " -l Last modified (committed or modified report)\n",
298 " -w Working directory must match\n",
299 " Options:\n",
300 " -D <date> Since date (Many formats)\n",
301 " -b <str> Back to record with str in module/file/repos field\n",
302 " -f <file> Specified file (same as command line) (repeatable)\n",
303 " -n <modulename> In module (repeatable)\n",
304 " -p <repos> In repository (repeatable)\n",
305 " -r <rev/tag> Since rev or tag (looks inside RCS files!)\n",
306 " -t <tag> Since tag record placed in history file (by anyone).\n",
307 " -u <user> For user name (repeatable)\n",
308 " -z <tz> Output for time zone <tz> (e.g. -z -0700)\n",
309 NULL((void*)0)};
310
311/* Sort routine for qsort:
312 - If a user is selected at all, sort it first. User-within-file is useless.
313 - If a module was selected explicitly, sort next on module.
314 - Then sort by file. "File" is "repository/file" unless "working" is set,
315 then it is "workdir/file". (Revision order should always track date.)
316 - Always sort timestamp last.
317*/
318static int
319sort_order (l, r)
320 const PTRvoid * l;
321 const PTRvoid * r;
322{
323 int i;
324 const struct hrec *left = (const struct hrec *) l;
325 const struct hrec *right = (const struct hrec *) r;
326
327 if (user_sort) /* If Sort by username, compare users */
328 {
329 if ((i = strcmp (left->user, right->user)) != 0)
330 return (i);
331 }
332 if (module_sort) /* If sort by modules, compare module names */
333 {
334 if (left->mod && right->mod)
335 if ((i = strcmp (left->mod, right->mod)) != 0)
336 return (i);
337 }
338 if (repos_sort) /* If sort by repository, compare them. */
339 {
340 if ((i = strcmp (left->repos, right->repos)) != 0)
341 return (i);
342 }
343 if (file_sort) /* If sort by filename, compare files, NOT dirs. */
344 {
345 if ((i = strcmp (left->file, right->file)) != 0)
346 return (i);
347
348 if (working)
349 {
350 if ((i = strcmp (left->dir, right->dir)) != 0)
351 return (i);
352
353 if ((i = strcmp (left->end, right->end)) != 0)
354 return (i);
355 }
356 }
357
358 /*
359 * By default, sort by date, time
360 * XXX: This fails after 2030 when date slides into sign bit
361 */
362 if ((i = ((long) (left->date) - (long) (right->date))) != 0)
363 return (i);
364
365 /* For matching dates, keep the sort stable by using record index */
366 return (left->idx - right->idx);
367}
368
369int
370history (argc, argv)
371 int argc;
372 char **argv;
373{
374 int i, c;
375 char *fname;
376
377 if (argc == -1)
1
Assuming the condition is false
2
Taking false branch
378 usage (history_usg);
379
380 since_rev = xstrdup ("");
381 since_tag = xstrdup ("");
382 backto = xstrdup ("");
383 rec_types = xstrdup ("");
384 optind = 0;
385 while ((c = getopt (argc, argv, "+Tacelow?D:b:f:m:n:p:r:t:u:x:X:z:")) != -1)
3
Assuming the condition is false
4
Loop condition is false. Execution continues on line 532
386 {
387 switch (c)
388 {
389 case 'T': /* Tag list */
390 report_count++;
391 tag_report++;
392 break;
393 case 'a': /* For all usernames */
394 all_users++;
395 break;
396 case 'c':
397 report_count++;
398 modified = 1;
399 break;
400 case 'e':
401 report_count++;
402 extract++;
403 free (rec_types);
404 rec_types = xstrdup (ALL_REC_TYPES"TOEFWUCGMAR");
405 break;
406 case 'l': /* Find Last file record */
407 last_entry = 1;
408 break;
409 case 'o':
410 report_count++;
411 v_checkout = 1;
412 break;
413 case 'w': /* Match Working Dir (CurDir) fields */
414 working = 1;
415 break;
416 case 'X': /* Undocumented debugging flag */
417#ifdef DEBUG
418 histfile = optarg;
419#endif
420 break;
421
422 case 'D': /* Since specified date */
423 if (*since_rev || *since_tag || *backto)
424 {
425 error (0, 0, "date overriding rev/tag/backto");
426 *since_rev = *since_tag = *backto = '\0';
427 }
428 since_date = Make_Date (optarg);
429 break;
430 case 'b': /* Since specified file/Repos */
431 if (since_date || *since_rev || *since_tag)
432 {
433 error (0, 0, "backto overriding date/rev/tag");
434 *since_rev = *since_tag = '\0';
435 if (since_date != NULL((void*)0))
436 free (since_date);
437 since_date = NULL((void*)0);
438 }
439 free (backto);
440 backto = xstrdup (optarg);
441 break;
442 case 'f': /* For specified file */
443 save_file ("", optarg, (char *) NULL((void*)0));
444 break;
445 case 'm': /* Full module report */
446 if (!module_report++) report_count++;
447 /* fall through */
448 case 'n': /* Look for specified module */
449 save_module (optarg);
450 break;
451 case 'p': /* For specified directory */
452 save_file (optarg, "", (char *) NULL((void*)0));
453 break;
454 case 'r': /* Since specified Tag/Rev */
455 if (since_date || *since_tag || *backto)
456 {
457 error (0, 0, "rev overriding date/tag/backto");
458 *since_tag = *backto = '\0';
459 if (since_date != NULL((void*)0))
460 free (since_date);
461 since_date = NULL((void*)0);
462 }
463 free (since_rev);
464 since_rev = xstrdup (optarg);
465 break;
466 case 't': /* Since specified Tag/Rev */
467 if (since_date || *since_rev || *backto)
468 {
469 error (0, 0, "tag overriding date/marker/file/repos");
470 *since_rev = *backto = '\0';
471 if (since_date != NULL((void*)0))
472 free (since_date);
473 since_date = NULL((void*)0);
474 }
475 free (since_tag);
476 since_tag = xstrdup (optarg);
477 break;
478 case 'u': /* For specified username */
479 save_user (optarg);
480 break;
481 case 'x':
482 report_count++;
483 extract++;
484 {
485 char *cp;
486
487 for (cp = optarg; *cp; cp++)
488 if (!strchr (ALL_REC_TYPES"TOEFWUCGMAR", *cp))
489 error (1, 0, "%c is not a valid report type", *cp);
490 }
491 free (rec_types);
492 rec_types = xstrdup (optarg);
493 break;
494 case 'z':
495 tz_local =
496 (optarg[0] == 'l' || optarg[0] == 'L')
497 && (optarg[1] == 't' || optarg[1] == 'T')
498 && !optarg[2];
499 if (tz_local)
500 tz_name = optarg;
501 else
502 {
503 /*
504 * Convert a known time with the given timezone to time_t.
505 * Use the epoch + 23 hours, so timezones east of GMT work.
506 */
507 static char f[] = "1/1/1970 23:00 %s";
508 char *buf = xmalloc (sizeof (f) - 2 + strlen (optarg));
509 time_t t;
510 sprintf (buf, f, optarg);
511 t = get_date (buf);
512 free (buf);
513 if (t == (time_t) -1)
514 error (0, 0, "%s is not a known time zone", optarg);
515 else
516 {
517 /*
518 * Convert to seconds east of GMT, removing the
519 * 23-hour offset mentioned above.
520 */
521 tz_seconds_east_of_GMT = (time_t)23 * 60 * 60 - t;
522 tz_name = optarg;
523 }
524 }
525 break;
526 case '?':
527 default:
528 usage (history_usg);
529 break;
530 }
531 }
532 argc -= optind;
533 argv += optind;
534 for (i = 0; i < argc; i++)
5
Assuming 'i' is >= 'argc'
6
Loop condition is false. Execution continues on line 539
535 save_file ("", argv[i], (char *) NULL((void*)0));
536
537
538 /* ================ Now analyze the arguments a bit */
539 if (!report_count)
7
Assuming 'report_count' is not equal to 0
8
Taking false branch
540 v_checkout++;
541 else if (report_count > 1)
9
Assuming 'report_count' is <= 1
10
Taking false branch
542 error (1, 0, "Only one report type allowed from: \"-Tcomxe\".");
543
544#ifdef CLIENT_SUPPORT1
545 if (current_parsed_root->isremote)
11
Assuming field 'isremote' is 0
12
Taking false branch
546 {
547 struct file_list_str *f1;
548 char **mod;
549
550 /* We're the client side. Fire up the remote server. */
551 start_server ();
552
553 ign_setup ();
554
555 if (tag_report)
556 send_arg("-T");
557 if (all_users)
558 send_arg("-a");
559 if (modified)
560 send_arg("-c");
561 if (last_entry)
562 send_arg("-l");
563 if (v_checkout)
564 send_arg("-o");
565 if (working)
566 send_arg("-w");
567 if (histfile)
568 send_arg("-X");
569 if (since_date)
570 client_senddate (since_date);
571 if (backto[0] != '\0')
572 option_with_arg ("-b", backto);
573 for (f1 = file_list; f1 < &file_list[file_count]; ++f1)
574 {
575 if (f1->l_file[0] == '*')
576 option_with_arg ("-p", f1->l_file + 1);
577 else
578 option_with_arg ("-f", f1->l_file);
579 }
580 if (module_report)
581 send_arg("-m");
582 for (mod = mod_list; mod < &mod_list[mod_count]; ++mod)
583 option_with_arg ("-n", *mod);
584 if (*since_rev)
585 option_with_arg ("-r", since_rev);
586 if (*since_tag)
587 option_with_arg ("-t", since_tag);
588 for (mod = user_list; mod < &user_list[user_count]; ++mod)
589 option_with_arg ("-u", *mod);
590 if (extract)
591 option_with_arg ("-x", rec_types);
592 option_with_arg ("-z", tz_name);
593
594 send_to_server ("history\012", 0);
595 return get_responses_and_close ();
596 }
597#endif
598
599 if (all_users)
13
Assuming 'all_users' is 0
14
Taking false branch
600 save_user ("");
601
602 if (mod_list)
15
Assuming 'mod_list' is null
16
Taking false branch
603 expand_modules ();
604
605 if (tag_report)
17
Assuming 'tag_report' is 0
18
Taking false branch
606 {
607 if (!strchr (rec_types, 'T'))
608 {
609 rec_types = xrealloc (rec_types, strlen (rec_types) + 5);
610 (void) strcat (rec_types, "T");
611 }
612 }
613 else if (extract)
19
Assuming 'extract' is 0
20
Taking false branch
614 {
615 if (user_list)
616 user_sort++;
617 }
618 else if (modified)
21
Assuming 'modified' is not equal to 0
22
Taking true branch
619 {
620 free (rec_types);
621 rec_types = xstrdup ("MAR");
622 /*
623 * If the user has not specified a date oriented flag ("Since"), sort
624 * by Repository/file before date. Default is "just" date.
625 */
626 if (last_entry
23
Assuming 'last_entry' is 0
627 || (!since_date && !*since_rev && !*since_tag && !*backto))
24
Assuming 'since_date' is non-null
628 {
629 repos_sort++;
630 file_sort++;
631 /*
632 * If we are not looking for last_modified and the user specified
633 * one or more users to look at, sort by user before filename.
634 */
635 if (!last_entry && user_list)
636 user_sort++;
637 }
638 }
639 else if (module_report)
640 {
641 free (rec_types);
642 rec_types = xstrdup (last_entry ? "OMAR" : ALL_REC_TYPES"TOEFWUCGMAR");
643 module_sort++;
644 repos_sort++;
645 file_sort++;
646 working = 0; /* User's workdir doesn't count here */
647 }
648 else
649 /* Must be "checkout" or default */
650 {
651 free (rec_types);
652 rec_types = xstrdup ("OF");
653 /* See comments in "modified" above */
654 if (!last_entry && user_list)
655 user_sort++;
656 if (last_entry
657 || (!since_date && !*since_rev && !*since_tag && !*backto))
658 file_sort++;
659 }
660
661 /* If no users were specified, use self (-a saves a universal ("") user) */
662 if (!user_list)
25
Assuming 'user_list' is non-null
26
Taking false branch
663 save_user (getcaller ());
664
665 /* If we're looking back to a Tag value, must consider "Tag" records */
666 if (*since_tag && !strchr (rec_types, 'T'))
27
Assuming the condition is false
667 {
668 rec_types = xrealloc (rec_types, strlen (rec_types) + 5);
669 (void) strcat (rec_types, "T");
670 }
671
672 if (histfile)
28
Assuming 'histfile' is null
29
Taking false branch
673 fname = xstrdup (histfile);
674 else
675 {
676 fname = xmalloc (strlen (current_parsed_root->directory) + sizeof (CVSROOTADM"CVSROOT")
677 + sizeof (CVSROOTADM_HISTORY"history") + 10);
678 (void) sprintf (fname, "%s/%s/%s", current_parsed_root->directory,
679 CVSROOTADM"CVSROOT", CVSROOTADM_HISTORY"history");
680 }
681
682 read_hrecs (fname);
683 if(hrec_count>0)
30
Assuming 'hrec_count' is > 0
31
Taking true branch
684 {
685 qsort ((PTRvoid *) hrec_head, hrec_count,
686 sizeof (struct hrec), sort_order);
687 }
688 report_hrecs ();
32
Calling 'report_hrecs'
689 free (fname);
690 if (since_date != NULL((void*)0))
691 free (since_date);
692 free (since_rev);
693 free (since_tag);
694 free (backto);
695 free (rec_types);
696
697 return (0);
698}
699
700void
701history_write (type, update_dir, revs, name, repository)
702 int type;
703 char *update_dir;
704 char *revs;
705 char *name;
706 char *repository;
707{
708 char *fname;
709 char *workdir;
710 char *username = getcaller ();
711 int fd;
712 char *line;
713 char *slash = "", *cp, *cp2, *repos;
714 int i;
715 static char *tilde = "";
716 static char *PrCurDir = NULL((void*)0);
717
718 if (logoff) /* History is turned off by cmd line switch */
719 return;
720 if ( strchr(logHistory, type) == NULL((void*)0) )
721 return;
722 fname = xmalloc (strlen (current_parsed_root->directory) + sizeof (CVSROOTADM"CVSROOT")
723 + sizeof (CVSROOTADM_HISTORY"history") + 3);
724 (void) sprintf (fname, "%s/%s/%s", current_parsed_root->directory,
725 CVSROOTADM"CVSROOT", CVSROOTADM_HISTORY"history");
726
727 /* turn off history logging if the history file does not exist */
728 if (!isfile (fname))
729 {
730 logoff = 1;
731 goto out;
732 }
733
734 if (trace)
735 fprintf (stderr(&__sF[2]), "%s-> fopen(%s,a)\n",
736 CLIENT_SERVER_STR((server_active) ? "S" : " "), fname);
737 if (noexec)
738 goto out;
739 fd = CVS_OPENopen (fname, O_WRONLY0x0001 | O_APPEND0x0008 | O_CREAT0x0200 | OPEN_BINARY(0), 0666);
740 if (fd < 0)
741 {
742 if (! really_quiet)
743 {
744 error (0, errno(*__errno()), "warning: cannot write to history file %s",
745 fname);
746 }
747 goto out;
748 }
749
750 repos = Short_Repository (repository);
751
752 if (!PrCurDir)
753 {
754 char *pwdir;
755
756 pwdir = get_homedir ();
757 PrCurDir = CurDir;
758 if (pwdir != NULL((void*)0))
759 {
760 /* Assumes neither CurDir nor pwdir ends in '/' */
761 i = strlen (pwdir);
762 if (!strncmp (CurDir, pwdir, i))
763 {
764 PrCurDir += i; /* Point to '/' separator */
765 tilde = "~";
766 }
767 else
768 {
769 /* Try harder to find a "homedir" */
770 struct saved_cwd cwd;
771 char *homedir;
772
773 if (save_cwd (&cwd))
774 error_exit ();
775
776 if ( CVS_CHDIRchdir (pwdir) < 0)
777 error (1, errno(*__errno()), "can't chdir(%s)", pwdir);
778 homedir = xgetwd ();
779 if (homedir == NULL((void*)0))
780 error (1, errno(*__errno()), "can't getwd in %s", pwdir);
781
782 if (restore_cwd (&cwd, NULL((void*)0)))
783 error_exit ();
784 free_cwd (&cwd);
785
786 i = strlen (homedir);
787 if (!strncmp (CurDir, homedir, i))
788 {
789 PrCurDir += i; /* Point to '/' separator */
790 tilde = "~";
791 }
792 free (homedir);
793 }
794 }
795 }
796
797 if (type == 'T')
798 {
799 repos = update_dir;
800 update_dir = "";
801 }
802 else if (update_dir && *update_dir)
803 slash = "/";
804 else
805 update_dir = "";
806
807 workdir = xmalloc (strlen (tilde) + strlen (PrCurDir) + strlen (slash)
808 + strlen (update_dir) + 10);
809 (void) sprintf (workdir, "%s%s%s%s", tilde, PrCurDir, slash, update_dir);
810
811 /*
812 * "workdir" is the directory where the file "name" is. ("^~" == $HOME)
813 * "repos" is the Repository, relative to $CVSROOT where the RCS file is.
814 *
815 * "$workdir/$name" is the working file name.
816 * "$CVSROOT/$repos/$name,v" is the RCS file in the Repository.
817 *
818 * First, note that the history format was intended to save space, not
819 * to be human readable.
820 *
821 * The working file directory ("workdir") and the Repository ("repos")
822 * usually end with the same one or more directory elements. To avoid
823 * duplication (and save space), the "workdir" field ends with
824 * an integer offset into the "repos" field. This offset indicates the
825 * beginning of the "tail" of "repos", after which all characters are
826 * duplicates.
827 *
828 * In other words, if the "workdir" field has a '*' (a very stupid thing
829 * to put in a filename) in it, then every thing following the last '*'
830 * is a hex offset into "repos" of the first character from "repos" to
831 * append to "workdir" to finish the pathname.
832 *
833 * It might be easier to look at an example:
834 *
835 * M273b3463|dgg|~/work*9|usr/local/cvs/examples|1.2|loginfo
836 *
837 * Indicates that the workdir is really "~/work/cvs/examples", saving
838 * 10 characters, where "~/work*d" would save 6 characters and mean that
839 * the workdir is really "~/work/examples". It will mean more on
840 * directories like: usr/local/gnu/emacs/dist-19.17/lisp/term
841 *
842 * "workdir" is always an absolute pathname (~/xxx is an absolute path)
843 * "repos" is always a relative pathname. So we can assume that we will
844 * never run into the top of "workdir" -- there will always be a '/' or
845 * a '~' at the head of "workdir" that is not matched by anything in
846 * "repos". On the other hand, we *can* run off the top of "repos".
847 *
848 * Only "compress" if we save characters.
849 */
850
851 if (!repos)
852 repos = "";
853
854 cp = workdir + strlen (workdir) - 1;
855 cp2 = repos + strlen (repos) - 1;
856 for (i = 0; cp2 >= repos && cp > workdir && *cp == *cp2--; cp--)
857 i++;
858
859 if (i > 2)
860 {
861 i = strlen (repos) - i;
862 (void) sprintf ((cp + 1), "*%x", i);
863 }
864
865 if (!revs)
866 revs = "";
867 line = xmalloc (strlen (username) + strlen (workdir) + strlen (repos)
868 + strlen (revs) + strlen (name) + 100);
869 sprintf (line, "%c%08llx|%s|%s|%s|%s|%s\n",
870 type, (long long) time ((time_t *) NULL((void*)0)),
871 username, workdir, repos, revs, name);
872
873 /* Lessen some race conditions on non-Posix-compliant hosts. */
874 if (lseek (fd, (off_t) 0, SEEK_END2) == -1)
875 error (1, errno(*__errno()), "cannot seek to end of history file: %s", fname);
876
877 if (write (fd, line, strlen (line)) < 0)
878 error (1, errno(*__errno()), "cannot write to history file: %s", fname);
879 free (line);
880 if (close (fd) != 0)
881 error (1, errno(*__errno()), "cannot close history file: %s", fname);
882 free (workdir);
883 out:
884 free (fname);
885}
886
887/*
888 * save_user() adds a user name to the user list to select. Zero-length
889 * username ("") matches any user.
890 */
891static void
892save_user (name)
893 char *name;
894{
895 if (user_count == user_max)
896 {
897 user_max = xsum (user_max, USER_INCREMENT2);
898 if (size_overflow_p (xtimes (user_max, sizeof (char *)))((((user_max) <= 0xffffffffffffffffUL / (sizeof (char *)) ?
(size_t) (user_max) * (sizeof (char *)) : 0xffffffffffffffffUL
)) == 0xffffffffffffffffUL)
)
899 {
900 error (0, 0, "save_user: too many users");
901 return;
902 }
903 user_list = xrealloc (user_list, xtimes (user_max, sizeof (char *))((user_max) <= 0xffffffffffffffffUL / (sizeof (char *)) ? (
size_t) (user_max) * (sizeof (char *)) : 0xffffffffffffffffUL
)
);
904 }
905 user_list[user_count++] = xstrdup (name);
906}
907
908/*
909 * save_file() adds file name and associated module to the file list to select.
910 *
911 * If "dir" is null, store a file name as is.
912 * If "name" is null, store a directory name with a '*' on the front.
913 * Else, store concatenated "dir/name".
914 *
915 * Later, in the "select" stage:
916 * - if it starts with '*', it is prefix-matched against the repository.
917 * - if it has a '/' in it, it is matched against the repository/file.
918 * - else it is matched against the file name.
919 */
920static void
921save_file (dir, name, module)
922 char *dir;
923 char *name;
924 char *module;
925{
926 char *cp;
927 struct file_list_str *fl;
928
929 if (file_count == file_max)
930 {
931 file_max = xsum (file_max, FILE_INCREMENT128);
932 if (size_overflow_p (xtimes (file_max, sizeof (*fl)))((((file_max) <= 0xffffffffffffffffUL / (sizeof (*fl)) ? (
size_t) (file_max) * (sizeof (*fl)) : 0xffffffffffffffffUL)) ==
0xffffffffffffffffUL)
)
933 {
934 error (0, 0, "save_file: too many files");
935 return;
936 }
937 file_list = xrealloc (file_list, xtimes (file_max, sizeof (*fl))((file_max) <= 0xffffffffffffffffUL / (sizeof (*fl)) ? (size_t
) (file_max) * (sizeof (*fl)) : 0xffffffffffffffffUL)
);
938 }
939 fl = &file_list[file_count++];
940 fl->l_file = cp = xmalloc (strlen (dir) + strlen (name) + 2);
941 fl->l_module = module;
942
943 if (dir && *dir)
944 {
945 if (name && *name)
946 {
947 (void) strcpy (cp, dir);
948 (void) strcat (cp, "/");
949 (void) strcat (cp, name);
950 }
951 else
952 {
953 *cp++ = '*';
954 (void) strcpy (cp, dir);
955 }
956 }
957 else
958 {
959 if (name && *name)
960 {
961 (void) strcpy (cp, name);
962 }
963 else
964 {
965 error (0, 0, "save_file: null dir and file name");
966 }
967 }
968}
969
970static void
971save_module (module)
972 char *module;
973{
974 if (mod_count == mod_max)
975 {
976 mod_max = xsum (mod_max, MODULE_INCREMENT5);
977 if (size_overflow_p (xtimes (mod_max, sizeof (char *)))((((mod_max) <= 0xffffffffffffffffUL / (sizeof (char *)) ?
(size_t) (mod_max) * (sizeof (char *)) : 0xffffffffffffffffUL
)) == 0xffffffffffffffffUL)
)
978 {
979 error (0, 0, "save_module: too many modules");
980 return;
981 }
982 mod_list = xrealloc (mod_list, xtimes (mod_max, sizeof (char *))((mod_max) <= 0xffffffffffffffffUL / (sizeof (char *)) ? (
size_t) (mod_max) * (sizeof (char *)) : 0xffffffffffffffffUL)
);
983 }
984 mod_list[mod_count++] = xstrdup (module);
985}
986
987static void
988expand_modules ()
989{
990}
991
992/* fill_hrec
993 *
994 * Take a ptr to 7-part history line, ending with a newline, for example:
995 *
996 * M273b3463|dgg|~/work*9|usr/local/cvs/examples|1.2|loginfo
997 *
998 * Split it into 7 parts and drop the parts into a "struct hrec".
999 * Return a pointer to the character following the newline.
1000 *
1001 */
1002
1003#define NEXT_BAR(here)do { while (isspace(*line)) line++; hr->here = line; while
((c = *line++) && c != '|') ; if (!c) return; line[-
1] = '\0'; } while (0)
do { \
1004 while (isspace(*line)) line++; \
1005 hr->here = line; \
1006 while ((c = *line++) && c != '|') ; \
1007 if (!c) return; line[-1] = '\0'; \
1008 } while (0)
1009
1010static void
1011fill_hrec (line, hr)
1012 char *line;
1013 struct hrec *hr;
1014{
1015 char *cp;
1016 int c;
1017
1018 hr->type = hr->user = hr->dir = hr->repos = hr->rev = hr->file =
1019 hr->end = hr->mod = NULL((void*)0);
1020 hr->date = -1;
1021 hr->idx = ++hrec_idx;
1022
1023 while (isspace ((unsigned char) *line))
1024 line++;
1025
1026 hr->type = line++;
1027 hr->date = strtoul (line, &cp, 16);
1028 if (cp == line || *cp != '|')
1029 return;
1030 line = cp + 1;
1031 NEXT_BAR (user)do { while (isspace(*line)) line++; hr->user = line; while
((c = *line++) && c != '|') ; if (!c) return; line[-
1] = '\0'; } while (0)
;
1032 NEXT_BAR (dir)do { while (isspace(*line)) line++; hr->dir = line; while (
(c = *line++) && c != '|') ; if (!c) return; line[-1]
= '\0'; } while (0)
;
1033 if ((cp = strrchr (hr->dir, '*')) != NULL((void*)0))
1034 {
1035 *cp++ = '\0';
1036 hr->end = line + strtoul (cp, NULL((void*)0), 16);
1037 }
1038 else
1039 hr->end = line - 1; /* A handy pointer to '\0' */
1040 NEXT_BAR (repos)do { while (isspace(*line)) line++; hr->repos = line; while
((c = *line++) && c != '|') ; if (!c) return; line[-
1] = '\0'; } while (0)
;
1041 NEXT_BAR (rev)do { while (isspace(*line)) line++; hr->rev = line; while (
(c = *line++) && c != '|') ; if (!c) return; line[-1]
= '\0'; } while (0)
;
1042 if (strchr ("FOET", *(hr->type)))
1043 hr->mod = line;
1044
1045 NEXT_BAR (file)do { while (isspace(*line)) line++; hr->file = line; while
((c = *line++) && c != '|') ; if (!c) return; line[-
1] = '\0'; } while (0)
;
1046}
1047
1048
1049#ifndef STAT_BLOCKSIZE
1050#if HAVE_ST_BLKSIZE1
1051#define STAT_BLOCKSIZE(s)(s).st_blksize (s).st_blksize
1052#else
1053#define STAT_BLOCKSIZE(s)(s).st_blksize (4 * 1024)
1054#endif
1055#endif
1056
1057
1058/* read_hrecs's job is to read the history file and fill in all the "hrec"
1059 * (history record) array elements with the ones we need to print.
1060 *
1061 * Logic:
1062 * - Read a block from the file.
1063 * - Walk through the block parsing line into hr records.
1064 * - if the hr isn't used, free its strings, if it is, bump the hrec counter
1065 * - at the end of a block, copy the end of the current block to the start
1066 * of space for the next block, then read in the next block. If we get less
1067 * than the whole block, we're done.
1068 */
1069static void
1070read_hrecs (fname)
1071 char *fname;
1072{
1073 unsigned char *cpstart, *cpend, *cp, *nl;
1074 char *hrline;
1075 int i;
1076 int fd;
1077 struct stat st_buf;
1078
1079 if ((fd = CVS_OPENopen (fname, O_RDONLY0x0000 | OPEN_BINARY(0))) < 0)
1080 error (1, errno(*__errno()), "cannot open history file: %s", fname);
1081
1082 if (fstat (fd, &st_buf) < 0)
1083 error (1, errno(*__errno()), "can't stat history file");
1084
1085 if (!(st_buf.st_size))
1086 error (1, 0, "history file is empty");
1087
1088 cpstart = xmalloc (2 * STAT_BLOCKSIZE(st_buf)(st_buf).st_blksize);
1089 cpstart[0] = '\0';
1090 cp = cpend = cpstart;
1091
1092 hrec_max = HREC_INCREMENT128;
1093 hrec_head = xmalloc (hrec_max * sizeof (struct hrec));
1094 hrec_idx = 0;
1095
1096 for (;;)
1097 {
1098 for (nl = cp; nl < cpend && *nl != '\n'; nl++)
1099 if (!isprint(*nl)) *nl = ' ';
1100
1101 if (nl >= cpend)
1102 {
1103 if (nl - cp >= STAT_BLOCKSIZE(st_buf)(st_buf).st_blksize)
1104 {
1105 error(1, 0, "history line %ld too long (> %lu)", hrec_idx + 1,
1106 (unsigned long) STAT_BLOCKSIZE(st_buf)(st_buf).st_blksize);
1107 }
1108 if (nl > cp)
1109 memmove (cpstart, cp, nl - cp);
1110 nl = cpstart + (nl - cp);
1111 cp = cpstart;
1112 i = read (fd, nl, STAT_BLOCKSIZE(st_buf)(st_buf).st_blksize);
1113 if (i > 0)
1114 {
1115 cpend = nl + i;
1116 *cpend = '\0';
1117 continue;
1118 }
1119 if (i < 0)
1120 error (1, errno(*__errno()), "error reading history file");
1121 if (nl == cp) break;
1122 error (0, 0, "warning: no newline at end of history file");
1123 }
1124 *nl = '\0';
1125
1126 if (hrec_count == hrec_max)
1127 {
1128 struct hrec *old_head = hrec_head;
1129
1130 hrec_max += HREC_INCREMENT128;
1131 hrec_head = xrealloc ((char *) hrec_head,
1132 hrec_max * sizeof (struct hrec));
1133 if (last_since_tag)
1134 last_since_tag = hrec_head + (last_since_tag - old_head);
1135 if (last_backto)
1136 last_backto = hrec_head + (last_backto - old_head);
1137 }
1138
1139 /* fill_hrec dates from when history read the entire
1140 history file in one chunk, and then records were pulled out
1141 by pointing to the various parts of this big chunk. This is
1142 why there are ugly hacks here: I don't want to completely
1143 re-write the whole history stuff right now. */
1144
1145 hrline = xstrdup ((char *)cp);
1146 fill_hrec (hrline, &hrec_head[hrec_count]);
1147 if (select_hrec (&hrec_head[hrec_count]))
1148 hrec_count++;
1149 else
1150 free(hrline);
1151
1152 cp = nl + 1;
1153 }
1154 free (cpstart);
1155 close (fd);
1156
1157 /* Special selection problem: If "since_tag" is set, we have saved every
1158 * record from the 1st occurrence of "since_tag", when we want to save
1159 * records since the *last* occurrence of "since_tag". So what we have
1160 * to do is bump hrec_head forward and reduce hrec_count accordingly.
1161 */
1162 if (last_since_tag)
1163 {
1164 hrec_count -= (last_since_tag - hrec_head);
1165 hrec_head = last_since_tag;
1166 }
1167
1168 /* Much the same thing is necessary for the "backto" option. */
1169 if (last_backto)
1170 {
1171 hrec_count -= (last_backto - hrec_head);
1172 hrec_head = last_backto;
1173 }
1174}
1175
1176/* Utility program for determining whether "find" is inside "string" */
1177static int
1178within (find, string)
1179 char *find, *string;
1180{
1181 int c, len;
1182
1183 if (!find || !string)
1184 return (0);
1185
1186 c = *find++;
1187 len = strlen (find);
1188
1189 while (*string)
1190 {
1191 if (!(string = strchr (string, c)))
1192 return (0);
1193 string++;
1194 if (!strncmp (find, string, len))
1195 return (1);
1196 }
1197 return (0);
1198}
1199
1200/* The purpose of "select_hrec" is to apply the selection criteria based on
1201 * the command arguments and defaults and return a flag indicating whether
1202 * this record should be remembered for printing.
1203 */
1204static int
1205select_hrec (hr)
1206 struct hrec *hr;
1207{
1208 char **cpp, *cp, *cp2;
1209 struct file_list_str *fl;
1210 int count;
1211
1212 /* basic validity checking */
1213 if (!hr->type || !hr->user || !hr->dir || !hr->repos || !hr->rev ||
1214 !hr->file || !hr->end)
1215 {
1216 error (0, 0, "warning: history line %ld invalid", hr->idx);
1217 return (0);
1218 }
1219
1220 /* "Since" checking: The argument parser guarantees that only one of the
1221 * following four choices is set:
1222 *
1223 * 1. If "since_date" is set, it contains the date specified on the
1224 * command line. hr->date fields earlier than "since_date" are ignored.
1225 * 2. If "since_rev" is set, it contains either an RCS "dotted" revision
1226 * number (which is of limited use) or a symbolic TAG. Each RCS file
1227 * is examined and the date on the specified revision (or the revision
1228 * corresponding to the TAG) in the RCS file (CVSROOT/repos/file) is
1229 * compared against hr->date as in 1. above.
1230 * 3. If "since_tag" is set, matching tag records are saved. The field
1231 * "last_since_tag" is set to the last one of these. Since we don't
1232 * know where the last one will be, all records are saved from the
1233 * first occurrence of the TAG. Later, at the end of "select_hrec"
1234 * records before the last occurrence of "since_tag" are skipped.
1235 * 4. If "backto" is set, all records with a module name or file name
1236 * matching "backto" are saved. In addition, all records with a
1237 * repository field with a *prefix* matching "backto" are saved.
1238 * The field "last_backto" is set to the last one of these. As in
1239 * 3. above, "select_hrec" adjusts to include the last one later on.
1240 */
1241 if (since_date)
1242 {
1243 char *ourdate = date_from_time_t (hr->date);
1244 count = RCS_datecmp (ourdate, since_date);
1245 free (ourdate);
1246 if (count < 0)
1247 return (0);
1248 }
1249 else if (*since_rev)
1250 {
1251 Vers_TS *vers;
1252 time_t t;
1253 struct file_info finfo;
1254
1255 memset (&finfo, 0, sizeof finfo);
1256 finfo.file = hr->file;
1257 /* Not used, so don't worry about it. */
1258 finfo.update_dir = NULL((void*)0);
1259 finfo.fullname = finfo.file;
1260 finfo.repository = hr->repos;
1261 finfo.entries = NULL((void*)0);
1262 finfo.rcs = NULL((void*)0);
1263
1264 vers = Version_TS (&finfo, (char *) NULL((void*)0), since_rev, (char *) NULL((void*)0),
1265 1, 0);
1266 if (vers->vn_rcs)
1267 {
1268 if ((t = RCS_getrevtime (vers->srcfile, vers->vn_rcs, (char *) 0, 0))
1269 != (time_t) 0)
1270 {
1271 if (hr->date < t)
1272 {
1273 freevers_ts (&vers);
1274 return (0);
1275 }
1276 }
1277 }
1278 freevers_ts (&vers);
1279 }
1280 else if (*since_tag)
1281 {
1282 if (*(hr->type) == 'T')
1283 {
1284 /*
1285 * A 'T'ag record, the "rev" field holds the tag to be set,
1286 * while the "repos" field holds "D"elete, "A"dd or a rev.
1287 */
1288 if (within (since_tag, hr->rev))
1289 {
1290 last_since_tag = hr;
1291 return (1);
1292 }
1293 else
1294 return (0);
1295 }
1296 if (!last_since_tag)
1297 return (0);
1298 }
1299 else if (*backto)
1300 {
1301 if (within (backto, hr->file) || within (backto, hr->mod) ||
1302 within (backto, hr->repos))
1303 last_backto = hr;
1304 else
1305 return (0);
1306 }
1307
1308 /* User checking:
1309 *
1310 * Run down "user_list", match username ("" matches anything)
1311 * If "" is not there and actual username is not there, return failure.
1312 */
1313 if (user_list && hr->user)
1314 {
1315 for (cpp = user_list, count = user_count; count; cpp++, count--)
1316 {
1317 if (!**cpp)
1318 break; /* null user == accept */
1319 if (!strcmp (hr->user, *cpp)) /* found listed user */
1320 break;
1321 }
1322 if (!count)
1323 return (0); /* Not this user */
1324 }
1325
1326 /* Record type checking:
1327 *
1328 * 1. If Record type is not in rec_types field, skip it.
1329 * 2. If mod_list is null, keep everything. Otherwise keep only modules
1330 * on mod_list.
1331 * 3. If neither a 'T', 'F' nor 'O' record, run through "file_list". If
1332 * file_list is null, keep everything. Otherwise, keep only files on
1333 * file_list, matched appropriately.
1334 */
1335 if (!strchr (rec_types, *(hr->type)))
1336 return (0);
1337 if (!strchr ("TFOE", *(hr->type))) /* Don't bother with "file" if "TFOE" */
1338 {
1339 if (file_list) /* If file_list is null, accept all */
1340 {
1341 for (fl = file_list, count = file_count; count; fl++, count--)
1342 {
1343 /* 1. If file_list entry starts with '*', skip the '*' and
1344 * compare it against the repository in the hrec.
1345 * 2. If file_list entry has a '/' in it, compare it against
1346 * the concatenation of the repository and file from hrec.
1347 * 3. Else compare the file_list entry against the hrec file.
1348 */
1349 char *cmpfile = NULL((void*)0);
1350
1351 if (*(cp = fl->l_file) == '*')
1352 {
1353 cp++;
1354 /* if argument to -p is a prefix of repository */
1355 if (!strncmp (cp, hr->repos, strlen (cp)))
1356 {
1357 hr->mod = fl->l_module;
1358 break;
1359 }
1360 }
1361 else
1362 {
1363 if (strchr (cp, '/'))
1364 {
1365 cmpfile = xmalloc (strlen (hr->repos)
1366 + strlen (hr->file)
1367 + 10);
1368 (void) sprintf (cmpfile, "%s/%s",
1369 hr->repos, hr->file);
1370 cp2 = cmpfile;
1371 }
1372 else
1373 {
1374 cp2 = hr->file;
1375 }
1376
1377 /* if requested file is found within {repos}/file fields */
1378 if (within (cp, cp2))
1379 {
1380 hr->mod = fl->l_module;
1381 break;
1382 }
1383 if (cmpfile != NULL((void*)0))
1384 free (cmpfile);
1385 }
1386 }
1387 if (!count)
1388 return (0); /* String specified and no match */
1389 }
1390 }
1391 if (mod_list)
1392 {
1393 for (cpp = mod_list, count = mod_count; count; cpp++, count--)
1394 {
1395 if (hr->mod && !strcmp (hr->mod, *cpp)) /* found module */
1396 break;
1397 }
1398 if (!count)
1399 return (0); /* Module specified & this record is not one of them. */
1400 }
1401
1402 return (1); /* Select this record unless rejected above. */
1403}
1404
1405/* The "sort_order" routine (when handed to qsort) has arranged for the
1406 * hrecs files to be in the right order for the report.
1407 *
1408 * Most of the "selections" are done in the select_hrec routine, but some
1409 * selections are more easily done after the qsort by "accept_hrec".
1410 */
1411static void
1412report_hrecs ()
1413{
1414 struct hrec *hr, *lr;
1415 struct tm *tm;
1416 int i, count, ty;
1417 char *cp;
1418 int user_len, file_len, rev_len, mod_len, repos_len;
1419
1420 if (*since_tag && !last_since_tag)
33
Assuming the condition is false
1421 {
1422 (void) printf ("No tag found: %s\n", since_tag);
1423 return;
1424 }
1425 else if (*backto && !last_backto)
34
Assuming the condition is false
1426 {
1427 (void) printf ("No module, file or repository with: %s\n", backto);
1428 return;
1429 }
1430 else if (hrec_count
34.1
'hrec_count' is >= 1
< 1)
35
Taking false branch
1431 {
1432 (void) printf ("No records selected.\n");
1433 return;
1434 }
1435
1436 user_len = file_len = rev_len = mod_len = repos_len = 0;
1437
1438 /* Run through lists and find maximum field widths */
1439 hr = lr = hrec_head;
1440 hr++;
1441 for (count = hrec_count; count--; lr = hr, hr++)
36
Loop condition is true. Entering loop body
48
Loop condition is false. Execution continues on line 1481
1442 {
1443 char *repos;
1444
1445 if (!count)
37
Assuming 'count' is not equal to 0
38
Taking false branch
1446 hr = NULL((void*)0);
1447 if (!accept_hrec (lr, hr))
39
Taking false branch
1448 continue;
1449
1450 ty = *(lr->type);
1451 repos = xstrdup (lr->repos);
1452 if ((cp = strrchr (repos, '/')) != NULL((void*)0))
40
Assuming the condition is false
41
Taking false branch
1453 {
1454 if (lr->mod && !strcmp (++cp, lr->mod))
1455 {
1456 (void) strcpy (cp, "*");
1457 }
1458 }
1459 if ((i = strlen (lr->user)) > user_len)
42
Assuming the condition is false
43
Taking false branch
1460 user_len = i;
1461 if ((i = strlen (lr->file)) > file_len)
44
Assuming the condition is false
45
Taking false branch
1462 file_len = i;
1463 if (ty != 'T' && (i = strlen (repos)) > repos_len)
46
Assuming the condition is false
1464 repos_len = i;
1465 if (ty != 'T' && (i = strlen (lr->rev)) > rev_len)
1466 rev_len = i;
1467 if (lr->mod && (i = strlen (lr->mod)) > mod_len)
47
Assuming field 'mod' is null
1468 mod_len = i;
1469 free (repos);
1470 }
1471
1472 /* Walk through hrec array setting "lr" (Last Record) to each element.
1473 * "hr" points to the record following "lr" -- It is NULL in the last
1474 * pass.
1475 *
1476 * There are two sections in the loop below:
1477 * 1. Based on the report type (e.g. extract, checkout, tag, etc.),
1478 * decide whether the record should be printed.
1479 * 2. Based on the record type, format and print the data.
1480 */
1481 for (lr = hrec_head, hr = (lr + 1); hrec_count--; lr = hr, hr++)
49
Loop condition is true. Entering loop body
1482 {
1483 char *workdir;
1484 char *repos;
1485
1486 if (!hrec_count)
50
Assuming 'hrec_count' is not equal to 0
51
Taking false branch
1487 hr = NULL((void*)0);
1488 if (!accept_hrec (lr, hr))
52
Calling 'accept_hrec'
58
Returning from 'accept_hrec'
59
Taking false branch
1489 continue;
1490
1491 ty = *(lr->type);
1492 if (!tz_local)
60
Assuming 'tz_local' is not equal to 0
61
Taking false branch
1493 {
1494 time_t t = lr->date + tz_seconds_east_of_GMT;
1495 tm = gmtime (&t);
1496 }
1497 else
1498 tm = localtime (&(lr->date));
1499
1500 (void) printf ("%c %04d-%02d-%02d %02d:%02d %s %-*s", ty,
1501 tm->tm_year+1900, tm->tm_mon + 1, tm->tm_mday, tm->tm_hour,
1502 tm->tm_min, tz_name, user_len, lr->user);
1503
1504 workdir = xmalloc (strlen (lr->dir) + strlen (lr->end) + 10);
1505 (void) sprintf (workdir, "%s%s", lr->dir, lr->end);
1506 if ((cp = strrchr (workdir, '/')) != NULL((void*)0))
62
Assuming the condition is false
63
Taking false branch
1507 {
1508 if (lr->mod && !strcmp (++cp, lr->mod))
1509 {
1510 (void) strcpy (cp, "*");
1511 }
1512 }
1513 repos = xmalloc (strlen (lr->repos) + 10);
1514 (void) strcpy (repos, lr->repos);
1515 if ((cp = strrchr (repos, '/')) != NULL((void*)0))
64
Assuming the condition is true
65
Taking true branch
1516 {
1517 if (lr->mod && !strcmp (++cp, lr->mod))
66
Assuming field 'mod' is null
1518 {
1519 (void) strcpy (cp, "*");
1520 }
1521 }
1522
1523 switch (ty)
67
Control jumps to 'case 79:' at line 1534
1524 {
1525 case 'T':
1526 /* 'T'ag records: repository is a "tag type", rev is the tag */
1527 (void) printf (" %-*s [%s:%s]", mod_len, lr->mod, lr->rev,
1528 repos);
1529 if (working)
1530 (void) printf (" {%s}", workdir);
1531 break;
1532 case 'F':
1533 case 'E':
1534 case 'O':
1535 if (lr->rev && *(lr->rev))
68
Assuming field 'rev' is null
1536 (void) printf (" [%s]", lr->rev);
1537 (void) printf (" %-*s =%s%-*s %s", repos_len, repos, lr->mod,
1538 mod_len + 1 - (int) strlen (lr->mod),
69
Null pointer passed as 1st argument to string length function
1539 "=", workdir);
1540 break;
1541 case 'W':
1542 case 'U':
1543 case 'C':
1544 case 'G':
1545 case 'M':
1546 case 'A':
1547 case 'R':
1548 (void) printf (" %-*s %-*s %-*s =%s= %s", rev_len, lr->rev,
1549 file_len, lr->file, repos_len, repos,
1550 lr->mod ? lr->mod : "", workdir);
1551 break;
1552 default:
1553 (void) printf ("Hey! What is this junk? RecType[0x%2.2x]", ty);
1554 break;
1555 }
1556 (void) putchar ('\n')(!__isthreaded ? __sputc('\n', (&__sF[1])) : (putc)('\n',
(&__sF[1])))
;
1557 free (workdir);
1558 free (repos);
1559 }
1560}
1561
1562static int
1563accept_hrec (lr, hr)
1564 struct hrec *hr, *lr;
1565{
1566 int ty;
1567
1568 ty = *(lr->type);
1569
1570 if (last_since_tag && ty == 'T')
53
Assuming 'last_since_tag' is non-null
54
Assuming the condition is true
55
Taking true branch
1571 return (1);
56
Returning without writing to 'lr->mod'
57
Returning the value 1, which participates in a condition later
1572
1573 if (v_checkout)
1574 {
1575 if (ty != 'O')
1576 return (0); /* Only interested in 'O' records */
1577
1578 /* We want to identify all the states that cause the next record
1579 * ("hr") to be different from the current one ("lr") and only
1580 * print a line at the allowed boundaries.
1581 */
1582
1583 if (!hr || /* The last record */
1584 strcmp (hr->user, lr->user) || /* User has changed */
1585 strcmp (hr->mod, lr->mod) ||/* Module has changed */
1586 (working && /* If must match "workdir" */
1587 (strcmp (hr->dir, lr->dir) || /* and the 1st parts or */
1588 strcmp (hr->end, lr->end)))) /* the 2nd parts differ */
1589
1590 return (1);
1591 }
1592 else if (modified)
1593 {
1594 if (!last_entry || /* Don't want only last rec */
1595 !hr || /* Last entry is a "last entry" */
1596 strcmp (hr->repos, lr->repos) || /* Repository has changed */
1597 strcmp (hr->file, lr->file))/* File has changed */
1598 return (1);
1599
1600 if (working)
1601 { /* If must match "workdir" */
1602 if (strcmp (hr->dir, lr->dir) || /* and the 1st parts or */
1603 strcmp (hr->end, lr->end)) /* the 2nd parts differ */
1604 return (1);
1605 }
1606 }
1607 else if (module_report)
1608 {
1609 if (!last_entry || /* Don't want only last rec */
1610 !hr || /* Last entry is a "last entry" */
1611 strcmp (hr->mod, lr->mod) ||/* Module has changed */
1612 strcmp (hr->repos, lr->repos) || /* Repository has changed */
1613 strcmp (hr->file, lr->file))/* File has changed */
1614 return (1);
1615 }
1616 else
1617 {
1618 /* "extract" and "tag_report" always print selected records. */
1619 return (1);
1620 }
1621
1622 return (0);
1623}