Handle hostnames with upper-case letters
[webmin.git] / disk-usage / disk-usage-lib.pl
1 # Functions for getting usage
2
3 do '../web-lib.pl';
4 &init_config();
5 do '../ui-lib.pl';
6
7 $usage_tree_file = "$module_config_directory/tree";
8 $heiropen_file = "$module_config_directory/heiropen";
9 $cron_cmd = "$module_config_directory/usage.pl";
10
11 # build_root_usage_tree(&dirs)
12 # Returns a usage tree from / containing all the specified directories
13 sub build_root_usage_tree
14 {
15 local $root = { 'dir' => '/',
16                 'total' => 0,
17                 'files' => 0 };
18 foreach my $dir (@{$_[0]}) {
19         # No need to do a directory that has already been done by a parent
20         local $already = &find_in_tree($root, $dir);
21         next if ($already && $dir ne "/");
22
23         local $tree = &build_usage_tree($dir);
24         if ($dir eq "/") {
25                 $root = $tree;
26                 }
27         else {
28                 # Insert into root at correct location
29                 while(1) {
30                         $tree->{'dir'} =~ /^(.*)\/(.*)$/;
31                         local $pdir = $1 || "/";
32                         local $file = $2;
33                         local $par = &find_in_tree($root, $pdir);
34                         if ($par) {
35                                 # Found a parent .. link to it
36                                 push(@{$par->{'subs'}}, $tree);
37                                 $tree->{'parent'} = $parent;
38
39                                 # Increase the totals for all parents
40                                 while($par) {
41                                         $par->{'total'} += $tree->{'total'};
42                                         $par = $par->{'parent'};
43                                         }
44                                 last;
45                                 }
46                         else {
47                                 # Need to make up a parent
48                                 $par = { 'dir' => $pdir, 'subs' => [ $tree ],
49                                          'total' => $tree->{'total'},
50                                          'files' => 0 };
51                                 $tree->{'parent'} = $par;
52                                 $tree = $par;
53                                 }
54                         }
55                 }
56         }
57 return $root;
58 }
59
60 # build_usage_tree(dir)
61 # Given a base directory, returns a structure containing details about it and
62 # all sub-directories
63 sub build_usage_tree
64 {
65 local ($dir) = @_;
66 local ($total, $files) = (0, 0);
67 opendir(DIR, $dir);
68 local @files = readdir(DIR);
69 closedir(DIR);
70 local $rv = { 'dir' => $dir, 'subs' => [ ] };
71 local @pst = stat($dir);
72
73 local $skip = &get_skip_dirs();
74 foreach my $f (@files) {
75         next if ($f eq "." || $f eq "..");
76         local $path = $dir eq "/" ? "/$f" : "$dir/$f";
77         next if ($skip->{$path});
78         local @st = lstat($path);
79         if ($config{'bsize'}) {
80                 $total += $st[12]*$config{'bsize'};
81                 $files += $st[12]*$config{'bsize'};
82                 }
83         else {
84                 $total += $st[7];
85                 $files += $st[7];
86                 }
87         if ($config{'xdev'} && $st[0] != $pst[0]) {
88                 next;   # Don't go to another filesystem
89                 }
90         if (-d _ && !-l _) {
91                 # A directory .. recurse into it
92                 local $subdir = &build_usage_tree($path, $rv);
93                 $subdir->{'parent'} = $rv;
94                 $total += $subdir->{'total'};
95                 push(@{$rv->{'subs'}}, $subdir);
96                 }
97         }
98 $rv->{'total'} = $total;
99 $rv->{'files'} = $files;
100 return $rv;
101 }
102
103 # get_usage_tree()
104 sub get_usage_tree
105 {
106 local (%tree, %pmap);
107 &read_file($usage_tree_file, \%tree) || return undef;
108 foreach my $k (keys %tree) {
109         if ($k ne "/" && $k =~ /^(.*)\/(.*)$/) {
110                 local $dir = $1 || "/";
111                 local $file = $2;
112                 push(@{$pmap{$dir}}, $k);
113                 }
114         }
115 return &hash_to_tree($tree{'root'}, \%tree, \%pmap);
116 }
117
118 # hash_to_tree(dir, &hash, &parentmap)
119 sub hash_to_tree
120 {
121 local ($dir, $hash, $pmap) = @_;
122 local $rv = { 'dir' => $dir, 'subs' => [ ] };
123 ($rv->{'total'}, $rv->{'files'}) = split(/ /, $hash->{$dir});
124 foreach my $subdir (@{$pmap->{$dir}}) {
125         local $substr = &hash_to_tree($subdir, $hash, $pmap);
126         $substr->{'parent'} = $rv;
127         push(@{$rv->{'subs'}}, $substr);
128         }
129 return $rv;
130 }
131
132 # save_usage_tree(&tree)
133 sub save_usage_tree
134 {
135 local ($dir) = @_;
136 local %tree;
137 &tree_to_hash($dir, \%tree);
138 $tree{'root'} = $dir->{'dir'};
139 &write_file($usage_tree_file, \%tree);
140 }
141
142 # tree_to_hash(&dir, &hash)
143 # Adds to the hash entries for some tree node and sub-nodes
144 sub tree_to_hash
145 {
146 local ($dir, $hash) = @_;
147 $hash->{$dir->{'dir'}} = $dir->{'total'}." ".$dir->{'files'};
148 foreach my $subdir (@{$dir->{'subs'}}) {
149         &tree_to_hash($subdir, $hash);
150         }
151 }
152
153 # find_in_tree(&tree, dir)
154 # Returns the node for some directory, or undef
155 sub find_in_tree
156 {
157 local ($tree, $dir) = @_;
158 return $tree if ($tree->{'dir'} eq $dir);
159 if ($tree->{'dir'} eq "/" ||
160     $dir =~ /^$tree->{'dir'}\//) {
161         foreach my $subdir (@{$tree->{'subs'}}) {
162                 local $found = &find_in_tree($subdir, $dir);
163                 return $found if ($found);
164                 }
165         }
166 return undef;
167 }
168
169 # get_heiropen()
170 # Returns an array of open categories
171 sub get_heiropen
172 {
173 open(HEIROPEN, $heiropen_file);
174 local @heiropen = <HEIROPEN>;
175 chop(@heiropen);
176 close(HEIROPEN);
177 return @heiropen;
178 }
179
180 # save_heiropen(&heir)
181 sub save_heiropen
182 {
183 &open_tempfile(HEIR, ">$heiropen_file");
184 foreach $h (@{$_[0]}) {
185         &print_tempfile(HEIR, $h,"\n");
186         }
187 &close_tempfile(HEIR);
188 }
189
190 sub find_cron_job
191 {
192 local @jobs = &cron::list_cron_jobs();
193 local ($job) = grep { $_->{'user'} eq 'root' &&
194                       $_->{'command'} eq $cron_cmd } @jobs;
195 return $job;
196 }
197
198 # get_skip_dirs()
199 # Returns a hash reference of directories to skip, based on the skip list
200 # and filesystems
201 sub get_skip_dirs
202 {
203 if (!%skip_cache) {
204         %skip_cache = map { $_, 1 } split(/\t+/, $config{'skip'});
205         if (&foreign_check("mount")) {
206                 &foreign_require("mount", "mount-lib.pl");
207                 local %fsskip = map { $_, 1 } split(/\s+/, $config{'fs'});
208                 foreach my $m (&mount::list_mounted()) {
209                         if ($fsskip{$m->[2]}) {
210                                 $skip_cache{$m->[0]} = 1;
211                                 }
212                         }
213                 }
214         }
215 return \%skip_cache;
216 }
217
218 1;
219