3 list_packs.c -- list_packs utility to count Git packs and their objects
4 Copyright (C) 2016,2017 Kyle J. McKay.
7 This program is free software; you can redistribute it and/or
8 modify it under the terms of the GNU General Public License
9 as published by the Free Software Foundation; either version 2
10 of the License, or (at your option) any later version.
12 This program is distributed in the hope that it will be useful,
13 but WITHOUT ANY WARRANTY; without even the implied warranty of
14 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 GNU General Public License for more details.
17 You should have received a copy of the GNU General Public License
18 along with this program; if not, write to the Free Software
19 Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
24 This utility is intended to be used by a script to assist in determining
25 whether or not it's time to run gc in the case where gc.auto=0 and when
26 it is to provide a convenient mechanism to feed selected pack names to
27 a script for futher processing at gc time.
29 Various options are available to select which .pack files to inspect
30 including supplying the names. This utility is intended to be able
31 to read the pack names from the --export-pack-edges file that may be
32 produced by git fast-import without needing any preprocessing.
34 See the list_packs.txt file or run the command with no arguments for help.
37 #define _XOPEN_SOURCE 600
38 #undef _FILE_OFFSET_BITS
39 #define _FILE_OFFSET_BITS 64
45 #include <sys/types.h> /* in some cases required before dirent.h or sys/stat.h */
51 #include <arpa/inet.h>
58 #define PATH_BUFF 4096
60 #define PATH_BUFF PATH_MAX
63 static int fgetfn(FILE *f
, char *b
, size_t s
);
64 static void process_pack_filename(char *fn
, size_t bl
, int hasps
);
65 static void process_pack(const char *fn
, uint32_t objcnt
);
66 static void process_packs_finish(void);
69 struct pack_info
*next
;
75 struct ext_info
*next
;
76 size_t extlen
; /* strlen(ext) */
77 char ext
[12]; /* includes leading '.' and trailing '\0' */
78 int xxt
; /* >0 excludes if ext present, 0 excludes if ext NOT present */
81 #define MAX_EXT_LEN (sizeof(((const struct ext_info *)0)->ext) - 2)
82 #define BAD_EXT_CHARS ":./\\ \t\n\v\f\r"
85 #include "list_packs.inc"
89 static int opt_xix
= -1;
90 static long opt_limit
= 0;
91 static int opt_count
= 0;
92 static uint64_t count
= 0;
93 static uint64_t objlimit
= 0;
94 static int opt_desc
= 0;
95 static int opt_boundary
= 0;
96 static uint64_t maxlimit
= 0;
97 static uint64_t processed
= 0;
99 static struct pack_info
*packlist
= NULL
;
100 static size_t packcount
= 0;
102 static struct ext_info
*extlist
= NULL
;
104 static char fnbuff
[PATH_BUFF
];
106 static void die(const char *fmt
, ...)
112 vfprintf(stderr
, fmt
, args
);
117 static void dienomem(const char *what
)
121 die("list_packs: error: out of memory%s%s\n", (what
? " for " : ""),
125 static void dienopackmem(void)
127 dienomem("pack list");
130 static void dienoextmem(void)
132 dienomem("ext list");
135 static void dieusage(int err
)
137 FILE *f
= err
? stderr
: stdout
;
140 fprintf(f
, "%s", USAGE
);
144 static int has_suffix(const char *s
, size_t l
, const char *x
, size_t b
)
146 if (!s
|| !x
|| !b
|| l
< (b
+ 1) /* ?<suffix> */)
148 return !strncmp(s
+ l
- b
, x
, b
);
150 #define has_idx_suffix(s,l) has_suffix((s),(l),".idx",4)
151 #define has_pack_suffix(s,l) has_suffix((s),(l),".pack",5)
153 static int is_pack_sha1_name(const char *s
, size_t l
)
157 if (strncmp(s
, "pack-", 5) || strncmp(s
+ l
- 5, ".pack", 5))
159 return strspn(s
+ 5, "0123456789abcdefABCDEF") >= 40;
162 static struct ext_info
*find_add_ext(const char *ext
)
164 size_t elen
= strlen(ext
);
165 struct ext_info
*result
= extlist
;
167 while (result
&& strcmp(result
->ext
, ext
)) {
168 result
= result
->next
;
171 result
= (struct ext_info
*)malloc(sizeof(struct ext_info
));
174 result
->extlen
= elen
+ 1;
175 result
->ext
[0] = '.';
176 memcpy(&result
->ext
[1], ext
, elen
+ 1);
177 if (elen
+ 2 < sizeof(result
->ext
))
178 memset(&result
->ext
[elen
+ 2], 0, sizeof(result
->ext
) - (elen
+ 2));
180 result
->next
= extlist
;
186 void handle_ext_option(const char *ext
, int v
)
189 struct ext_info
*einfo
;
191 if (!ext
|| !*ext
|| !(elen
= strlen(ext
)) ||
192 elen
> MAX_EXT_LEN
|| strcspn(ext
, BAD_EXT_CHARS
) != elen
)
193 dieusage(EXIT_FAILURE
);
194 if (!strcmp(ext
, "idx")) {
197 einfo
= find_add_ext(ext
);
202 int main(int argc
, char *argv
[])
206 const char *only
= NULL
;
207 const char *dir
= NULL
;
211 for (argn
= 1; argn
< argc
; ++argn
) {
212 if (!strcmp(argv
[argn
], "-h") || !strcmp(argv
[argn
], "--help")) {
213 dieusage(EXIT_SUCCESS
);
214 } else if (!strcmp(argv
[argn
], "-q") || !strcmp(argv
[argn
], "--quiet")) {
216 } else if (!strcmp(argv
[argn
], "-a") || !strcmp(argv
[argn
], "--all")) {
218 } else if (!strcmp(argv
[argn
], "--exclude-idx")) {
220 } else if (!strcmp(argv
[argn
], "--exclude-no-idx")) {
222 } else if (!strcmp(argv
[argn
], "--exclude-keep")) {
223 handle_ext_option("keep", 1);
224 } else if (!strcmp(argv
[argn
], "--exclude-no-keep")) {
225 handle_ext_option("keep", 0);
226 } else if (!strcmp(argv
[argn
], "--exclude-bitmap")) {
227 handle_ext_option("bitmap", 1);
228 } else if (!strcmp(argv
[argn
], "--exclude-no-bitmap")) {
229 handle_ext_option("bitmap", 0);
230 } else if (!strcmp(argv
[argn
], "--exclude-bndl")) {
231 handle_ext_option("bndl", 1);
232 } else if (!strcmp(argv
[argn
], "--exclude-no-bndl")) {
233 handle_ext_option("bndl", 0);
234 } else if (!strcmp(argv
[argn
], "--count")) {
236 } else if (!strcmp(argv
[argn
], "--count-objects")) {
238 } else if (!strcmp(argv
[argn
], "--include-boundary")) {
240 } else if (!strcmp(argv
[argn
], "--exclude-ext")) {
242 dieusage(EXIT_FAILURE
);
243 handle_ext_option(argv
[argn
], 1);
244 } else if (!strcmp(argv
[argn
], "--exclude-no-ext")) {
246 dieusage(EXIT_FAILURE
);
247 handle_ext_option(argv
[argn
], 0);
248 } else if (!strcmp(argv
[argn
], "--exclude-limit")) {
253 dieusage(EXIT_FAILURE
);
254 limit
= strtol(argv
[argn
], &end
, 0);
255 if (!*argv
[argn
] || *end
|| !limit
)
256 dieusage(EXIT_FAILURE
);
258 } else if (!strcmp(argv
[argn
], "--object-limit")) {
263 dieusage(EXIT_FAILURE
);
264 limit
= strtol(argv
[argn
], &end
, 0);
265 if (!*argv
[argn
] || *end
|| !limit
)
266 dieusage(EXIT_FAILURE
);
269 objlimit
= (uint64_t)-limit
;
271 objlimit
= (uint64_t)limit
;
273 } else if (!strcmp(argv
[argn
], "--max-matches")) {
278 dieusage(EXIT_FAILURE
);
279 limit
= strtol(argv
[argn
], &end
, 0);
280 if (!*argv
[argn
] || *end
|| limit
<= 0)
281 dieusage(EXIT_FAILURE
);
282 maxlimit
= (uint64_t)limit
;
283 } else if (!strcmp(argv
[argn
], "--only")) {
284 if (++argn
>= argc
|| !*argv
[argn
])
285 dieusage(EXIT_FAILURE
);
287 } else if (!strcmp(argv
[argn
], "-C")) {
288 if (++argn
>= argc
|| !*argv
[argn
])
289 dieusage(EXIT_FAILURE
);
290 if (chdir(argv
[argn
])) {
292 fprintf(stderr
, "list_packs: error: "
293 "chdir '%s' failed\n", argv
[argn
]);
296 } else if (!strcmp(argv
[argn
], "--")) {
299 } else if (argv
[argn
][0] == '-' && argv
[argn
][1]) {
300 dieusage(EXIT_FAILURE
);
305 if (argn
< argc
&& *argv
[argn
])
307 if (argn
!= argc
|| (!only
&& !dir
) || (only
&& dir
) || (only
&& opt_a
))
308 dieusage(EXIT_FAILURE
);
310 if (!strcmp(only
, "-")) {
314 in
= fopen(only
, "r");
316 die("list_packs: error: could not open %s\n", only
);
318 while (fgetfn(in
, fnbuff
, sizeof(fnbuff
) - (MAX_EXT_LEN
+ 1))) {
320 size_t l
= strlen(fn
);
325 if (l
> 2 && !strncmp(fn
, "./", 2)) {
329 ips
= has_pack_suffix(fn
, l
);
330 process_pack_filename(fn
, (ips
? l
- 5 : l
), ips
);
340 while (l
> 1 && dir
[l
-1] == '/') {
343 if (l
> 2 && !strncmp(dir
, "./", 2)) {
347 if (l
+ 10 /* "/?.bitmap\0" */ > PATH_BUFF
)
348 die("list_packs: error: dirname too long\n");
349 memcpy(fnbuff
, dir
, l
);
353 die("list_packs: error: could not read directory %s\n", fnbuff
);
354 if (!strcmp(fnbuff
, ".")) {
358 if (l
&& fnbuff
[l
-1] != '/')
360 while ((e
= readdir(d
)) != NULL
) {
361 /* d_namlen is a nice, but non-POSIX extension */
362 size_t el
= strlen(e
->d_name
);
364 if (has_pack_suffix(e
->d_name
, el
) &&
365 (opt_a
|| is_pack_sha1_name(e
->d_name
, el
))) {
366 if (l
+ el
+ 3 /* "ap\0" */ > PATH_BUFF
) {
368 fprintf(stderr
, "list_packs: warning: "
369 "ignored input filename greater "
370 "than %d characters long\n",
374 memcpy(fnbuff
+ l
, e
->d_name
, el
+ 1 /* \0 */);
375 process_pack_filename(fnbuff
, l
+ el
- 5 /* .pack */, 1);
380 process_packs_finish();
382 printf("%"PRIu64
"\n", count
);
387 #define FNDELIM "\t\n\v\f\r :"
389 static int fgetfn(FILE *f
, char *b
, size_t s
)
394 if (!fgets(b
, (int)s
, f
)) {
397 fprintf(stderr
, "list_packs: error: an error "
398 "occurred reading pack name list file\n");
406 fnl
= strcspn(b
, FNDELIM
);
407 if (b
[l
-1] != '\n' && !feof(f
)) {
410 while ((ch
= getc_unlocked(f
)) != EOF
&& ch
!= '\n') {
418 if (fnl
< l
|| (!ferror(f
) && !trunc
)) {
424 fprintf(stderr
, "list_packs: error: an error "
425 "occurred reading pack name list file\n");
429 fprintf(stderr
, "list_packs: warning: ignored input filename "
430 "greater than %d characters long\n", (int)s
- 2);
435 static int file_exists(const char *fn
, struct stat
*s
)
438 if (S_ISREG(s
->st_mode
))
441 fprintf(stderr
, "list_packs: warning: ignoring "
442 "non-file '%s'\n", fn
);
447 static void process_pack_filename(char *fn
, size_t bl
, int hasps
)
457 const struct ext_info
*einfo
;
459 if (stat(fn
, &ps
) || !S_ISREG(ps
.st_mode
)) {
461 fprintf(stderr
, "list_packs: warning: ignoring "
462 "non-file '%s'\n", fn
);
465 if (ps
.st_size
< 32) {
467 fprintf(stderr
, "list_packs: warning: ignoring "
468 "invalid pack file '%s'\n", fn
);
473 if (einfo
->xxt
>= 0) {
476 memcpy(fn
+ bl
, einfo
->ext
, einfo
->extlen
+ 1);
477 hext
= file_exists(fn
, &es
);
478 if ((einfo
->xxt
&& hext
) || (!einfo
->xxt
&& !hext
))
486 memcpy(fn
+ bl
, ".idx", 5);
487 hx
= file_exists(fn
, &es
);
488 if ((opt_xix
&& hx
) || (!opt_xix
&& !hx
))
492 memcpy(fn
+ bl
, ".pack", 6);
498 fprintf(stderr
, "list_packs: warning: ignoring "
499 "unopenable pack file '%s'\n", fn
);
502 if (fread(&hdr
, 12, 1, f
) != 1) {
505 fprintf(stderr
, "list_packs: warning: ignoring "
506 "unreadable pack file '%s'\n", fn
);
510 packver
= ntohl(hdr
.u
[1]);
511 objcnt
= ntohl(hdr
.u
[2]);
512 if (memcmp(hdr
.c
, "PACK", 4) || (packver
!= 2 && packver
!= 3) ||
513 ps
.st_size
< ((off_t
)objcnt
+ 32)) {
515 fprintf(stderr
, "list_packs: warning: ignoring "
516 "invalid pack file '%s'\n", fn
);
519 if (!opt_xix
&& es
.st_size
< ((off_t
)objcnt
* 28 + 1072)) {
521 fprintf(stderr
, "list_packs: warning: ignoring pack "
522 "with invalid idx file '%.*s.idx'\n", (int)bl
, fn
);
526 if ((opt_limit
> 0 && objcnt
>= (uint32_t)opt_limit
) ||
527 (opt_limit
< 0 && objcnt
< (uint32_t)-opt_limit
))
530 /* the PACK file passed all the checks, process it */
532 size_t fnlen
= strlen(fn
);
533 struct pack_info
*info
= (struct pack_info
*)
534 malloc(sizeof(struct pack_info
) + fnlen
);
538 info
->objcount
= objcnt
;
539 memcpy(info
->filename
, fn
, fnlen
+ 1);
540 info
->next
= packlist
;
544 process_pack(fn
, objcnt
);
548 static void process_pack(const char *fn
, uint32_t objcnt
)
550 if (maxlimit
&& processed
>= maxlimit
)
563 static void process_pack_info(const struct pack_info
*pack
)
565 process_pack(pack
->filename
, pack
->objcount
);
568 static int sort_asc(const void *p
, const void *q
)
570 const struct pack_info
**a
= (const struct pack_info
**)p
;
571 const struct pack_info
**b
= (const struct pack_info
**)q
;
572 if ((*a
)->objcount
< (*b
)->objcount
)
574 if ((*a
)->objcount
> (*b
)->objcount
)
576 return strcmp((*a
)->filename
, (*b
)->filename
);
579 static int sort_dsc(const void *p
, const void *q
)
581 return sort_asc(q
, p
);
584 static void process_packs_finish(void)
586 struct pack_info
**table
, *p
;
590 if (!objlimit
|| !packlist
|| !packcount
)
592 table
= (struct pack_info
**)malloc(sizeof(struct pack_info
*) * packcount
);
601 qsort(table
, packcount
, sizeof(struct pack_info
*), (opt_desc
? sort_dsc
: sort_asc
));
603 for (i
=0; i
< packcount
; ++i
) {
604 tally
+= table
[i
]->objcount
;
605 if (tally
<= objlimit
) {
606 process_pack_info(table
[i
]);
609 process_pack_info(table
[i
]);