Handle hostnames with upper-case letters
[webmin.git] / logrotate / logrotate-lib.pl
1 # logrotate-lib.pl
2 # Common functions for parsing the logrotate configuration file
3
4 BEGIN { push(@INC, ".."); };
5 use WebminCore;
6 &init_config();
7
8 if (open(VERSION, "$module_config_directory/version")) {
9         chop($logrotate_version = <VERSION>);
10         close(VERSION);
11         }
12
13 # Use sample config if it exists but real config doesn't yet
14 if (!-r $config{'logrotate_conf'} && -r $config{'sample_conf'}) {
15         &copy_source_dest($config{'sample_conf'}, $config{'logrotate_conf'});
16         }
17
18 sub get_config_parent
19 {
20 if (!$get_config_parent_cache) {
21         local ($conf, $lines) = &get_config();
22         $get_config_parent_cache = { 'members' => $conf,
23                                      'file' => $config{'logrotate_conf'},
24                                      'line' => 0,
25                                      'eline' => $lines,
26                                      'global' => 1 };
27         }
28 return $get_config_parent_cache;
29 }
30
31 # get_config([file])
32 # Returns a list of logrotate config file entries
33 sub get_config
34 {
35 local $file = $_[0] || $config{'logrotate_conf'};
36 if (!$_[0] && $get_config_cache{$file}) {
37         return wantarray ? ( $get_config_cache{$file},
38                              $get_config_lnum_cache{$file},
39                              $get_config_files_cache{$file} )
40                          : $get_config_cache{$file};
41         }
42 local @files = ( $file );
43 local @rv;
44 local $addto = \@rv;
45 local $section = undef;
46 local $lnum = 0;
47 local $fh = "FILE".$file_count++;
48 open($fh, $file);
49 while(<$fh>) {
50         s/\r|\n//g;
51         s/#.*$//;
52         if (/^\s*(.*){\s*$/) {
53                 # Start of a section
54                 push(@name, &split_words($1));
55                 $section = { 'name' => [ @name ],
56                              'members' => [ ],
57                              'index' => scalar(@$addto),
58                              'line' => defined($namestart) ? $namestart : $lnum,
59                              'eline' => $lnum,
60                              'file' => $file };
61                 push(@$addto, $section);
62                 $addto = $section->{'members'};
63                 @name = ( );
64                 $namestart = undef;
65                 }
66         elsif ((/^\s*\// || /^\s*"\//) && !$section) {
67                 # A path before a section
68                 $namestart = $lnum if (!@name);
69                 push(@name, &split_words($_));
70                 }
71         elsif (/^\s*}\s*$/) {
72                 # End of a section
73                 $addto = \@rv;
74                 $section->{'eline'} = $lnum;
75                 $section = undef;
76                 }
77         elsif (/^\s*include\s+(.*)$/i) {
78                 # Including other directives files
79                 local $incfile = $1;
80                 if (-d $incfile) {
81                         # Multiple files!
82                         local $f;
83                         opendir(DIR, $incfile);
84                         local @dirs = sort { $a cmp $b } readdir(DIR);
85                         closedir(DIR);
86                         foreach $f (@dirs) {
87                                 next if ($f =~ /^\./ ||
88                                          $f =~ /\.rpm(save|orig|new)$/ ||
89                                          $f =~ /\~$/ ||
90                                          $f =~ /,v$/ ||
91                                          $f =~ /\.swp$/ ||
92                                          $f =~ /\.lock$/);
93                                 local ($inc, $ilnum, $ifiles) =
94                                         &get_config("$incfile/$f");
95                                 push(@files, @$ifiles);
96                                 map { $_->{'index'} += @$addto } @$inc;
97                                 push(@$addto, @$inc);
98                                 }
99                         closedir(DIR);
100                         }
101                 else {
102                         # A single file
103                         local ($inc, $ilnum, $ifiles) = &get_config($incfile);
104                         push(@files, @$ifiles);
105                         map { $_->{'index'} += @$addto } @$inc;
106                         push(@$addto, @$inc);
107                         }
108                 }
109         elsif (/^\s*(\S+)\s*(.*)$/) {
110                 # Single directive
111                 local $dir =  { 'name' => $1,
112                                 'value' => $2,
113                                 'index' => scalar(@$addto),
114                                 'line' => $lnum,
115                                 'eline' => $lnum,
116                                 'file' => $file };
117                 push(@$addto, $dir);
118                 if ($1 eq 'postrotate' || $1 eq 'prerotate') {
119                         # Followed by a multi-line script!
120                         while(<$fh>) {
121                                 $lnum++;
122                                 s/\r|\n//g;
123                                 last if (/^\s*(endscript|endrotate)\s*$/);
124                                 s/^\s+//;
125                                 $dir->{'script'} .= $_."\n";
126                                 }
127                         $dir->{'eline'} = $lnum;
128                         }
129                 }
130         $lnum++;
131         }
132 close($fh);
133 if (!$_[0]) {
134         $get_config_cache{$file} = \@rv;
135         $get_config_lnum_cache{$file} = $lnum;
136         $get_config_files_cache{$file} = \@files;
137         }
138 return wantarray ? (\@rv, $lnum, \@files) : \@rv;
139 }
140
141 sub split_words
142 {
143 local @rv;
144 local $str = $_[0];
145 while($str =~ /^\s*"(.*)"(.*)$/ || $str =~ /^\s*(\S+)(.*)$/) {
146         push(@rv, $1);
147         $str = $2;
148         }
149 return @rv;
150 }
151
152 sub join_words
153 {
154 return join(" ", map { /\s/ ? "\"$_\"" : $_ } @_);
155 }
156
157 # find(name, &config)
158 sub find
159 {
160 local @rv = grep { lc($_->{'name'}) eq lc($_[0]) } @{$_[1]};
161 return wantarray ? @rv : $rv[0];
162 }
163
164 # find_value(name, &config)
165 sub find_value
166 {
167 local @rv = map { defined($_->{'script'}) ? $_->{'script'} : $_->{'value'} }
168                 grep { lc($_->{'name'}) eq lc($_[0]) } @{$_[1]};
169 return wantarray ? @rv : $rv[0];
170 }
171
172 # get_logrotate_version(&out)
173 sub get_logrotate_version
174 {
175 local $out = &backquote_command("$config{'logrotate'} -v 2>&1", 1);
176 ${$_[0]} = $out if ($_[0]);
177 return $out =~ /logrotate\s+([0-9\.]+)\s/ ||
178        $out =~ /logrotate\-([0-9\.]+)\s/ ? $1 : undef;
179 }
180
181 # get_period(&conf)
182 sub get_period
183 {
184 foreach $p ("daily", "weekly", "monthly") {
185         local $ex = &find($p, $_[0]);
186         return $p if ($ex);
187         }
188 return undef;
189 }
190
191 # save_directive(&parent, &old|name, &new, [indent])
192 sub save_directive
193 {
194 local $conf = $_[0]->{'members'};
195 local $old = !defined($_[1]) ? undef : ref($_[1]) ? $_[1] : &find($_[1], $conf);
196 local $lref = &read_file_lines($old ? $old->{'file'} : $_[0]->{'file'});
197 local $new = !defined($_[2]) ? undef : ref($_[2]) ? $_[2] :
198                         { 'name' => $old ? $old->{'name'} : $_[1],
199                           'value' => $_[2] };
200 local @lines = &directive_lines($new, $_[3]) if ($new);
201 local $gparent = &get_config_parent();
202 if ($old && $new) {
203         # Update
204         local $oldlines = $old->{'eline'} - $old->{'line'} + 1;
205         splice(@$lref, $old->{'line'}, $oldlines, @lines);
206         $new->{'line'} = $old->{'line'};
207         $new->{'index'} = $old->{'index'};
208         $new->{'file'} = $old->{'file'};
209         $new->{'eline'} = $new->{'line'} + scalar(@lines) - 1;
210         %$old = %{$new};
211         &renumber($gparent, $old->{'file'}, $old->{'eline'},
212                   scalar(@lines) - $oldlines, $old);
213         }
214 elsif ($old && !$new) {
215         # Delete
216         local $oldlines = $old->{'eline'} - $old->{'line'} + 1;
217         splice(@$lref, $old->{'line'}, $old->{'eline'} - $old->{'line'} + 1);
218         splice(@$conf, $old->{'index'}, 1);
219         &renumber($gparent, $old->{'file'}, $old->{'line'}, -$oldlines);
220         }
221 elsif (!$old && $new && $_[0]->{'global'} && !$new->{'members'}) {
222         # Add at the start of the file
223         if (defined($_[0]->{'line'})) {
224                 splice(@$lref, 0, 0, @lines);
225                 $new->{'line'} = 0;
226                 $new->{'eline'} = $new->{'line'} + scalar(@lines) - 1;
227                 $new->{'file'} = $_[0]->{'file'};
228                 &renumber($gparent, $new->{'file'}, $new->{'line'}-1, scalar(@lines));
229                 }
230         $new->{'index'} = 0;
231         splice(@$conf, 0, 0, $new);
232         }
233 elsif (!$old && $new) {
234         # Add (to end of section)
235         if (defined($_[0]->{'line'})) {
236                 if (!$new->{'file'} || $_[0]->{'file'} eq $new->{'file'}) {
237                         # Adding to parent file
238                         splice(@$lref, $_[0]->{'eline'}, 0, @lines);
239                         $new->{'line'} = $_[0]->{'eline'};
240                         $new->{'eline'} = $new->{'line'} + scalar(@lines) - 1;
241                         $new->{'file'} = $_[0]->{'file'};
242                         &renumber($gparent, $new->{'file'}, $new->{'line'}-1, scalar(@lines));
243                         }
244                 else {
245                         # Adding to another file
246                         local $lref2 = &read_file_lines($new->{'file'});
247                         $new->{'line'} = scalar(@$lref2);
248                         $new->{'eline'} = $new->{'line'} + scalar(@lines) - 1;
249                         push(@$lref2, @lines);
250                         }
251                 }
252         $new->{'index'} = scalar(@$conf);
253         push(@$conf, $new);
254         }
255 }
256
257 # renumber(&object, file, startline, count, [&skip])
258 sub renumber
259 {
260 return if (!$_[3]);
261 if ($_[0]->{'file'} eq $_[1] && $_[0] ne $_[4]) {
262         $_[0]->{'line'} += $_[3] if ($_[0]->{'line'} > $_[2]);
263         $_[0]->{'eline'} += $_[3] if ($_[0]->{'eline'} > $_[2]);
264         }
265 if ($_[0]->{'members'}) {
266         local $c;
267         foreach $c (@{$_[0]->{'members'}}) {
268                 &renumber($c, $_[1], $_[2], $_[3], $_[4]);
269                 }
270         }
271 }
272
273 # directive_lines(&dir, indent)
274 sub directive_lines
275 {
276 local @rv;
277 if ($_[0]->{'members'}) {
278         push(@rv, $_[1].&join_words(@{$_[0]->{'name'}})." {");
279         foreach $m (@{$_[0]->{'members'}}) {
280                 push(@rv, &directive_lines($m, $_[1]."\t"));
281                 }
282         push(@rv, $_[1]."}");
283         }
284 elsif ($_[0]->{'script'}) {
285         push(@rv, $_[1].$_[0]->{'name'});
286         foreach $s (split(/\n/, $_[0]->{'script'})) {
287                 push(@rv, $_[1].$s);
288                 }
289         push(@rv, $_[1]."endscript");
290         }
291 else {
292         push(@rv, $_[1].$_[0]->{'name'}.
293                   ($_[0]->{'value'} eq "" ? "" : " ".$_[0]->{'value'}));
294         }
295 return @rv;
296 }
297
298 # delete_if_empty(file)
299 sub delete_if_empty
300 {
301 local $conf = &get_config();
302 local %files = map { $_, 1 } &unique(map { $_->{'file'} } @$conf);
303 &unlink_file($_[0]) if (!$files{$_[0]});
304 }
305
306 %global_default = ( "nocompress" => "",
307                     "compress" => undef,
308                     "nodelaycompress" => "",
309                     "delaycompress" => undef,
310                     "ifempty" => "",
311                     "notifempty" => undef,
312                     "nocopytruncate" => "",
313                     "copytruncate" => undef,
314                     "nomissingok" => "",
315                     "missingok" => undef,
316                     "rotate" => 0,
317                     "create" => "",
318                     "nocreate" => undef,
319                     "noolddir" => "",
320                     "olddir" => undef,
321                     "ext" => undef,
322                     "mail" => undef,
323                     "nomail" => "",
324                     "maillast" => "",
325                     "mailfirst" => undef,
326                     "errors" => undef,
327                     "postrotate" => undef,
328                     "prerotate" => undef,
329                     "errors" => undef,
330                   );
331
332 # rotate_log_now(&log)
333 # Call logrotate on a config fragment file to rotate just one set of logs
334 # immediately.
335 sub rotate_log_now
336 {
337 local $conf = &get_config();
338 local $temp = &transname();
339 open(TEMP, ">$temp");
340 local $c;
341 foreach $c (@$conf) {
342         if (!$c->{'members'}) {
343                 print TEMP map { "$_\n" } &directive_lines($c);
344                 }
345         }
346 print TEMP map { "$_\n" } &directive_lines($_[0]);
347 close(TEMP);
348 local $out = &backquote_logged("$config{'logrotate'} -f $temp 2>&1");
349 return ($?, $out);
350 }
351
352 # get_add_file()
353 # Returns the file to which new logrotate sections should be added
354 sub get_add_file
355 {
356 if ($config{'add_file'}) {
357         # Make sure file is valid
358         local ($conf, $lnum, $files) = &get_config();
359         if (&indexof($config{'add_file'}, @$files) >= 0) {
360                 return $config{'add_file'};
361                 }
362         }
363 return $config{'logrotate_conf'};
364 }
365
366 1;
367