xref: /openbsd/usr.sbin/pkg_add/OpenBSD/Vstat.pm (revision 771fbea0)
1# ex:ts=8 sw=4:
2# $OpenBSD: Vstat.pm,v 1.69 2017/10/22 08:55:22 espie Exp $
3#
4# Copyright (c) 2003-2007 Marc Espie <espie@openbsd.org>
5#
6# Permission to use, copy, modify, and distribute this software for any
7# purpose with or without fee is hereby granted, provided that the above
8# copyright notice and this permission notice appear in all copies.
9#
10# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
11# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
12# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
13# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
14# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
15# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
16# OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
17
18# Provides stat and statfs-like functions for package handling.
19
20# allows user to add/remove files.
21
22# uses mount and df directly for now.
23
24use strict;
25use warnings;
26
27{
28package OpenBSD::Vstat::Object;
29my $cache = {};
30my $x = undef;
31my $dummy = bless \$x, __PACKAGE__;
32
33sub new
34{
35	my ($class, $value) = @_;
36	if (!defined $value) {
37		return $dummy;
38	}
39	if (!defined $cache->{$value}) {
40		$cache->{$value} = bless \$value, $class;
41	}
42	return $cache->{$value};
43}
44
45sub exists
46{
47	return 1;
48}
49
50sub value
51{
52	my $self = shift;
53	return $$self;
54}
55
56sub none
57{
58	return OpenBSD::Vstat::Object::None->new;
59}
60
61}
62
63{
64package OpenBSD::Vstat::Object::None;
65our @ISA = qw(OpenBSD::Vstat::Object);
66
67my $x = undef;
68my $none = bless \$x, __PACKAGE__;
69
70sub exists
71{
72	return 0;
73}
74
75sub new
76{
77	return $none;
78}
79}
80
81{
82package OpenBSD::Vstat::Object::Directory;
83our @ISA = qw(OpenBSD::Vstat::Object);
84
85sub new
86{
87	my ($class, $fname, $set, $o) = @_;
88	bless { name => $fname, set => $set, o => $o }, $class;
89}
90
91# XXX directories don't do anything until you test for their presence.
92# which only happens if you want to replace a directory with a file.
93sub exists
94{
95	my $self = shift;
96	require OpenBSD::SharedItems;
97
98	return OpenBSD::SharedItems::check_shared($self->{set}, $self->{o});
99}
100
101}
102
103package OpenBSD::Vstat;
104use File::Basename;
105use OpenBSD::Paths;
106
107sub stat
108{
109	my ($self, $fname) = @_;
110	my $dev = (stat $fname)[0];
111
112	if (!defined $dev && $fname ne '/') {
113		return $self->stat(dirname($fname));
114	}
115	return OpenBSD::Mounts->find($dev, $fname, $self->{state});
116}
117
118sub account_for
119{
120	my ($self, $name, $size) = @_;
121	my $e = $self->stat($name);
122	$e->{used} += $size;
123	return $e;
124}
125
126sub account_later
127{
128	my ($self, $name, $size) = @_;
129	my $e = $self->stat($name);
130	$e->{delayed} += $size;
131	return $e;
132}
133
134sub new
135{
136	my ($class, $state) = @_;
137
138	bless {v => [{}], state => $state}, $class;
139}
140
141sub exists
142{
143	my ($self, $name) = @_;
144	for my $v (@{$self->{v}}) {
145		if (defined $v->{$name}) {
146			return $v->{$name}->exists;
147		}
148	}
149	return -e $name;
150}
151
152sub value
153{
154	my ($self, $name) = @_;
155	for my $v (@{$self->{v}}) {
156		if (defined $v->{$name}) {
157			return $v->{$name}->value;
158		}
159	}
160	return undef;
161}
162
163sub synchronize
164{
165	my $self = shift;
166
167	OpenBSD::Mounts->synchronize;
168	if ($self->{state}->{not}) {
169		# this is the actual stacking case: in pretend mode,
170		# I have to put a second vfs on top
171		if (@{$self->{v}} == 2) {
172			my $top = shift @{$self->{v}};
173			while (my ($k, $v) = each %$top) {
174				$self->{v}[0]{$k} = $v;
175			}
176		}
177		unshift(@{$self->{v}}, {});
178	} else {
179		$self->{v} = [{}];
180	}
181}
182
183sub drop_changes
184{
185	my $self = shift;
186
187	OpenBSD::Mounts->drop_changes;
188	# drop the top layer
189	$self->{v}[0] = {};
190}
191
192sub add
193{
194	my ($self, $name, $size, $value) = @_;
195	$self->{v}[0]->{$name} = OpenBSD::Vstat::Object->new($value);
196	return defined($size) ? $self->account_for($name, $size) : undef;
197}
198
199sub remove
200{
201	my ($self, $name, $size) = @_;
202	$self->{v}[0]->{$name} = OpenBSD::Vstat::Object->none;
203	return defined($size) ? $self->account_later($name, -$size) : undef;
204}
205
206sub remove_first
207{
208	my ($self, $name, $size) = @_;
209	$self->{v}[0]->{$name} = OpenBSD::Vstat::Object->none;
210	return defined($size) ? $self->account_for($name, -$size) : undef;
211}
212
213# since directories may become files during updates, we may have to remove
214# them early, so we need to record them: store exactly as much info as needed
215# for SharedItems.
216sub remove_directory
217{
218	my ($self, $name, $o) = @_;
219	$self->{v}[0]->{$name} = OpenBSD::Vstat::Object::Directory->new($name,
220	    $self->{state}->{current_set}, $o);
221}
222
223
224sub tally
225{
226	my $self = shift;
227
228	OpenBSD::Mounts->tally($self->{state});
229}
230
231package OpenBSD::Mounts;
232
233my $devinfo;
234my $devinfo2;
235my $giveup;
236
237sub giveup
238{
239	if (!defined $giveup) {
240		$giveup = OpenBSD::MountPoint::Fail->new;
241	}
242	return $giveup;
243}
244
245sub new
246{
247	my ($class, $dev, $mp, $opts) = @_;
248
249	if (!defined $devinfo->{$dev}) {
250		$devinfo->{$dev} = OpenBSD::MountPoint->new($dev, $mp, $opts);
251	}
252	return $devinfo->{$dev};
253}
254
255sub run
256{
257	my $state = shift;
258	my $code = pop;
259	open(my $cmd, "-|", @_) or
260		$state->errsay("Can't run #1", join(' ', @_))
261		and return;
262	while (<$cmd>) {
263		&$code($_);
264	}
265	if (!close($cmd)) {
266		if ($!) {
267			$state->errsay("Error running #1: #2", $!, join(' ', @_));
268		} else {
269			$state->errsay("Exit status #1 from #2", $?, join(' ', @_));
270		}
271	}
272}
273
274sub ask_mount
275{
276	my ($class, $state) = @_;
277
278	delete $ENV{'BLOCKSIZE'};
279	run($state, OpenBSD::Paths->mount, sub {
280		my $l = shift;
281		chomp $l;
282		if ($l =~ m/^(.*?)\s+on\s+(\/.*?)\s+type\s+.*?(?:\s+\((.*?)\))?$/o) {
283			my ($dev, $mp, $opts) = ($1, $2, $3);
284			$class->new($dev, $mp, $opts);
285		} else {
286			$state->errsay("Can't parse mount line: #1", $l);
287		}
288	});
289}
290
291sub ask_df
292{
293	my ($class, $fname, $state) = @_;
294
295	my $info = $class->giveup;
296	my $blocksize = 512;
297
298	$class->ask_mount($state) if !defined $devinfo;
299	run($state, OpenBSD::Paths->df, "--", $fname, sub {
300		my $l = shift;
301		chomp $l;
302		if ($l =~ m/^Filesystem\s+(\d+)\-blocks/o) {
303			$blocksize = $1;
304		} elsif ($l =~ m/^(.*?)\s+\d+\s+\d+\s+(\-?\d+)\s+\d+\%\s+\/.*?$/o) {
305			my ($dev, $avail) = ($1, $2);
306			$info = $devinfo->{$dev};
307			if (!defined $info) {
308				$info = $class->new($dev);
309			}
310			$info->{avail} = $avail;
311			$info->{blocksize} = $blocksize;
312		}
313	});
314
315	return $info;
316}
317
318sub find
319{
320	my ($class, $dev, $fname, $state) = @_;
321	if (!defined $dev) {
322		return $class->giveup;
323	}
324	if (!defined $devinfo2->{$dev}) {
325		$devinfo2->{$dev} = $class->ask_df($fname, $state);
326	}
327	return $devinfo2->{$dev};
328}
329
330sub synchronize
331{
332	for my $v (values %$devinfo2) {
333		$v->synchronize;
334	}
335}
336
337sub drop_changes
338{
339	for my $v (values %$devinfo2) {
340		$v->drop_changes;
341	}
342}
343
344sub tally
345{
346	my ($self, $state) = @_;
347
348	for my $v ((sort {$a->name cmp $b->name } values %$devinfo2), $self->giveup) {
349		$v->tally($state);
350	}
351}
352
353package OpenBSD::MountPoint;
354
355sub parse_opts
356{
357	my ($self, $opts) = @_;
358	for my $o (split /\,\s*/o, $opts) {
359		if ($o eq 'read-only') {
360			$self->{ro} = 1;
361		} elsif ($o eq 'nodev') {
362			$self->{nodev} = 1;
363		} elsif ($o eq 'nosuid') {
364			$self->{nosuid} = 1;
365		} elsif ($o eq 'noexec') {
366			$self->{noexec} = 1;
367		}
368	}
369}
370
371sub ro
372{
373	return shift->{ro};
374}
375
376sub nodev
377{
378	return shift->{nodev};
379}
380
381sub nosuid
382{
383	return shift->{nosuid};
384}
385
386sub noexec
387{
388	return shift->{noexec};
389}
390
391sub new
392{
393	my ($class, $dev, $mp, $opts) = @_;
394	my $n = bless { commited_use => 0, used => 0, delayed => 0,
395	    hw => 0, dev => $dev, mp => $mp }, $class;
396	if (defined $opts) {
397		$n->parse_opts($opts);
398	}
399	return $n;
400}
401
402
403sub avail
404{
405	my ($self, $used) = @_;
406	return $self->{avail} - $self->{used}/$self->{blocksize};
407}
408
409sub name
410{
411	my $self = shift;
412	return "$self->{dev} on $self->{mp}";
413}
414
415sub report_ro
416{
417	my ($s, $state, $fname) = @_;
418
419	if ($state->verbose >= 3 or ++($s->{problems}) < 4) {
420		$state->errsay("Error: #1 is read-only (#2)",
421		    $s->name, $fname);
422	} elsif ($s->{problems} == 4) {
423		$state->errsay("Error: ... more files for #1", $s->name);
424	}
425	$state->{problems}++;
426}
427
428sub report_overflow
429{
430	my ($s, $state, $fname) = @_;
431
432	if ($state->verbose >= 3 or ++($s->{problems}) < 4) {
433		$state->errsay("Error: #1 is not large enough (#2)",
434		    $s->name, $fname);
435	} elsif ($s->{problems} == 4) {
436		$state->errsay("Error: ... more files do not fit on #1",
437		    $s->name);
438	}
439	$state->{problems}++;
440	$state->{overflow} = 1;
441}
442
443sub report_noexec
444{
445	my ($s, $state, $fname) = @_;
446	$state->errsay("Error: #1 is noexec (#2)", $s->name, $fname);
447	$state->{problems}++;
448}
449
450sub synchronize
451{
452	my $v = shift;
453
454	if ($v->{used} > $v->{hw}) {
455		$v->{hw} = $v->{used};
456	}
457	$v->{used} += $v->{delayed};
458	$v->{delayed} = 0;
459	$v->{commited_use} = $v->{used};
460}
461
462sub drop_changes
463{
464	my $v = shift;
465
466	$v->{used} = $v->{commited_use};
467	$v->{delayed} = 0;
468}
469
470sub tally
471{
472	my ($data, $state) = @_;
473
474	return  if $data->{used} == 0;
475	$state->print("#1: #2 bytes", $data->name, $data->{used});
476	my $avail = $data->avail;
477	if ($avail < 0) {
478		$state->print(" (missing #1 blocks)", int(-$avail+1));
479	} elsif ($data->{hw} >0 && $data->{hw} > $data->{used}) {
480		$state->print(" (highwater #1 bytes)", $data->{hw});
481	}
482	$state->print("\n");
483}
484
485package OpenBSD::MountPoint::Fail;
486our @ISA=qw(OpenBSD::MountPoint);
487
488sub avail
489{
490	return 1;
491}
492
493sub new
494{
495	my $class = shift;
496	my $n = $class->SUPER::new('???', '???');
497	$n->{avail} = 0;
498	return $n;
499}
500
5011;
502