use warnings;
use POSIX qw(EINTR EEXIST EOPNOTSUPP);
use IO::Socket::IP;
-use Socket qw(AF_INET AF_INET6 AI_ALL AI_V4MAPPED);
+use Socket qw(AF_INET AF_INET6 AI_ALL AI_V4MAPPED AI_CANONNAME SOCK_DGRAM
+ IPPROTO_TCP);
use IO::Select;
use File::Basename;
use File::Path qw(make_path);
use Net::DBus qw(dbus_uint32 dbus_uint64);
use Net::DBus::Callback;
use Net::DBus::Reactor;
+use Scalar::Util 'weaken';
+use PVE::Syscall;
# avoid warning when parsing long hex values with hex()
no warnings 'portable'; # Support for 64-bit ints required
}
# flock: we use one file handle per process, so lock file
-# can be called multiple times and succeeds for the same process.
+# can be nested multiple times and succeeds for the same process.
+#
+# Since this is the only way we lock now and we don't have the old
+# 'lock(); code(); unlock();' pattern anymore we do not actually need to
+# count how deep we're nesting. Therefore this hash now stores a weak reference
+# to a boolean telling us whether we already have a lock.
my $lock_handles = {};
my $mode = $shared ? LOCK_SH : LOCK_EX;
- my $lock_func = sub {
- if (!$lock_handles->{$$}->{$filename}) {
- my $fh = new IO::File(">>$filename") ||
- die "can't open file - $!\n";
- $lock_handles->{$$}->{$filename} = { fh => $fh, refcount => 0};
- }
+ my $lockhash = ($lock_handles->{$$} //= {});
+
+ # Returns a locked file handle.
+ my $get_locked_file = sub {
+ my $fh = IO::File->new(">>$filename")
+ or die "can't open file - $!\n";
- if (!flock($lock_handles->{$$}->{$filename}->{fh}, $mode|LOCK_NB)) {
- print STDERR "trying to acquire lock...";
+ if (!flock($fh, $mode|LOCK_NB)) {
+ print STDERR "trying to acquire lock...";
my $success;
while(1) {
- $success = flock($lock_handles->{$$}->{$filename}->{fh}, $mode);
+ $success = flock($fh, $mode);
# try again on EINTR (see bug #273)
if ($success || ($! != EINTR)) {
last;
}
}
- if (!$success) {
- print STDERR " failed\n";
- die "can't acquire lock '$filename' - $!\n";
- }
- print STDERR " OK\n";
- }
- $lock_handles->{$$}->{$filename}->{refcount}++;
+ if (!$success) {
+ print STDERR " failed\n";
+ die "can't acquire lock '$filename' - $!\n";
+ }
+ print STDERR " OK\n";
+ }
+
+ return $fh;
};
my $res;
-
- eval { run_with_timeout($timeout, $lock_func); };
- my $err = $@;
- if ($err) {
- $err = "can't lock file '$filename' - $err";
- } else {
- eval { $res = &$code(@param) };
- $err = $@;
- }
-
- if (my $fh = $lock_handles->{$$}->{$filename}->{fh}) {
- my $refcount = --$lock_handles->{$$}->{$filename}->{refcount};
- if ($refcount <= 0) {
- $lock_handles->{$$}->{$filename} = undef;
- close ($fh);
+ my $checkptr = $lockhash->{$filename};
+ my $check = 0; # This must not go out of scope before running the code.
+ my $local_fh; # This must stay local
+ if (!$checkptr || !$$checkptr) {
+ # We cannot create a weak reference in a single atomic step, so we first
+ # create a false-value, then create a reference to it, then weaken it,
+ # and after successfully locking the file we change the boolean value.
+ #
+ # The reason for this is that if an outer SIGALRM throws an exception
+ # between creating the reference and weakening it, a subsequent call to
+ # lock_file_full() will see a leftover full reference to a valid
+ # variable. This variable must be 0 in order for said call to attempt to
+ # lock the file anew.
+ #
+ # An externally triggered exception elsewhere in the code will cause the
+ # weak reference to become 'undef', and since the file handle is only
+ # stored in the local scope in $local_fh, the file will be closed by
+ # perl's cleanup routines as well.
+ #
+ # This still assumes that an IO::File handle can properly deal with such
+ # exceptions thrown during its own destruction, but that's up to perls
+ # guts now.
+ $lockhash->{$filename} = \$check;
+ weaken $lockhash->{$filename};
+ $local_fh = eval { run_with_timeout($timeout, $get_locked_file) };
+ if ($@) {
+ $@ = "can't lock file '$filename' - $@";
+ return undef;
}
+ $check = 1;
}
-
- if ($err) {
- $@ = $err;
- return undef;
- }
-
- $@ = undef;
-
+ $res = eval { &$code(@param); };
+ return undef if $@;
return $res;
}
my $timeout;
my $oldtimeout;
my $pid;
- my $exitcode;
+ my $exitcode = -1;
my $outfunc;
my $errfunc;
my $output;
my $afterfork;
my $noerr;
+ my $keeplocale;
eval {
$afterfork = $param{$p};
} elsif ($p eq 'noerr') {
$noerr = $param{$p};
+ } elsif ($p eq 'keeplocale') {
+ $keeplocale = $param{$p};
} else {
die "got unknown parameter '$p' for run_command\n";
}
my $writer = $input && $input =~ m/^<&/ ? $input : IO::File->new();
my $error = IO::File->new();
- # try to avoid locale related issues/warnings
- my $lang = $param{lang} || 'C';
-
my $orig_pid = $$;
eval {
- local $ENV{LC_ALL} = $lang;
+ local $ENV{LC_ALL} = 'C' if !$keeplocale;
# suppress LVM warnings like: "File descriptor 3 left open";
local $ENV{LVM_SUPPRESS_FD_WARNINGS} = "1";
}
sub next_unused_port {
- my ($range_start, $range_end, $family) = @_;
+ my ($range_start, $range_end, $family, $address) = @_;
# We use a file to register allocated ports.
# Those registrations expires after $expiretime.
}
my $newport;
+ my %sockargs = (Listen => 5,
+ ReuseAddr => 1,
+ Family => $family,
+ Proto => IPPROTO_TCP,
+ GetAddrInfoFlags => 0);
+ $sockargs{LocalAddr} = $address if defined($address);
for (my $p = $range_start; $p < $range_end; $p++) {
next if $ports->{$p}; # reserved
- my $sock = IO::Socket::IP->new(Listen => 5,
- LocalPort => $p,
- ReuseAddr => 1,
- Family => $family,
- Proto => 0,
- GetAddrInfoFlags => 0);
+ $sockargs{LocalPort} = $p;
+ my $sock = IO::Socket::IP->new(%sockargs);
if ($sock) {
close($sock);
return $newport;
};
- my $p = lock_file($filename, 10, $code);
+ my $p = lock_file('/var/lock/pve-ports.lck', 10, $code);
die $@ if $@;
die "unable to find free port (${range_start}-${range_end})\n" if !$p;
}
sub next_migrate_port {
- my ($family) = @_;
- return next_unused_port(60000, 60050, $family);
+ my ($family, $address) = @_;
+ return next_unused_port(60000, 60050, $family, $address);
}
sub next_vnc_port {
- my ($family) = @_;
- return next_unused_port(5900, 6000, $family);
+ my ($family, $address) = @_;
+ return next_unused_port(5900, 6000, $family, $address);
}
sub next_spice_port {
- my ($family) = @_;
- return next_unused_port(61000, 61099, $family);
+ my ($family, $address) = @_;
+ return next_unused_port(61000, 61099, $family, $address);
}
# NOTE: NFS syscall can't be interrupted, so alarm does
return Encode::decode("utf8", uri_unescape($data));
}
+# depreciated - do not use!
+# we now decode all parameters by default
sub decode_utf8_parameters {
my ($param) = @_;
}
sub dump_journal {
- my ($start, $limit, $since, $until) = @_;
+ my ($start, $limit, $since, $until, $service) = @_;
my $lines = [];
my $count = 0;
my $cmd = ['journalctl', '-o', 'short', '--no-pager'];
+ push @$cmd, '--unit', $service if $service;
push @$cmd, '--since', $since if $since;
push @$cmd, '--until', $until if $until;
run_command($cmd, outfunc => $parser);
return $res[0]->{family};
}
+# get the fully qualified domain name of a host
+# same logic as hostname(1): The FQDN is the name getaddrinfo(3) returns,
+# given a nodename as a parameter
+sub get_fqdn {
+ my ($nodename) = @_;
+
+ my $hints = {
+ flags => AI_CANONNAME,
+ socktype => SOCK_DGRAM
+ };
+
+ my ($err, @addrs) = Socket::getaddrinfo($nodename, undef, $hints);
+
+ die "getaddrinfo: $err" if $err;
+
+ return $addrs[0]->{canonname};
+}
+
# Parses any sane kind of host, or host+port pair:
# The port is always optional and thus may be undef.
sub parse_host_and_port {
sub unshare($) {
my ($flags) = @_;
- return 0 == syscall(272, $flags);
+ return 0 == syscall(PVE::Syscall::unshare, $flags);
}
sub setns($$) {
my ($fileno, $nstype) = @_;
- return 0 == syscall(308, $fileno, $nstype);
+ return 0 == syscall(PVE::Syscall::setns, $fileno, $nstype);
}
sub syncfs($) {
my ($fileno) = @_;
- return 0 == syscall(306, $fileno);
+ return 0 == syscall(PVE::Syscall::syncfs, $fileno);
}
sub sync_mountpoint {
sub openat($$$;$) {
my ($dirfd, $pathname, $flags, $mode) = @_;
- my $fd = syscall(257, $dirfd, $pathname, $flags, $mode//0);
+ my $fd = syscall(PVE::Syscall::openat, $dirfd, $pathname, $flags, $mode//0);
return undef if $fd < 0;
# sysopen() doesn't deal with numeric file descriptors apparently
# so we need to convert to a mode string for IO::Handle->new_from_fd
my $handle = IO::Handle->new_from_fd($fd, $flagstr);
return $handle if $handle;
my $err = $!; # save error before closing the raw fd
- syscall(3, $fd); # close
+ syscall(PVE::Syscall::close, $fd); # close
$! = $err;
return undef;
}
sub mkdirat($$$) {
my ($dirfd, $name, $mode) = @_;
- return syscall(258, $dirfd, $name, $mode) == 0;
+ return syscall(PVE::Syscall::mkdirat, $dirfd, $name, $mode) == 0;
}
# NOTE: This calls the dbus main loop and must not be used when another dbus
die "systemd job never completed\n" if !$done;
}
+my $salt_starter = time();
+
+sub encrypt_pw {
+ my ($pw) = @_;
+
+ $salt_starter++;
+ my $salt = substr(Digest::SHA::sha1_base64(time() + $salt_starter + $$), 0, 8);
+
+ # crypt does not want '+' in salt (see 'man crypt')
+ $salt =~ s/\+/X/g;
+
+ return crypt(encode("utf8", $pw), "\$5\$$salt\$");
+}
+
1;