Config-Generator
view release on metacpan or search on metacpan
lib/Config/Generator/File.pm view on Meta::CPAN
#+##############################################################################
# #
# File: Config/Generator/File.pm #
# #
# Description: Config::Generator file support #
# #
#-##############################################################################
#
# module definition
#
package Config::Generator::File;
use strict;
use warnings;
our $VERSION = "1.1";
our $REVISION = sprintf("%d.%02d", q$Revision: 1.21 $ =~ /(\d+)\.(\d+)/);
#
# used modules
#
use No::Worries::Die qw(dief);
use No::Worries::Dir qw(dir_ensure dir_parent);
use No::Worries::Export qw(export_control);
use No::Worries::File qw(file_read file_write);
use No::Worries::Log qw(log_debug);
use No::Worries::Proc qw(proc_run);
use No::Worries::Stat qw(ST_MODE ST_UID ST_GID);
use No::Worries::String qw(string_quantify);
use No::Worries::Warn qw(warnf);
use Params::Validate qw(validate_pos :types);
use POSIX qw(:errno_h);
use Config::Generator qw($NoAction $Verbosity $RootDir);
#
# global variables
#
our(%_Registered);
#
# printing helpers
#
sub _printf1 (@) { printf(@_) if $Verbosity >= 1 }
sub _printf2 (@) { printf(@_) if $Verbosity >= 2 }
#
# path absence helper
#
sub _ensure_absent ($) {
my($path) = @_;
my($what);
lstat($path) or return;
if (-f _) {
$what = sprintf("file %s", $path);
if ($NoAction) {
_printf1("would have removed %s\n", $what);
} elsif (unlink($path)) {
_printf1("removed %s\n", $what);
} else {
warnf("did not remove %s: %s", $what, $!);
}
} elsif (-d _) {
$what = sprintf("directory %s", $path);
if ($NoAction) {
_printf1("would have removed %s\n", $what);
} elsif (rmdir($path)) {
_printf1("removed %s\n", $what);
} else {
warnf("did not remove %s: %s", $what, $!);
}
} else {
warnf("skipped alien file: %s", $path);
}
}
#
# directory parent helper
#
sub _ensure_parent ($) {
my($path) = @_;
my($parent);
$parent = $path = dir_parent($path);
return if -d $parent;
while (not -d $path) {
last if $_Registered{$path};
last if $path eq $RootDir;
$_Registered{$path} = { type => "parent" };
$path = dir_parent($path);
}
log_debug("making directory %s", $parent);
dir_ensure($parent);
}
#
# (almost) atomic file write helper
#
sub _atomic_write ($$) {
my($path, $data) = @_;
my(@stat, $tmpath);
$tmpath = $path . ".tmp";
_unlink($tmpath) if -e $tmpath;
file_write($tmpath, data => $data);
@stat = stat($path);
if (@stat) {
_chmod($stat[ST_MODE] & oct(7777), $tmpath);
_chown($stat[ST_UID], $stat[ST_GID], $tmpath);
_unlink($path);
}
rename($tmpath, $path)
or dief("cannot rename(%s, %s): %s", $tmpath, $path, $!);
}
#
# file contents helper
#
sub _ensure_contents ($$) {
my($path, $contents) = @_;
my($what);
log_debug("checking file %s", $path);
$what = sprintf("file %s", $path);
if (-e $path) {
dief("path exists already and is not a file: %s", $path)
unless -f _;
if (file_read($path) eq $contents) {
_printf2("checked %s\n", $what);
} else {
proc_run(
command => [ "diff", "-u", $path, "-" ],
stdin => \$contents,
) if $Verbosity;
if ($NoAction) {
_printf1("would have updated %s\n", $what);
} else {
_atomic_write($path, \$contents);
_printf1("updated %s\n", $what);
}
}
} else {
if ($NoAction) {
_printf1("would have created %s\n", $what);
} else {
_ensure_parent($path);
_atomic_write($path, \$contents);
_printf1("created %s\n", $what);
}
}
}
#
# fatal helpers
#
sub _chmod ($$) {
my($mode, $path) = @_;
chmod($mode, $path)
or dief("cannot chmod(%04o, %s): %s", $mode, $path, $!);
}
sub _chown ($$$) {
my($uid, $gid, $path) = @_;
lib/Config/Generator/File.pm view on Meta::CPAN
sub ensure_directory ($) {
my($path) = validate_pos(@_, @ensure_directory_options);
my($rpath, $what);
$rpath = $RootDir . $path;
log_debug("checking directory %s", $rpath);
dief("duplicate directory: %s", $path) if $_Registered{$path};
$_Registered{$path} = { type => "directory" };
$what = sprintf("directory %s", $rpath);
if (-d $rpath) {
_printf2("checked %s\n", $what);
} else {
dief("path exists already and is not a directory: %s", $rpath)
if -e _;
if ($NoAction) {
_printf1("would have created %s\n", $what);
} else {
_ensure_parent($rpath);
dir_ensure($rpath);
_printf1("created %s\n", $what);
}
}
}
#
# ensure file contents
#
my @ensure_file_options = (
$ensure_directory_options[0],
{ type => SCALAR },
);
sub ensure_file ($$) {
my($path, $contents) = validate_pos(@_, @ensure_file_options);
dief("duplicate file: %s", $path) if $_Registered{$path};
$_Registered{$path} = { type => "file" };
_ensure_contents($RootDir . $path, $contents);
}
#
# ensure symlink target
#
my @ensure_symlink_options = (
$ensure_directory_options[0],
{ type => SCALAR },
);
sub ensure_symlink ($$) {
my($path, $target) = validate_pos(@_, @ensure_symlink_options);
my($rpath, $what, @stat, $actual);
$rpath = $RootDir . $path;
log_debug("checking symlink %s", $rpath);
dief("duplicate symlink: %s", $path) if $_Registered{$path};
$_Registered{$path} = { type => "symlink" };
$what = sprintf("symlink %s -> %s", $rpath, $target);
@stat = lstat($rpath);
if (-e _) {
dief("path exists already and is not a symlink: %s", $rpath)
unless -l _;
$actual = readlink($rpath);
dief("cannot readlink(%s): %s", $rpath, $!)
unless defined($actual);
if ($target eq $actual) {
_printf2("checked %s\n", $what);
} else {
if ($NoAction) {
_printf1("would have updated %s\n", $what);
} else {
_unlink($rpath);
_symlink($target, $rpath);
_printf1("updated %s\n", $what);
}
}
} else {
if ($NoAction) {
_printf1("would have created %s\n", $what);
} else {
_ensure_parent($rpath);
_symlink($target, $rpath);
_printf1("created %s\n", $what);
}
}
}
#
# ensure file mode
#
my @ensure_mode_options = (
$ensure_directory_options[0],
{ type => SCALAR, regex => qr/^\d+$/ },
);
sub ensure_mode ($$) {
my($path, $mode) = validate_pos(@_, @ensure_mode_options);
my($rpath, $what, @stat);
$rpath = $RootDir . $path;
log_debug("checking mode %s", $rpath);
dief("not a registered file or directory: %s", $path)
unless $_Registered{$path}
and $_Registered{$path}{type} =~ /^(file|directory)$/;
dief("duplicate mode: %s", $path) if $_Registered{$path}{mode};
$_Registered{$path}{mode} = sprintf("%04o", $mode);
dief("invalid mode: %s", $_Registered{$path}{mode})
unless 0 < $mode and $mode <= oct(7777);
$what = sprintf("the mode of %s: %s", $rpath, $_Registered{$path}{mode});
@stat = stat($rpath);
unless (@stat) {
dief("cannot stat(%s): %s", $rpath, $!)
unless $NoAction and $! == ENOENT;
_printf2("would have checked %s (but it does no exist yet)\n", $what);
return;
}
if (($stat[ST_MODE] & oct(7777)) == $mode) {
_printf2("checked %s\n", $what);
} elsif ($> and $_Registered{$path}{type} eq "directory") {
# we should not change a directory mode if we are not root
# (we could shoot ourself in the foot with a read-only directory)
_printf2("would have changed %s (but we are not root)\n", $what);
} else {
if ($NoAction) {
_printf1("would have changed %s\n", $what);
} else {
_chmod($mode, $rpath);
_printf1("changed %s\n", $what);
}
}
}
#
# ensure file user
#
my @ensure_user_options = (
$ensure_directory_options[0],
{ type => SCALAR },
);
sub ensure_user ($$) {
my($path, $user) = validate_pos(@_, @ensure_user_options);
my($rpath, $what, $uid, @stat);
$rpath = $RootDir . $path;
log_debug("checking user %s", $rpath);
dief("not a registered file or directory: %s", $path)
unless $_Registered{$path}
and $_Registered{$path}{type} =~ /^(file|directory)$/;
dief("duplicate user: %s", $path) if $_Registered{$path}{user};
$_Registered{$path}{user} = $user;
$what = sprintf("the user of %s: %s", $rpath, $_Registered{$path}{user});
if ($>) {
# we do not change the user if we are not root
# (and we stop immediately since the user may not even exist)
_printf2("would have checked %s (but we are not root)\n", $what);
return;
}
$uid = getpwnam($user);
dief("unknown user: %s", $user) unless defined($uid);
@stat = stat($rpath);
unless (@stat) {
dief("cannot stat(%s): %s", $rpath, $!)
unless $NoAction and $! == ENOENT;
_printf2("would have checked %s (but it does no exist yet)\n", $what);
return;
}
if ($stat[ST_UID] == $uid) {
_printf2("checked %s\n", $what);
} else {
if ($NoAction) {
_printf1("would have changed %s\n", $what);
} else {
_chown($uid, $stat[ST_GID], $rpath);
_printf1("changed %s\n", $what);
}
}
}
#
# ensure file group
#
my @ensure_group_options = (
$ensure_directory_options[0],
{ type => SCALAR },
);
sub ensure_group ($$) {
my($path, $group) = validate_pos(@_, @ensure_group_options);
my($rpath, $what, $gid, @stat);
$rpath = $RootDir . $path;
log_debug("checking group %s", $rpath);
dief("not a registered file or directory: %s", $path)
unless $_Registered{$path}
and $_Registered{$path}{type} =~ /^(file|directory)$/;
dief("duplicate group: %s", $path) if $_Registered{$path}{group};
$_Registered{$path}{group} = $group;
$what = sprintf("the group of %s: %s", $rpath, $_Registered{$path}{group});
if ($>) {
# we do not change the group if we are not root
# (and we stop immediately since the group may not even exist)
_printf2("would have checked %s (but we are not root)\n", $what);
return;
}
$gid = getgrnam($group);
dief("unknown group: %s", $group) unless defined($gid);
@stat = stat($rpath);
unless (@stat) {
dief("cannot stat(%s): %s", $rpath, $!)
unless $NoAction and $! == ENOENT;
_printf2("would have checked %s (but it does no exist yet)\n", $what);
return;
}
if ($stat[ST_GID] == $gid) {
_printf2("checked %s\n", $what);
} else {
if ($NoAction) {
_printf1("would have changed %s\n", $what);
} else {
_chown($stat[ST_UID], $gid, $rpath);
_printf1("changed %s\n", $what);
}
}
}
#
# return the list of registered files (paths only)
#
sub files_manifest () {
my(@lines);
@lines = sort(keys(%_Registered));
return(@lines) if wantarray();
return(join("", map("$_\n", @lines)));
}
#
# return the list of registered files (rpm spec %files format)
#
# note: this assumes an initial "%defattr(-,root,root,-)" line
#
sub files_spec () {
my(%defattr, @lines, @list, $mode, $user, $group);
%defattr = (
mode => "-",
user => "root",
group => "root",
);
foreach my $path (sort(keys(%_Registered))) {
@list = ();
$mode = $_Registered{$path}{mode} || $defattr{mode};
$user = $_Registered{$path}{user} || $defattr{user};
$group = $_Registered{$path}{group} || $defattr{group};
push(@list, "%attr($mode, $user, $group)")
if $mode ne $defattr{mode}
or $user ne $defattr{user}
or $group ne $defattr{group};
push(@list, "%dir") if $_Registered{$path}{type} eq "directory";
push(@list, $path);
push(@lines, "@list");
}
return(@lines) if wantarray();
return(join("", map("$_\n", @lines)));
}
#
( run in 2.413 seconds using v1.01-cache-2.11-cpan-39bf76dae61 )