ports/infrastructure/lib/OpenBSD/TrackFile.pm

327 lines
8.1 KiB
Perl

#! /usr/bin/perl
# $OpenBSD: TrackFile.pm,v 1.9 2023/05/16 13:59:17 espie Exp $
# Copyright (c) 2018-2022 Marc Espie <espie@openbsd.org>
#
# Permission to use, copy, modify, and distribute this software for any
# purpose with or without fee is hereby granted, provided that the above
# copyright notice and this permission notice appear in all copies.
#
# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
# OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
use v5.36;
# This is a set of utility classes for update-plist
# so in order to put objects in the right plist part, we need to track
# destination file: objects are annotated with an "OpenBSD::TrackedFile"
# which consists ultimately of all the items that are going to end up in
# that specific file
package OpenBSD::TrackedFile;
# the actual OpenBSD::TrackedFile(s) will be created by the next (factory) class
sub new($class, $name, $ext)
{
bless {name => $name, # the actual filename proper
ext => $ext, # extension like -new since update-plist
# creates new files for people to diff
items => [], # list of items for 1st pass
items2 => [] # actual list of items that get written
}, $class;
}
sub add($self, $item)
{
push(@{$self->{items}}, $item);
}
# this is the internal method that gets called through prepare_restate
sub add2($self, $item, $p)
{
if ($item->NoDuplicateNames) {
my $s = $p->subst->remove_ignored_vars($item->{prepared});
my $s2 = $p->subst->do($s);
if (defined (my $k = $item->keyword)) {
$s2 =~ s/^\@\Q$k\E\s//;
}
$p->{stash}{$s2}++;
my $comment = $p->subst->{maybe_comment};
if ($s ne $item->{prepared} &&
$item->{prepared} !~ m/^\Q$comment\E/) {
$item->{candidate_for_comment} = $s2;
}
}
push(@{$self->{items2}}, $item);
}
sub fh($self)
{
if (!defined $self->{fh}) {
my $full = $self->name.$self->{ext};
open($self->{fh}, '>', $full) or die "Can't open $full: $!";
}
return $self->{fh};
}
sub name($self)
{
return $self->{name};
}
# iterating through the list for preparing
sub next_item($self)
{
if (@{$self->{items}} != 0) {
return shift @{$self->{items}};
} else {
return undef;
}
}
# iterating through the list for writing
sub next_item2($self)
{
if (@{$self->{items2}} != 0) {
return shift @{$self->{items2}};
} else {
return undef;
}
}
# this is the factory class that is responsible for (physically) dispatching
# items into the right plist fragment
package OpenBSD::TrackFile;
# the base factory creates a "default" destination
sub new($class, $default, $ext)
{
my $self = bless {ext => $ext}, $class;
$self->{known}{$default} =
$self->{default} =
OpenBSD::TrackedFile->new($default, $self->{ext});
return $self;
}
# each new fragment creates a new OpenBSD::TrackedFile
# (unless it already exists)
sub file($self, $name)
{
$self->{known}{$name} //=
OpenBSD::TrackedFile->new($name, $self->{ext});
return $self->{known}{$name};
}
sub default($self)
{
return $self->{default};
}
# and this is the actual method that writes every object: responsible for
# handling each and every file, and also passing offstate changes (@mode and
# the likes) to prepare_restate to avoid too much duplication
sub write_all($self, $p)
{
# we mimic the way pkg_create writes files
# first pass is just going to scan through the list and queue actual
# stuff we want to write
for my $i (@{$p->{base_plists}}) {
$p->{restate} = {};
my @stack = ();
push(@stack, $self->file($i));
while (my $file = pop @stack) {
while (my $j = $file->next_item) {
my $filename = $j->prepare_restate($file, $p);
if (defined $filename) {
push(@stack, $file);
$file = $self->file($filename);
}
}
}
}
# second pass writes out the resulting "restated" data
for my $i (@{$p->{base_plists}}) {
$p->{restate} = {};
my @stack = ();
push(@stack, $self->file($i));
while (my $file = pop @stack) {
while (my $j = $file->next_item2) {
my $filename = $j->write_restate($file, $p);
if (defined $filename) {
push(@stack, $file);
$file = $self->file($filename);
}
}
close($file->fh);
}
}
}
package OpenBSD::PackingElement;
# the actual methods that keep state (@mode/@owner/@group) and do
# backsubstitution and writing.
# part of the state is the current fragment, so it should return the
# new filename when it changes
# Note that this is not called as a visitor, but directly by the FileTracker
# on the lists it builds
# The methods are split into a "restate" and a "backsubst" level so that
# subclasses may concentrate on that part of the job
# this is the actual writer.
sub write_restate($o, $file, $p)
{
$o->write_backsubst($file, $p);
return undef;
}
# this just prepares data for the second pass
sub prepare_restate($o, $file, $p)
{
$o->prepare_backsubst($file, $p);
return undef;
}
sub prepare_backsubst($o, $file, $p)
{
my $s = $p->subst->do_backsubst($o->fullstring, $o->unsubst, $o);
$o->{prepared} = $s;
$file->add2($o, $p);
}
# default backsubstitution and writing.
sub write_backsubst($o, $file, $p)
{
if (defined (my $s = $o->{candidate_for_comment})) {
if ($p->{stash}{$s} > 1) {
$o->{prepared} =
$p->subst->{maybe_comment}.$o->{prepared};
}
}
print {$file->fh} $o->{prepared}, "\n";
}
package OpenBSD::PackingElement::SpecialFile;
sub write_restate($, $, $)
{
}
sub prepare_restate($, $, $)
{
}
package OpenBSD::PackingElement::Fragment;
# while writing, change file accordingly
sub write_restate($self, $file, $p)
{
# don't do backsubst on fragments, pkg_create does not!
$self->write($file->fh);
my $base = $file->name;
my $frag = $self->frag;
$base =~ s/PFRAG\./PFRAG.$frag-/ or
$base =~ s/PLIST/PFRAG.$frag/;
return $base if $p->{tracker}{known}{$base};
return undef;
}
sub prepare_restate($self, $file, $p)
{
# don't do backsubst on fragments, pkg_create does not!
$file->add2($self, $p);
my $base = $file->name;
my $frag = $self->frag;
$base =~ s/PFRAG\./PFRAG.$frag-/ or
$base =~ s/PLIST/PFRAG.$frag/;
return $base if $p->{tracker}{known}{$base};
return undef;
}
package OpenBSD::PackingElement::FileObject;
sub write_restate($self, $f, $p)
{
# TODO there should be some more code matching the mode to the original
# file that was copied
for my $k (qw(mode owner group)) {
my $s = "\@$k";
if (defined $self->{$k}) {
if (defined $p->{restate}{$k}) {
if ($p->{restate}{$k} eq $self->{$k}) {
next;
}
}
if ($k eq 'mode') {
$s .= " ".$self->{$k};
} else {
$s .= " ".
$p->subst->do_backsubst($self->{$k});
}
} else {
if (!defined $p->{restate}{$k}) {
next;
}
}
$p->{restate}{$k} = $self->{$k};
print {$f->fh} $s, "\n";
}
$self->write_backsubst($f, $p);
return undef;
}
package OpenBSD::PackingElement::FileBase;
sub write_backsubst($self, $f, $p)
{
if (defined $self->{nochecksum}) {
print {$f->fh} "\@comment no checksum\n";
}
if (defined $self->{nodebug}) {
print {$f->fh} "\@comment no debug\n";
}
$self->SUPER::write_backsubst($f, $p);
}
package OpenBSD::PackingElement::LoginClass;
use File::Basename;
sub prepare_backsubst($self, $f, $p)
{
$self->SUPER::prepare_backsubst($f, $p);
if (!defined $self->{mytags}) {
my $n = OpenBSD::PackingElement::Sample->new(
"/etc/login.conf.d/".basename($self->name));
$n->prepare_backsubst($f, $p);
}
}
package OpenBSD::PackingElement::Lib;
sub prepare_backsubst($self, $f, $p)
{
if ($self->name =~ m,^(.*?)lib([^\/]+)\.so\.(\d+\.\d+)$,) {
my ($path, $name, $version) = ($1, $2, $3);
my $k = "LIB${name}_VERSION";
# XXX redo backsubst on the variable name
my $s = $p->subst->do_backsubst(
"\@lib ${path}lib$name.so.\$\{$k\}", $self->unsubst, $self);
$self->check_lib_version($version, $name,
$p->subst->value($k));
$self->{prepared} = $s;
$f->add2($self, $p);
} else {
$self->SUPER::write_backsubst($f, $p);
}
}
1;