/* * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public * License v2 as published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public * License along with this program; if not, write to the * Free Software Foundation, Inc., 59 Temple Place - Suite 330, * Boston, MA 021110-1307, USA. */ #define _GNU_SOURCE #include #include #include #include #include #include #include #include #include #include #include #include #include #include #undef ULONG_MAX #include "kerncompat.h" #include "ctree.h" #include "ioctl.h" #include "utils.h" static int use_color; static void push_im(gdImagePtr im, char *name, char *dir) { char fullname[2000]; FILE *pngout; if (!im) return; snprintf(fullname, sizeof(fullname), "%s/%s", dir, name); pngout = fopen(fullname, "w"); if (!pngout) { printf("unable to create file %s\n", fullname); exit(1); } gdImagePng(im, pngout); fclose(pngout); gdImageDestroy(im); } static char * chunk_type(u64 flags) { switch (flags & (BTRFS_BLOCK_GROUP_SYSTEM | BTRFS_BLOCK_GROUP_DATA | BTRFS_BLOCK_GROUP_METADATA)) { case BTRFS_BLOCK_GROUP_SYSTEM: return "system"; case BTRFS_BLOCK_GROUP_DATA: return "data"; case BTRFS_BLOCK_GROUP_METADATA: return "metadata"; case BTRFS_BLOCK_GROUP_DATA | BTRFS_BLOCK_GROUP_METADATA: return "mixed"; default: return "invalid"; } } static void print_bg(FILE *html, char *name, u64 start, u64 len, u64 used, u64 flags, u64 areas) { double frag = (double)areas / (len / 4096) * 2; fprintf(html, "

%s chunk starts at %lld, size is %s, %.2f%% used, " "%.2f%% fragmented

