3 list_packs.c -- list_packs utility to count Git packs and their objects
4 Copyright (C) 2016 Kyle J. McKay. All rights reserved.
6 This program is free software; you can redistribute it and/or
7 modify it under the terms of the GNU General Public License
8 as published by the Free Software Foundation; either version 2
9 of the License, or (at your option) any later version.
11 This program is distributed in the hope that it will be useful,
12 but WITHOUT ANY WARRANTY; without even the implied warranty of
13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 GNU General Public License for more details.
16 You should have received a copy of the GNU General Public License
17 along with this program; if not, write to the Free Software
18 Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
23 This utility is intended to be used by a script to assist in determining
24 whether or not it's time to run gc in the case where gc.auto=0 and when
25 it is to provide a convenient mechanism to feed selected pack names to
26 a script for futher processing at gc time.
28 Various options are available to select which .pack files to inspect
29 including supplying the names. This utility is intended to be able
30 to read the pack names from the --export-pack-edges file that may be
31 produced by git fast-import without needing any preprocessing.
33 See the list_packs.txt file or run the command with no arguments for help.
36 #define _XOPEN_SOURCE 600
37 #undef _FILE_OFFSET_BITS
38 #define _FILE_OFFSET_BITS 64
44 #include <sys/types.h> /* in some cases required before dirent.h or sys/stat.h */
50 #include <arpa/inet.h>
57 #define PATH_BUFF 4096
59 #define PATH_BUFF PATH_MAX
62 static int fgetfn(FILE *f
, char *b
, size_t s
);
63 static void process_pack_filename(char *fn
, size_t bl
, int hasps
);
64 static void process_pack(const char *fn
, uint32_t objcnt
);
65 static void process_packs_finish(void);
68 struct pack_info
*next
;
74 struct ext_info
*next
;
75 size_t extlen
; /* strlen(ext) */
76 char ext
[12]; /* includes leading '.' and trailing '\0' */
77 int xxt
; /* >0 excludes if ext present, 0 excludes if ext NOT present */
80 #define MAX_EXT_LEN (sizeof(((const struct ext_info *)0)->ext) - 2)
81 #define BAD_EXT_CHARS ":./\\ \t\n\v\f\r"
84 #include "list_packs.inc"
88 static int opt_xix
= -1;
89 static long opt_limit
= 0;
90 static int opt_count
= 0;
91 static uint64_t count
= 0;
92 static uint64_t objlimit
= 0;
93 static int opt_desc
= 0;
94 static int opt_boundary
= 0;
95 static uint64_t maxlimit
= 0;
96 static uint64_t processed
= 0;
98 static struct pack_info
*packlist
= NULL
;
99 static size_t packcount
= 0;
101 static struct ext_info
*extlist
= NULL
;
103 static char fnbuff
[PATH_BUFF
];
105 static void die(const char *fmt
, ...)
111 vfprintf(stderr
, fmt
, args
);
116 static void dienomem(const char *what
)
120 die("list_packs: error: out of memory%s%s\n", (what
? " for " : ""),
124 static void dienopackmem(void)
126 dienomem("pack list");
129 static void dienoextmem(void)
131 dienomem("ext list");
134 static void dieusage(int err
)
136 FILE *f
= err
? stderr
: stdout
;
139 fprintf(f
, "%s", USAGE
);
143 static int has_suffix(const char *s
, size_t l
, const char *x
, size_t b
)
145 if (!s
|| !x
|| !b
|| l
< (b
+ 1) /* ?<suffix> */)
147 return !strncmp(s
+ l
- b
, x
, b
);
149 #define has_idx_suffix(s,l) has_suffix((s),(l),".idx",4)
150 #define has_pack_suffix(s,l) has_suffix((s),(l),".pack",5)
152 static int is_pack_sha1_name(const char *s
, size_t l
)
156 if (strncmp(s
, "pack-", 5) || strncmp(s
+ l
- 5, ".pack", 5))
158 return strspn(s
+ 5, "0123456789abcdefABCDEF") == 40;
161 static struct ext_info
*find_add_ext(const char *ext
)
163 size_t elen
= strlen(ext
);
164 struct ext_info
*result
= extlist
;
166 while (result
&& strcmp(result
->ext
, ext
)) {
167 result
= result
->next
;
170 result
= (struct ext_info
*)malloc(sizeof(struct ext_info
));
173 result
->extlen
= elen
+ 1;
174 result
->ext
[0] = '.';
175 memcpy(&result
->ext
[1], ext
, elen
+ 1);
176 if (elen
+ 2 < sizeof(result
->ext
))
177 memset(&result
->ext
[elen
+ 2], 0, sizeof(result
->ext
) - (elen
+ 2));
179 result
->next
= extlist
;
185 void handle_ext_option(const char *ext
, int v
)
188 struct ext_info
*einfo
;
190 if (!ext
|| !*ext
|| !(elen
= strlen(ext
)) ||
191 elen
> MAX_EXT_LEN
|| strcspn(ext
, BAD_EXT_CHARS
) != elen
)
192 dieusage(EXIT_FAILURE
);
193 if (!strcmp(ext
, "idx")) {
196 einfo
= find_add_ext(ext
);
201 int main(int argc
, char *argv
[])
205 const char *only
= NULL
;
206 const char *dir
= NULL
;
210 for (argn
= 1; argn
< argc
; ++argn
) {
211 if (!strcmp(argv
[argn
], "-h") || !strcmp(argv
[argn
], "--help")) {
212 dieusage(EXIT_SUCCESS
);
213 } else if (!strcmp(argv
[argn
], "-q") || !strcmp(argv
[argn
], "--quiet")) {
215 } else if (!strcmp(argv
[argn
], "-a") || !strcmp(argv
[argn
], "--all")) {
217 } else if (!strcmp(argv
[argn
], "--exclude-idx")) {
219 } else if (!strcmp(argv
[argn
], "--exclude-no-idx")) {
221 } else if (!strcmp(argv
[argn
], "--exclude-keep")) {
222 handle_ext_option("keep", 1);
223 } else if (!strcmp(argv
[argn
], "--exclude-no-keep")) {
224 handle_ext_option("keep", 0);
225 } else if (!strcmp(argv
[argn
], "--exclude-bitmap")) {
226 handle_ext_option("bitmap", 1);
227 } else if (!strcmp(argv
[argn
], "--exclude-no-bitmap")) {
228 handle_ext_option("bitmap", 0);
229 } else if (!strcmp(argv
[argn
], "--exclude-bndl")) {
230 handle_ext_option("bndl", 1);
231 } else if (!strcmp(argv
[argn
], "--exclude-no-bndl")) {
232 handle_ext_option("bndl", 0);
233 } else if (!strcmp(argv
[argn
], "--count")) {
235 } else if (!strcmp(argv
[argn
], "--count-objects")) {
237 } else if (!strcmp(argv
[argn
], "--include-boundary")) {
239 } else if (!strcmp(argv
[argn
], "--exclude-ext")) {
241 dieusage(EXIT_FAILURE
);
242 handle_ext_option(argv
[argn
], 1);
243 } else if (!strcmp(argv
[argn
], "--exclude-no-ext")) {
245 dieusage(EXIT_FAILURE
);
246 handle_ext_option(argv
[argn
], 0);
247 } else if (!strcmp(argv
[argn
], "--exclude-limit")) {
252 dieusage(EXIT_FAILURE
);
253 limit
= strtol(argv
[argn
], &end
, 0);
254 if (!*argv
[argn
] || *end
|| !limit
)
255 dieusage(EXIT_FAILURE
);
257 } else if (!strcmp(argv
[argn
], "--object-limit")) {
262 dieusage(EXIT_FAILURE
);
263 limit
= strtol(argv
[argn
], &end
, 0);
264 if (!*argv
[argn
] || *end
|| !limit
)
265 dieusage(EXIT_FAILURE
);
268 objlimit
= (uint64_t)-limit
;
270 objlimit
= (uint64_t)limit
;
272 } else if (!strcmp(argv
[argn
], "--max-matches")) {
277 dieusage(EXIT_FAILURE
);
278 limit
= strtol(argv
[argn
], &end
, 0);
279 if (!*argv
[argn
] || *end
|| limit
<= 0)
280 dieusage(EXIT_FAILURE
);
281 maxlimit
= (uint64_t)limit
;
282 } else if (!strcmp(argv
[argn
], "--only")) {
283 if (++argn
>= argc
|| !*argv
[argn
])
284 dieusage(EXIT_FAILURE
);
286 } else if (!strcmp(argv
[argn
], "-C")) {
287 if (++argn
>= argc
|| !*argv
[argn
])
288 dieusage(EXIT_FAILURE
);
289 if (chdir(argv
[argn
])) {
291 fprintf(stderr
, "list_packs: error: "
292 "chdir '%s' failed\n", argv
[argn
]);
295 } else if (!strcmp(argv
[argn
], "--")) {
298 } else if (argv
[argn
][0] == '-' && argv
[argn
][1]) {
299 dieusage(EXIT_FAILURE
);
304 if (argn
< argc
&& *argv
[argn
])
306 if (argn
!= argc
|| (!only
&& !dir
) || (only
&& dir
) || (only
&& opt_a
))
307 dieusage(EXIT_FAILURE
);
309 if (!strcmp(only
, "-")) {
313 in
= fopen(only
, "r");
315 die("list_packs: error: could not open %s\n", only
);
317 while (fgetfn(in
, fnbuff
, sizeof(fnbuff
) - (MAX_EXT_LEN
+ 1))) {
319 size_t l
= strlen(fn
);
324 if (l
> 2 && !strncmp(fn
, "./", 2)) {
328 ips
= has_pack_suffix(fn
, l
);
329 process_pack_filename(fn
, (ips
? l
- 5 : l
), ips
);
339 while (l
> 1 && dir
[l
-1] == '/') {
342 if (l
> 2 && !strncmp(dir
, "./", 2)) {
346 if (l
+ 10 /* "/?.bitmap\0" */ > PATH_BUFF
)
347 die("list_packs: error: dirname too long\n");
348 memcpy(fnbuff
, dir
, l
);
352 die("list_packs: error: could not read directory %s\n", fnbuff
);
353 if (!strcmp(fnbuff
, ".")) {
357 if (l
&& fnbuff
[l
-1] != '/')
359 while ((e
= readdir(d
)) != NULL
) {
360 /* d_namlen is a nice, but non-POSIX extension */
361 size_t el
= strlen(e
->d_name
);
363 if (has_pack_suffix(e
->d_name
, el
) &&
364 (opt_a
|| is_pack_sha1_name(e
->d_name
, el
))) {
365 if (l
+ el
+ 3 /* "ap\0" */ > PATH_BUFF
) {
367 fprintf(stderr
, "list_packs: warning: "
368 "ignored input filename greater "
369 "than %d characters long\n",
373 memcpy(fnbuff
+ l
, e
->d_name
, el
+ 1 /* \0 */);
374 process_pack_filename(fnbuff
, l
+ el
- 5 /* .pack */, 1);
379 process_packs_finish();
381 printf("%"PRIu64
"\n", count
);
386 #define FNDELIM "\t\n\v\f\r :"
388 static int fgetfn(FILE *f
, char *b
, size_t s
)
393 if (!fgets(b
, (int)s
, f
)) {
396 fprintf(stderr
, "list_packs: error: an error "
397 "occurred reading pack name list file\n");
405 fnl
= strcspn(b
, FNDELIM
);
406 if (b
[l
-1] != '\n' && !feof(f
)) {
409 while ((ch
= getc_unlocked(f
)) != EOF
&& ch
!= '\n') {
417 if (fnl
< l
|| (!ferror(f
) && !trunc
)) {
423 fprintf(stderr
, "list_packs: error: an error "
424 "occurred reading pack name list file\n");
428 fprintf(stderr
, "list_packs: warning: ignored input filename "
429 "greater than %d characters long\n", (int)s
- 2);
434 static int file_exists(const char *fn
, struct stat
*s
)
437 if (S_ISREG(s
->st_mode
))
440 fprintf(stderr
, "list_packs: warning: ignoring "
441 "non-file '%s'\n", fn
);
446 static void process_pack_filename(char *fn
, size_t bl
, int hasps
)
456 const struct ext_info
*einfo
;
458 if (stat(fn
, &ps
) || !S_ISREG(ps
.st_mode
)) {
460 fprintf(stderr
, "list_packs: warning: ignoring "
461 "non-file '%s'\n", fn
);
464 if (ps
.st_size
< 32) {
466 fprintf(stderr
, "list_packs: warning: ignoring "
467 "invalid pack file '%s'\n", fn
);
472 if (einfo
->xxt
>= 0) {
475 memcpy(fn
+ bl
, einfo
->ext
, einfo
->extlen
+ 1);
476 hext
= file_exists(fn
, &es
);
477 if ((einfo
->xxt
&& hext
) || (!einfo
->xxt
&& !hext
))
485 memcpy(fn
+ bl
, ".idx", 5);
486 hx
= file_exists(fn
, &es
);
487 if ((opt_xix
&& hx
) || (!opt_xix
&& !hx
))
491 memcpy(fn
+ bl
, ".pack", 6);
497 fprintf(stderr
, "list_packs: warning: ignoring "
498 "unopenable pack file '%s'\n", fn
);
501 if (fread(&hdr
, 12, 1, f
) != 1) {
504 fprintf(stderr
, "list_packs: warning: ignoring "
505 "unreadable pack file '%s'\n", fn
);
509 packver
= ntohl(hdr
.u
[1]);
510 objcnt
= ntohl(hdr
.u
[2]);
511 if (memcmp(hdr
.c
, "PACK", 4) || (packver
!= 2 && packver
!= 3) ||
512 ps
.st_size
< ((off_t
)objcnt
+ 32)) {
514 fprintf(stderr
, "list_packs: warning: ignoring "
515 "invalid pack file '%s'\n", fn
);
518 if (!opt_xix
&& es
.st_size
< ((off_t
)objcnt
* 28 + 1072)) {
520 fprintf(stderr
, "list_packs: warning: ignoring pack "
521 "with invalid idx file '%.*s.idx'\n", (int)bl
, fn
);
525 if ((opt_limit
> 0 && objcnt
>= (uint32_t)opt_limit
) ||
526 (opt_limit
< 0 && objcnt
< (uint32_t)-opt_limit
))
529 /* the PACK file passed all the checks, process it */
531 size_t fnlen
= strlen(fn
);
532 struct pack_info
*info
= (struct pack_info
*)
533 malloc(sizeof(struct pack_info
) + fnlen
);
537 info
->objcount
= objcnt
;
538 memcpy(info
->filename
, fn
, fnlen
+ 1);
539 info
->next
= packlist
;
543 process_pack(fn
, objcnt
);
547 static void process_pack(const char *fn
, uint32_t objcnt
)
549 if (maxlimit
&& processed
>= maxlimit
)
562 static void process_pack_info(const struct pack_info
*pack
)
564 process_pack(pack
->filename
, pack
->objcount
);
567 static int sort_asc(const void *p
, const void *q
)
569 const struct pack_info
**a
= (const struct pack_info
**)p
;
570 const struct pack_info
**b
= (const struct pack_info
**)q
;
571 if ((*a
)->objcount
< (*b
)->objcount
)
573 if ((*a
)->objcount
> (*b
)->objcount
)
575 return strcmp((*a
)->filename
, (*b
)->filename
);
578 static int sort_dsc(const void *p
, const void *q
)
580 return sort_asc(q
, p
);
583 static void process_packs_finish(void)
585 struct pack_info
**table
, *p
;
589 if (!objlimit
|| !packlist
|| !packcount
)
591 table
= (struct pack_info
**)malloc(sizeof(struct pack_info
*) * packcount
);
600 qsort(table
, packcount
, sizeof(struct pack_info
*), (opt_desc
? sort_dsc
: sort_asc
));
602 for (i
=0; i
< packcount
; ++i
) {
603 tally
+= table
[i
]->objcount
;
604 if (tally
<= objlimit
) {
605 process_pack_info(table
[i
]);
608 process_pack_info(table
[i
]);