treewide: include parse-options.h in source files
[git/debian.git] / diagnose.c
blobb7a29612dae6d5c8405491d0de6ce934c279b542
1 #include "cache.h"
2 #include "diagnose.h"
3 #include "compat/disk.h"
4 #include "archive.h"
5 #include "dir.h"
6 #include "help.h"
7 #include "strvec.h"
8 #include "object-store.h"
9 #include "packfile.h"
10 #include "parse-options.h"
12 struct archive_dir {
13 const char *path;
14 int recursive;
17 struct diagnose_option {
18 enum diagnose_mode mode;
19 const char *option_name;
22 static struct diagnose_option diagnose_options[] = {
23 { DIAGNOSE_STATS, "stats" },
24 { DIAGNOSE_ALL, "all" },
27 int option_parse_diagnose(const struct option *opt, const char *arg, int unset)
29 int i;
30 enum diagnose_mode *diagnose = opt->value;
32 if (!arg) {
33 *diagnose = unset ? DIAGNOSE_NONE : DIAGNOSE_STATS;
34 return 0;
37 for (i = 0; i < ARRAY_SIZE(diagnose_options); i++) {
38 if (!strcmp(arg, diagnose_options[i].option_name)) {
39 *diagnose = diagnose_options[i].mode;
40 return 0;
44 return error(_("invalid --%s value '%s'"), opt->long_name, arg);
47 static void dir_file_stats_objects(const char *full_path, size_t full_path_len,
48 const char *file_name, void *data)
50 struct strbuf *buf = data;
51 struct stat st;
53 if (!stat(full_path, &st))
54 strbuf_addf(buf, "%-70s %16" PRIuMAX "\n", file_name,
55 (uintmax_t)st.st_size);
58 static int dir_file_stats(struct object_directory *object_dir, void *data)
60 struct strbuf *buf = data;
62 strbuf_addf(buf, "Contents of %s:\n", object_dir->path);
64 for_each_file_in_pack_dir(object_dir->path, dir_file_stats_objects,
65 data);
67 return 0;
71 * Get the d_type of a dirent. If the d_type is unknown, derive it from
72 * stat.st_mode.
74 * Note that 'path' is assumed to have a trailing slash. It is also modified
75 * in-place during the execution of the function, but is then reverted to its
76 * original value before returning.
78 static unsigned char get_dtype(struct dirent *e, struct strbuf *path)
80 struct stat st;
81 unsigned char dtype = DTYPE(e);
82 size_t base_path_len;
84 if (dtype != DT_UNKNOWN)
85 return dtype;
87 /* d_type unknown in dirent, try to fall back on lstat results */
88 base_path_len = path->len;
89 strbuf_addstr(path, e->d_name);
90 if (lstat(path->buf, &st))
91 goto cleanup;
93 /* determine d_type from st_mode */
94 if (S_ISREG(st.st_mode))
95 dtype = DT_REG;
96 else if (S_ISDIR(st.st_mode))
97 dtype = DT_DIR;
98 else if (S_ISLNK(st.st_mode))
99 dtype = DT_LNK;
101 cleanup:
102 strbuf_setlen(path, base_path_len);
103 return dtype;
106 static int count_files(struct strbuf *path)
108 DIR *dir = opendir(path->buf);
109 struct dirent *e;
110 int count = 0;
112 if (!dir)
113 return 0;
115 while ((e = readdir_skip_dot_and_dotdot(dir)) != NULL)
116 if (get_dtype(e, path) == DT_REG)
117 count++;
119 closedir(dir);
120 return count;
123 static void loose_objs_stats(struct strbuf *buf, const char *path)
125 DIR *dir = opendir(path);
126 struct dirent *e;
127 int count;
128 int total = 0;
129 unsigned char c;
130 struct strbuf count_path = STRBUF_INIT;
131 size_t base_path_len;
133 if (!dir)
134 return;
136 strbuf_addstr(buf, "Object directory stats for ");
137 strbuf_add_absolute_path(buf, path);
138 strbuf_addstr(buf, ":\n");
140 strbuf_add_absolute_path(&count_path, path);
141 strbuf_addch(&count_path, '/');
142 base_path_len = count_path.len;
144 while ((e = readdir_skip_dot_and_dotdot(dir)) != NULL)
145 if (get_dtype(e, &count_path) == DT_DIR &&
146 strlen(e->d_name) == 2 &&
147 !hex_to_bytes(&c, e->d_name, 1)) {
148 strbuf_setlen(&count_path, base_path_len);
149 strbuf_addf(&count_path, "%s/", e->d_name);
150 total += (count = count_files(&count_path));
151 strbuf_addf(buf, "%s : %7d files\n", e->d_name, count);
154 strbuf_addf(buf, "Total: %d loose objects", total);
156 strbuf_release(&count_path);
157 closedir(dir);
160 static int add_directory_to_archiver(struct strvec *archiver_args,
161 const char *path, int recurse)
163 int at_root = !*path;
164 DIR *dir;
165 struct dirent *e;
166 struct strbuf buf = STRBUF_INIT;
167 size_t len;
168 int res = 0;
170 dir = opendir(at_root ? "." : path);
171 if (!dir) {
172 if (errno == ENOENT) {
173 warning(_("could not archive missing directory '%s'"), path);
174 return 0;
176 return error_errno(_("could not open directory '%s'"), path);
179 if (!at_root)
180 strbuf_addf(&buf, "%s/", path);
181 len = buf.len;
182 strvec_pushf(archiver_args, "--prefix=%s", buf.buf);
184 while (!res && (e = readdir_skip_dot_and_dotdot(dir))) {
185 struct strbuf abspath = STRBUF_INIT;
186 unsigned char dtype;
188 strbuf_add_absolute_path(&abspath, at_root ? "." : path);
189 strbuf_addch(&abspath, '/');
190 dtype = get_dtype(e, &abspath);
192 strbuf_setlen(&buf, len);
193 strbuf_addstr(&buf, e->d_name);
195 if (dtype == DT_REG)
196 strvec_pushf(archiver_args, "--add-file=%s", buf.buf);
197 else if (dtype != DT_DIR)
198 warning(_("skipping '%s', which is neither file nor "
199 "directory"), buf.buf);
200 else if (recurse &&
201 add_directory_to_archiver(archiver_args,
202 buf.buf, recurse) < 0)
203 res = -1;
205 strbuf_release(&abspath);
208 closedir(dir);
209 strbuf_release(&buf);
210 return res;
213 int create_diagnostics_archive(struct strbuf *zip_path, enum diagnose_mode mode)
215 struct strvec archiver_args = STRVEC_INIT;
216 char **argv_copy = NULL;
217 int stdout_fd = -1, archiver_fd = -1;
218 struct strbuf buf = STRBUF_INIT;
219 int res, i;
220 struct archive_dir archive_dirs[] = {
221 { ".git", 0 },
222 { ".git/hooks", 0 },
223 { ".git/info", 0 },
224 { ".git/logs", 1 },
225 { ".git/objects/info", 0 }
228 if (mode == DIAGNOSE_NONE) {
229 res = 0;
230 goto diagnose_cleanup;
233 stdout_fd = dup(STDOUT_FILENO);
234 if (stdout_fd < 0) {
235 res = error_errno(_("could not duplicate stdout"));
236 goto diagnose_cleanup;
239 archiver_fd = xopen(zip_path->buf, O_CREAT | O_WRONLY | O_TRUNC, 0666);
240 if (dup2(archiver_fd, STDOUT_FILENO) < 0) {
241 res = error_errno(_("could not redirect output"));
242 goto diagnose_cleanup;
245 init_zip_archiver();
246 strvec_pushl(&archiver_args, "git-diagnose", "--format=zip", NULL);
248 strbuf_reset(&buf);
249 strbuf_addstr(&buf, "Collecting diagnostic info\n\n");
250 get_version_info(&buf, 1);
252 strbuf_addf(&buf, "Repository root: %s\n", the_repository->worktree);
253 get_disk_info(&buf);
254 write_or_die(stdout_fd, buf.buf, buf.len);
255 strvec_pushf(&archiver_args,
256 "--add-virtual-file=diagnostics.log:%.*s",
257 (int)buf.len, buf.buf);
259 strbuf_reset(&buf);
260 strbuf_addstr(&buf, "--add-virtual-file=packs-local.txt:");
261 dir_file_stats(the_repository->objects->odb, &buf);
262 foreach_alt_odb(dir_file_stats, &buf);
263 strvec_push(&archiver_args, buf.buf);
265 strbuf_reset(&buf);
266 strbuf_addstr(&buf, "--add-virtual-file=objects-local.txt:");
267 loose_objs_stats(&buf, ".git/objects");
268 strvec_push(&archiver_args, buf.buf);
270 /* Only include this if explicitly requested */
271 if (mode == DIAGNOSE_ALL) {
272 for (i = 0; i < ARRAY_SIZE(archive_dirs); i++) {
273 if (add_directory_to_archiver(&archiver_args,
274 archive_dirs[i].path,
275 archive_dirs[i].recursive)) {
276 res = error_errno(_("could not add directory '%s' to archiver"),
277 archive_dirs[i].path);
278 goto diagnose_cleanup;
283 strvec_pushl(&archiver_args, "--prefix=",
284 oid_to_hex(the_hash_algo->empty_tree), "--", NULL);
286 /* `write_archive()` modifies the `argv` passed to it. Let it. */
287 argv_copy = xmemdupz(archiver_args.v,
288 sizeof(char *) * archiver_args.nr);
289 res = write_archive(archiver_args.nr, (const char **)argv_copy, NULL,
290 the_repository, NULL, 0);
291 if (res) {
292 error(_("failed to write archive"));
293 goto diagnose_cleanup;
296 fprintf(stderr, "\n"
297 "Diagnostics complete.\n"
298 "All of the gathered info is captured in '%s'\n",
299 zip_path->buf);
301 diagnose_cleanup:
302 if (archiver_fd >= 0) {
303 dup2(stdout_fd, STDOUT_FILENO);
304 close(stdout_fd);
305 close(archiver_fd);
307 free(argv_copy);
308 strvec_clear(&archiver_args);
309 strbuf_release(&buf);
311 return res;