\n", chunk_type(flags), start, pretty_size(len), 100.0 * used / len, 100.0 * frag); fprintf(html, "\n", name); } enum tree_colors { COLOR_ROOT = 0, COLOR_EXTENT, COLOR_CHUNK, COLOR_DEV, COLOR_FS, COLOR_CSUM, COLOR_RELOC, COLOR_DATA, COLOR_UNKNOWN, COLOR_MAX }; static int get_color(struct btrfs_extent_item *item, int len) { u64 refs; u64 flags; u8 type; u64 offset; struct btrfs_extent_inline_ref *ref; refs = btrfs_stack_extent_refs(item); flags = btrfs_stack_extent_flags(item); if (flags & BTRFS_EXTENT_FLAG_DATA) return COLOR_DATA; if (refs > 1) { /* this must be an fs tree */ return COLOR_FS; } ref = (void *)item + sizeof(struct btrfs_extent_item) + sizeof(struct btrfs_tree_block_info); type = btrfs_stack_extent_inline_ref_type(ref); offset = btrfs_stack_extent_inline_ref_offset(ref); switch (type) { case BTRFS_EXTENT_DATA_REF_KEY: return COLOR_DATA; case BTRFS_SHARED_BLOCK_REF_KEY: case BTRFS_SHARED_DATA_REF_KEY: return COLOR_FS; case BTRFS_TREE_BLOCK_REF_KEY: break; default: return COLOR_UNKNOWN; } switch (offset) { case BTRFS_ROOT_TREE_OBJECTID: return COLOR_ROOT; case BTRFS_EXTENT_TREE_OBJECTID: return COLOR_EXTENT; case BTRFS_CHUNK_TREE_OBJECTID: return COLOR_CHUNK; case BTRFS_DEV_TREE_OBJECTID: return COLOR_DEV; case BTRFS_FS_TREE_OBJECTID: return COLOR_FS; case BTRFS_CSUM_TREE_OBJECTID: return COLOR_CSUM; case BTRFS_DATA_RELOC_TREE_OBJECTID: return COLOR_RELOC; } return COLOR_UNKNOWN; } static void init_colors(gdImagePtr im, int *colors) { colors[COLOR_ROOT] = gdImageColorAllocate(im, 255, 0, 0); colors[COLOR_EXTENT] = gdImageColorAllocate(im, 0, 255, 0); colors[COLOR_CHUNK] = gdImageColorAllocate(im, 255, 0, 0); colors[COLOR_DEV] = gdImageColorAllocate(im, 255, 0, 0); colors[COLOR_FS] = gdImageColorAllocate(im, 0, 0, 0); colors[COLOR_CSUM] = gdImageColorAllocate(im, 0, 0, 255); colors[COLOR_RELOC] = gdImageColorAllocate(im, 128, 128, 128); colors[COLOR_DATA] = gdImageColorAllocate(im, 100, 0, 0); colors[COLOR_UNKNOWN] = gdImageColorAllocate(im, 50, 50, 50); } int list_fragments(int fd, u64 flags, char *dir) { int ret; struct btrfs_ioctl_search_args args; struct btrfs_ioctl_search_key *sk = &args.key; int i; struct btrfs_ioctl_search_header *sh; unsigned long off = 0; int bgnum = 0; u64 bgstart = 0; u64 bglen = 0; u64 bgend = 0; u64 bgflags = 0; u64 bgused = 0; u64 saved_extent = 0; u64 saved_len = 0; int saved_color = 0; u64 last_end = 0; u64 areas = 0; long px; char name[1000]; FILE *html; int colors[COLOR_MAX]; gdImagePtr im = NULL; int black = 0; int width = 800; snprintf(name, sizeof(name), "%s/index.html", dir); html = fopen(name, "w"); if (!html) { printf("unable to create %s\n", name); exit(1); } fprintf(html, "
\n"); fprintf(html, "Btrfs Block Group Allocation Map\n"); fprintf(html, "\n"); fprintf(html, "
\n"); memset(&args, 0, sizeof(args)); sk->tree_id = 2; sk->max_type = -1; sk->min_type = 0; sk->max_objectid = (u64)-1; sk->max_offset = (u64)-1; sk->max_transid = (u64)-1; /* just a big number, doesn't matter much */ sk->nr_items = 4096; while(1) { ret = ioctl(fd, BTRFS_IOC_TREE_SEARCH, &args); if (ret < 0) { fprintf(stderr, "ERROR: can't perform the search\n"); return ret; } /* the ioctl returns the number of item it found in nr_items */ if (sk->nr_items == 0) break; off = 0; for (i = 0; i < sk->nr_items; i++) { int j; sh = (struct btrfs_ioctl_search_header *)(args.buf + off); off += sizeof(*sh); if (sh->type == BTRFS_BLOCK_GROUP_ITEM_KEY) { struct btrfs_block_group_item *bg; if (im) { push_im(im, name, dir); im = NULL; print_bg(html, name, bgstart, bglen, bgused, bgflags, areas); } ++bgnum; bg = (struct btrfs_block_group_item *) (args.buf + off); bgflags = btrfs_block_group_flags(bg); bgused = btrfs_block_group_used(bg); printf("found block group %lld len %lld " "flags %lld\n", sh->objectid, sh->offset, bgflags); if (!(bgflags & flags)) { /* skip this block group */ sk->min_objectid = sh->objectid + sh->offset; sk->min_type = 0; sk->min_offset = 0; break; } im = gdImageCreate(width, (sh->offset / 4096 + 799) / width); black = gdImageColorAllocate(im, 0, 0, 0); for (j = 0; j < ARRAY_SIZE(colors); ++j) colors[j] = black; init_colors(im, colors); bgstart = sh->objectid; bglen = sh->offset; bgend = bgstart + bglen; snprintf(name, sizeof(name), "bg%d.png", bgnum); last_end = bgstart; if (saved_len) { px = (saved_extent - bgstart) / 4096; for (j = 0; j < saved_len / 4096; ++j) { int x = (px + j) % width; int y = (px + j) / width; gdImageSetPixel(im, x, y, saved_color); } last_end += saved_len; } areas = 0; saved_len = 0; } if (im && sh->type == BTRFS_EXTENT_ITEM_KEY) { int c; struct btrfs_extent_item *item; item = (struct btrfs_extent_item *) (args.buf + off); if (use_color) c = colors[get_color(item, sh->len)]; else c = black; if (sh->objectid > bgend) { printf("WARN: extent %lld is without " "block group\n", sh->objectid); goto skip; } if (sh->objectid == bgend) { saved_extent = sh->objectid; saved_len = sh->offset; saved_color = c; goto skip; } px = (sh->objectid - bgstart) / 4096; for (j = 0; j < sh->offset / 4096; ++j) { int x = (px + j) % width; int y = (px + j) / width; gdImageSetPixel(im, x, y, c); } if (sh->objectid != last_end) ++areas; last_end = sh->objectid + sh->offset; skip:; } off += sh->len; /* * record the mins in sk so we can make sure the * next search doesn't repeat this root */ sk->min_objectid = sh->objectid; sk->min_type = sh->type; sk->min_offset = sh->offset; } sk->nr_items = 4096; /* increment by one */ if (++sk->min_offset == 0) if (++sk->min_type == 0) if (++sk->min_objectid == 0) break; } if (im) { push_im(im, name, dir); print_bg(html, name, bgstart, bglen, bgused, bgflags, areas); } if (use_color) { fprintf(html, "

"); fprintf(html, "data - dark red, "); fprintf(html, "fs tree - black, "); fprintf(html, "extent tree - green, "); fprintf(html, "csum tree - blue, "); fprintf(html, "reloc tree - grey, "); fprintf(html, "other trees - red, "); fprintf(html, "unknown tree - dark grey"); fprintf(html, "

"); } fprintf(html, "\n"); return ret; } void usage(void) { printf("usage: btrfs-fragments [options] \n"); printf(" -c use color\n"); printf(" -d print data chunks\n"); printf(" -m print metadata chunks\n"); printf(" -s print system chunks\n"); printf(" (default is data+metadata)\n"); printf(" -o output directory, default is html\n"); exit(1); } int main(int argc, char **argv) { char *path; int fd; int ret; u64 flags = 0; char *dir = "html"; DIR *dirstream = NULL; while (1) { int c = getopt(argc, argv, "cmso:h"); if (c < 0) break; switch (c) { case 'c': use_color = 1; break; case 'd': flags |= BTRFS_BLOCK_GROUP_DATA; break; case 'm': flags |= BTRFS_BLOCK_GROUP_METADATA; break; case 's': flags |= BTRFS_BLOCK_GROUP_SYSTEM; break; case 'o': dir = optarg; break; case 'h': default: usage(); } } set_argv0(argv); argc = argc - optind; if (check_argc_min(argc, 1)) { usage(); exit(1); } path = argv[optind++]; fd = open_file_or_dir(path, &dirstream); if (fd < 0) { fprintf(stderr, "ERROR: can't access '%s'\n", path); exit(1); } if (flags == 0) flags = BTRFS_BLOCK_GROUP_DATA | BTRFS_BLOCK_GROUP_METADATA; ret = list_fragments(fd, flags, dir); close_file_or_dir(fd, dirstream); if (ret) exit(1); exit(0); }