--- /dev/null
+/*
+ * 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 <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_sizes(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;
+ u64 saved_flags = 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 white = 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");
+ 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);
+
+ white = gdImageColorAllocate(im, 255, 255, 255);
+ black = gdImageColorAllocate(im, 0, 0, 0);
+
+ for (j = 0; j < 10; ++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) {
+ u64 e_flags;
+ int c;
+ struct btrfs_extent_item *item;
+
+ item = (struct btrfs_extent_item *)
+ (args.buf + off);
+ e_flags = btrfs_stack_extent_flags(item);
+
+ 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_flags = e_flags;
+ 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");
+
+ 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";
+
+ 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();
+ }
+ }
+
+ if (optind < argc) {
+ path = argv[optind++];
+ } else {
+ usage();
+ exit(1);
+ }
+
+ fd = open_file_or_dir(path);
+ 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);
+ if (ret)
+ exit(1);
+
+ exit(0);
+}