| /* |
| * 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. |
| */ |
| |
| #include <stdio.h> |
| #include <stdlib.h> |
| #include <string.h> |
| #include <sys/ioctl.h> |
| #include <sys/types.h> |
| #include <dirent.h> |
| #include <sys/stat.h> |
| #include <unistd.h> |
| #include <fcntl.h> |
| #include <libgen.h> |
| #include <limits.h> |
| #include <uuid/uuid.h> |
| #include <ctype.h> |
| |
| #include <gd.h> |
| |
| #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, "<p>%s chunk starts at %lld, size is %s, %.2f%% used, " |
| "%.2f%% fragmented</p>\n", chunk_type(flags), start, |
| pretty_size(len), 100.0 * used / len, 100.0 * frag); |
| fprintf(html, "<img src=\"%s\" border=\"1\" />\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, "<html><header>\n"); |
| fprintf(html, "<title>Btrfs Block Group Allocation Map</title>\n"); |
| fprintf(html, "<style type=\"text/css\">\n"); |
| fprintf(html, "img {margin-left: 1em; margin-bottom: 2em;}\n"); |
| fprintf(html, "</style>\n"); |
| fprintf(html, "</header><body>\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"); |
| goto out_close; |
| } |
| /* 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, "<p>"); |
| 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, "</p>"); |
| } |
| fprintf(html, "</body></html>\n"); |
| |
| out_close: |
| fclose(html); |
| |
| return ret; |
| } |
| |
| void |
| usage(void) |
| { |
| printf("usage: btrfs-fragments [options] <path>\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 <dir> 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 = btrfs_open_dir(path, &dirstream, 1); |
| if (fd < 0) |
| 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); |
| } |