btrfs-progs: filesystem: add 'du' command
[platform/upstream/btrfs-progs.git] / cmds-fi-du.c
1 /*
2  * This program is free software; you can redistribute it and/or
3  * modify it under the terms of the GNU General Public
4  * License v2 as published by the Free Software Foundation.
5  *
6  * This program is distributed in the hope that it will be useful,
7  * but WITHOUT ANY WARRANTY; without even the implied warranty of
8  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
9  * General Public License for more details.
10  *
11  * You should have received a copy of the GNU General Public
12  * License along with this program; if not, write to the
13  * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
14  * Boston, MA 021110-1307, USA.
15  */
16
17 #include <sys/types.h>
18 #include <sys/stat.h>
19 #include <unistd.h>
20 #include <stdio.h>
21 #include <stdlib.h>
22 #include <string.h>
23 #include <stdarg.h>
24 #include <getopt.h>
25 #include <fcntl.h>
26 #include <dirent.h>
27
28 #include <sys/ioctl.h>
29 #include <linux/fs.h>
30 #include <linux/fiemap.h>
31
32 #include "utils.h"
33 #include "commands.h"
34 #include "kerncompat.h"
35 #include "rbtree.h"
36
37 static int summarize = 0;
38 static unsigned unit_mode = UNITS_RAW;
39 static char path[PATH_MAX] = { 0, };
40 static char *pathp = path;
41 static char *path_max = &path[PATH_MAX - 1];
42
43 /* Track which inodes we've seen for the purposes of hardlink detection. */
44 struct seen_inode {
45         struct rb_node  i_node;
46         u64             i_ino;
47         u64             i_subvol;
48 };
49 static struct rb_root seen_inodes = RB_ROOT;
50
51 static int cmp_si(struct seen_inode *si, u64 ino, u64 subvol)
52 {
53         if (ino < si->i_ino)
54                 return -1;
55         else if (ino > si->i_ino)
56                 return 1;
57         if (subvol < si->i_subvol)
58                 return -1;
59         else if (subvol > si->i_subvol)
60                 return 1;
61         return 0;
62 }
63
64 static int mark_inode_seen(u64 ino, u64 subvol)
65 {
66         int cmp;
67         struct rb_node **p = &seen_inodes.rb_node;
68         struct rb_node *parent = NULL;
69         struct seen_inode *si;
70
71         while (*p) {
72                 parent = *p;
73
74                 si = rb_entry(parent, struct seen_inode, i_node);
75                 cmp = cmp_si(si, ino, subvol);
76                 if (cmp < 0)
77                         p = &(*p)->rb_left;
78                 else if (cmp > 0)
79                         p = &(*p)->rb_right;
80                 else
81                         BUG();
82         }
83
84         si = calloc(1, sizeof(*si));
85         if (!si)
86                 return ENOMEM;
87
88         si->i_ino = ino;
89         si->i_subvol = subvol;
90
91         rb_link_node(&si->i_node, parent, p);
92         rb_insert_color(&si->i_node, &seen_inodes);
93
94         return 0;
95 }
96
97 static int inode_seen(u64 ino, u64 subvol)
98 {
99         int cmp;
100         struct rb_node *n = seen_inodes.rb_node;
101         struct seen_inode *si;
102
103         while (n) {
104                 si = rb_entry(n, struct seen_inode, i_node);
105
106                 cmp = cmp_si(si, ino, subvol);
107                 if (cmp < 0)
108                         n = n->rb_left;
109                 else if (cmp > 0)
110                         n = n->rb_right;
111                 else
112                         return EEXIST;
113         }
114         return 0;
115
116 }
117
118 const char * const cmd_filesystem_du_usage[] = {
119         "btrfs filesystem du [options] <path> [<path>..]",
120         "Summarize disk usage of each file.",
121         "-h|--human-readable",
122         "                   human friendly numbers, base 1024 (default)",
123         "-s                 display only a total for each argument",
124         NULL
125 };
126
127 /*
128  * Inline extents are skipped because they do not take data space,
129  * delalloc and unknown are skipped because we do not know how much
130  * space they will use yet.
131  */
132 #define SKIP_FLAGS      (FIEMAP_EXTENT_UNKNOWN|FIEMAP_EXTENT_DELALLOC|FIEMAP_EXTENT_DATA_INLINE)
133 static int du_calc_file_space(int dirfd, const char *filename,
134                               uint64_t *ret_total, uint64_t *ret_shared)
135 {
136         char buf[16384];
137         struct fiemap *fiemap = (struct fiemap *)buf;
138         struct fiemap_extent *fm_ext = &fiemap->fm_extents[0];
139         int count = (sizeof(buf) - sizeof(*fiemap)) /
140                         sizeof(struct fiemap_extent);
141         unsigned int i, ret;
142         int last = 0;
143         int rc;
144         u64 ext_len;
145         int fd;
146         u64 file_total = 0;
147         u64 file_shared = 0;
148         u32 flags;
149
150         memset(fiemap, 0, sizeof(struct fiemap));
151
152         fd = openat(dirfd, filename, O_RDONLY);
153         if (fd == -1) {
154                 ret = errno;
155                 fprintf(stderr, "ERROR: can't access '%s': %s\n",
156                         filename, strerror(ret));
157                 return ret;
158         }
159
160         do {
161                 fiemap->fm_length = ~0ULL;
162                 fiemap->fm_extent_count = count;
163                 rc = ioctl(fd, FS_IOC_FIEMAP, (unsigned long) fiemap);
164                 if (rc < 0) {
165                         ret = errno;
166                         goto out_close;
167                 }
168
169                 /* If 0 extents are returned, then more ioctls are not needed */
170                 if (fiemap->fm_mapped_extents == 0)
171                         break;
172
173                 for (i = 0; i < fiemap->fm_mapped_extents; i++) {
174                         ext_len = fm_ext[i].fe_length;
175                         flags = fm_ext[i].fe_flags;
176
177                         if (flags & FIEMAP_EXTENT_LAST)
178                                 last = 1;
179
180                         if (flags & SKIP_FLAGS)
181                                 continue;
182
183                         file_total += ext_len;
184                         if (flags & FIEMAP_EXTENT_SHARED)
185                                 file_shared += ext_len;
186                 }
187
188                 fiemap->fm_start = (fm_ext[i - 1].fe_logical +
189                                     fm_ext[i - 1].fe_length);
190         } while (last == 0);
191
192         *ret_total = file_total;
193         *ret_shared = file_shared;
194
195         ret = 0;
196 out_close:
197         close(fd);
198         return ret;
199 }
200
201 struct du_dir_ctxt {
202         uint64_t        bytes_total;
203         uint64_t        bytes_shared;
204 };
205
206 static int du_add_file(const char *filename, int dirfd, uint64_t *ret_total,
207                        uint64_t *ret_shared, int top_level);
208
209 static int du_walk_dir(struct du_dir_ctxt *ctxt)
210 {
211         int fd, ret, type;
212         DIR *dirstream = NULL;
213         struct dirent *entry;
214
215         fd = open_file_or_dir(path, &dirstream);
216         if (fd < 0)
217                 return fd;
218
219         ret = 0;
220         do {
221                 uint64_t tot, shr;
222
223                 errno = 0;
224                 entry = readdir(dirstream);
225                 if (entry) {
226                         if (strcmp(entry->d_name, ".") == 0
227                             || strcmp(entry->d_name, "..") == 0)
228                                 continue;
229
230                         type = entry->d_type;
231                         if (type == DT_REG || type == DT_DIR) {
232                                 tot = shr = 0;
233
234                                 ret = du_add_file(entry->d_name,
235                                                   dirfd(dirstream), &tot,
236                                                   &shr, 0);
237                                 if (ret)
238                                         break;
239
240                                 ctxt->bytes_total += tot;
241                                 ctxt->bytes_shared += shr;
242                         }
243                 }
244         } while (entry != NULL);
245
246         close_file_or_dir(fd, dirstream);
247         return ret;
248 }
249
250 static int du_add_file(const char *filename, int dirfd, uint64_t *ret_total,
251                        uint64_t *ret_shared, int top_level)
252 {
253         int ret, len = strlen(filename);
254         char *pathtmp;
255         struct stat st;
256         struct du_dir_ctxt dir;
257         uint64_t file_total = 0;
258         uint64_t file_shared = 0;
259         u64 subvol;
260         int fd;
261         DIR *dirstream = NULL;
262
263         ret = fstatat(dirfd, filename, &st, 0);
264         if (ret) {
265                 ret = errno;
266                 return ret;
267         }
268
269         if (!S_ISREG(st.st_mode) && !S_ISDIR(st.st_mode))
270                 return 0;
271
272         if (len > (path_max - pathp)) {
273                 fprintf(stderr, "ERROR: Path max exceeded: %s %s\n", path,
274                         filename);
275                 return ENAMETOOLONG;
276         }
277
278         pathtmp = pathp;
279         if (pathp == path)
280                 ret = sprintf(pathp, "%s", filename);
281         else
282                 ret = sprintf(pathp, "/%s", filename);
283         pathp += ret;
284
285         fd = open_file_or_dir(path, &dirstream);
286         if (fd < 0) {
287                 ret = fd;
288                 goto out;
289         }
290
291         ret = lookup_ino_rootid(fd, &subvol);
292         if (ret)
293                 goto out_close;
294
295         if (inode_seen(st.st_ino, subvol))
296                 goto out_close;
297
298         ret = mark_inode_seen(st.st_ino, subvol);
299         if (ret)
300                 goto out_close;
301
302         if (S_ISREG(st.st_mode)) {
303                 ret = du_calc_file_space(dirfd, filename, &file_total,
304                                          &file_shared);
305                 if (ret)
306                         goto out_close;
307         } else if (S_ISDIR(st.st_mode)) {
308                 memset(&dir, 0, sizeof(dir));
309
310                 ret = du_walk_dir(&dir);
311                 *pathp = '\0';
312                 if (ret)
313                         goto out_close;
314
315                 file_total = dir.bytes_total;
316                 file_shared = dir.bytes_shared;
317         }
318
319         if (!summarize || top_level) {
320                 printf("%s\t%s\t%s\n", pretty_size_mode(file_total, unit_mode),
321                        pretty_size_mode((file_total - file_shared), unit_mode),
322                        path);
323         }
324
325         if (ret_total)
326                 *ret_total = file_total;
327         if (ret_shared)
328                 *ret_shared = file_shared;
329
330 out_close:
331         close_file_or_dir(fd, dirstream);
332 out:
333         /* reset path to just before this element */
334         pathp = pathtmp;
335
336         return ret;
337 }
338
339 int cmd_filesystem_du(int argc, char **argv)
340 {
341         int ret = 0, error = 0;
342         int i;
343
344         optind = 1;
345         while (1) {
346                 int long_index;
347                 static const struct option long_options[] = {
348                         { "summarize", no_argument, NULL, 's'},
349                         { "human-readable", no_argument, NULL, 'h'},
350                         { NULL, 0, NULL, 0 }
351                 };
352                 int c = getopt_long(argc, argv, "sh", long_options,
353                                 &long_index);
354
355                 if (c < 0)
356                         break;
357                 switch (c) {
358                 case 'h':
359                         unit_mode = UNITS_HUMAN;
360                         break;
361                 case 's':
362                         summarize = 1;
363                         break;
364                 default:
365                         usage(cmd_filesystem_du_usage);
366                 }
367         }
368
369         if (check_argc_min(argc - optind, 1))
370                 usage(cmd_filesystem_du_usage);
371
372         printf("total\texclusive\tfilename\n");
373
374         for (i = optind; i < argc; i++) {
375                 ret = du_add_file(argv[i], AT_FDCWD, NULL, NULL, 1);
376                 if (ret) {
377                         fprintf(stderr, "ERROR: can't check space of '%s': %s\n",
378                                 argv[i], strerror(ret));
379                         error = 1;
380                 }
381         }
382
383         return error;
384 }