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