About time I put a licence on this. MIT as usual, naturally.
[sgt/agedu] / du.c
CommitLineData
70322ae3 1/*
2 * du.c: implementation of du.h.
3 */
4
5#define _GNU_SOURCE
6#include <features.h>
7
8#include <stdio.h>
9#include <string.h>
10#include <stdlib.h>
11#include <errno.h>
12
13#include <sys/types.h>
14#include <sys/stat.h>
15#include <unistd.h>
16
17#include "du.h"
18#include "malloc.h"
19
20#ifdef __linux__
21#define __KERNEL__
22#include <unistd.h>
23#include <fcntl.h>
24#include <linux/types.h>
25#include <linux/dirent.h>
26#include <linux/unistd.h>
27typedef int dirhandle;
28typedef struct {
29 struct dirent data[32];
30 struct dirent *curr;
31 int pos, endpos;
32} direntry;
33_syscall3(int, getdents, uint, fd, struct dirent *, dirp, uint, count)
34#define OPENDIR(f) open(f, O_RDONLY | O_NOATIME | O_DIRECTORY)
35#define DIRVALID(dh) ((dh) >= 0)
36#define READDIR(dh,de) ((de).curr = (de).data, (de).pos = 0, \
37 ((de).endpos = getdents((dh), (de).data, sizeof((de).data))) > 0)
38#define DENAME(de) ((de).curr->d_name)
39#define DEDONE(de) ((de).pos >= (de).endpos)
40#define DEADVANCE(de) ((de).pos += (de).curr->d_reclen, \
41 (de).curr = (struct dirent *)((char *)(de).data + (de).pos))
42#define CLOSEDIR(dh) close(dh)
43#else
44#include <dirent.h>
45typedef DIR *dirhandle;
46typedef struct dirent *direntry;
47#define OPENDIR(f) opendir(f)
48#define DIRVALID(dh) ((dh) != NULL)
49#define READDIR(dh,de) (((de) = readdir(dh)) ? 1 : 0)
50#define DENAME(de) ((de)->d_name)
51#define DEDONE(de) ((de) == NULL)
52#define DEADVANCE(de) ((de) = NULL)
53#define CLOSEDIR(dh) closedir(dh)
54#endif
55
56static int str_cmp(const void *av, const void *bv)
57{
58 return strcmp(*(const char **)av, *(const char **)bv);
59}
60
61static void du_recurse(char **path, size_t pathlen, size_t *pathsize,
62 gotdata_fn_t gotdata, void *gotdata_ctx)
63{
64 direntry de;
65 dirhandle d;
66 struct stat64 st;
67 char **names;
68 size_t i, nnames, namesize;
69
70 if (lstat64(*path, &st) < 0) {
71 fprintf(stderr, "%s: lstat: %s\n", *path, strerror(errno));
72 return;
73 }
74
75 if (!gotdata(gotdata_ctx, *path, &st))
76 return;
77
78 if (!S_ISDIR(st.st_mode))
79 return;
80
81 names = NULL;
82 nnames = namesize = 0;
83
84 d = OPENDIR(*path);
85 if (!DIRVALID(d)) {
86 fprintf(stderr, "%s: opendir: %s\n", *path, strerror(errno));
87 return;
88 }
89 while (READDIR(d, de)) {
90 do {
91 const char *name = DENAME(de);
92 if (name[0] == '.' && (!name[1] || (name[1] == '.' && !name[2]))) {
93 /* do nothing - we skip "." and ".." */
94 } else {
95 if (nnames >= namesize) {
96 namesize = nnames * 3 / 2 + 64;
97 names = sresize(names, namesize, char *);
98 }
99 names[nnames++] = dupstr(name);
100 }
101 DEADVANCE(de);
102 } while (!DEDONE(de));
103 }
104 CLOSEDIR(d);
105
106 if (nnames == 0)
107 return;
108
109 qsort(names, nnames, sizeof(*names), str_cmp);
110
111 for (i = 0; i < nnames; i++) {
112 size_t newpathlen = pathlen + 1 + strlen(names[i]);
113 if (*pathsize <= newpathlen) {
114 *pathsize = newpathlen * 3 / 2 + 256;
115 *path = sresize(*path, *pathsize, char);
116 }
256c29a2 117 /*
118 * Avoid duplicating a slash if we got a trailing one to
119 * begin with (i.e. if we're starting the scan in '/' itself).
120 */
121 if (pathlen > 0 && (*path)[pathlen-1] == '/') {
122 strcpy(*path + pathlen, names[i]);
123 newpathlen--;
124 } else {
125 sprintf(*path + pathlen, "/%s", names[i]);
126 }
70322ae3 127
128 du_recurse(path, newpathlen, pathsize, gotdata, gotdata_ctx);
129
130 sfree(names[i]);
131 }
132 sfree(names);
133}
134
444c684c 135void du(const char *inpath, gotdata_fn_t gotdata, void *gotdata_ctx)
70322ae3 136{
137 char *path;
138 size_t pathlen, pathsize;
139
140 pathlen = strlen(inpath);
141 pathsize = pathlen + 256;
142 path = snewn(pathsize, char);
143 strcpy(path, inpath);
144
145 du_recurse(&path, pathlen, &pathsize, gotdata, gotdata_ctx);
146}