[pve-devel] r5539 - pve-access-control/trunk/PVE
svn-commits at proxmox.com
svn-commits at proxmox.com
Wed Feb 16 06:37:51 CET 2011
Author: dietmar
Date: 2011-02-16 06:37:51 +0100 (Wed, 16 Feb 2011)
New Revision: 5539
Added:
pve-access-control/trunk/PVE/RPCEnvironment.pm
Modified:
pve-access-control/trunk/PVE/Makefile
Log:
moved from pve-common
Modified: pve-access-control/trunk/PVE/Makefile
===================================================================
--- pve-access-control/trunk/PVE/Makefile 2011-02-16 05:36:56 UTC (rev 5538)
+++ pve-access-control/trunk/PVE/Makefile 2011-02-16 05:37:51 UTC (rev 5539)
@@ -4,4 +4,5 @@
install:
install -D -m 0644 AccessControl.pm ${DESTDIR}${PERLDIR}/PVE/AccessControl.pm
install -D -m 0644 ACLCache.pm ${DESTDIR}${PERLDIR}/PVE/ACLCache.pm
+ install -D -m 0644 RPCEnvironment.pm ${DESTDIR}${PERLDIR}/PVE/RPCEnvironment.pm
make -C API2 install
\ No newline at end of file
Added: pve-access-control/trunk/PVE/RPCEnvironment.pm
===================================================================
--- pve-access-control/trunk/PVE/RPCEnvironment.pm (rev 0)
+++ pve-access-control/trunk/PVE/RPCEnvironment.pm 2011-02-16 05:37:51 UTC (rev 5539)
@@ -0,0 +1,331 @@
+package PVE::RPCEnvironment;
+
+use strict;
+use warnings;
+use POSIX ":sys_wait_h";
+use IO::File;
+use Fcntl qw(:flock);
+use PVE::SafeSyslog;
+use PVE::INotify;
+use PVE::ProcFSTools;
+
+# we use this singleton class to pass RPC related environment value
+
+my $pve_env;
+
+# save $SIG{CHLD} handler implementation.
+# simply set $SIG{CHLD} = $worker_reaper;
+# and register forked processes with &$register_worker(pid)
+# Note: using $SIG{CHLD} = 'IGNORE' or $SIG{CHLD} = sub { wait (); } or ...
+# has serious side effects, because perls built in system() and open()
+# functions can't get the correct exit status of a child. So we cant use
+# that (also see perlipc)
+
+my $WORKER_PIDS;
+
+my $worker_reaper = sub {
+ local $!; local $?;
+ foreach my $pid (keys %$WORKER_PIDS) {
+ my $waitpid = waitpid ($pid, WNOHANG);
+ if (defined($waitpid) && ($waitpid == $pid)) {
+ delete ($WORKER_PIDS->{$pid});
+ }
+ }
+};
+
+my $register_worker = sub {
+ my $pid = shift;
+
+ return if !$pid;
+
+ # do not register if already finished
+ my $waitpid = waitpid ($pid, WNOHANG);
+ if (defined($waitpid) && ($waitpid == $pid)) {
+ delete ($WORKER_PIDS->{$pid});
+ return;
+ }
+
+ $WORKER_PIDS->{$pid} = 1;
+};
+
+sub get {
+
+ die "not initialized" if !$pve_env;
+
+ return $pve_env;
+}
+
+sub init {
+ my ($class, $type, %params) = @_;
+
+ $class = ref($class) || $class;
+
+ die "already initialized" if $pve_env;
+
+ die "unknown environment type" if !$type || $type !~ m/^(cli|pub|priv)$/;
+
+ $SIG{CHLD} = $worker_reaper;
+
+ # environment types
+ # cli ... command started fron command line
+ # pub ... access from public server (apache)
+ # priv ... access from private server (pvedaemon)
+
+ my $self = {
+ type => $type,
+ };
+
+ bless $self, $class;
+
+ foreach my $p (keys %params) {
+ if ($p eq 'atfork') {
+ $self->{$p} = $params{$p};
+ } else {
+ die "unknown option '$p'";
+ }
+ }
+
+ $pve_env = $self;
+
+ my ($sysname, $nodename) = POSIX::uname();
+
+ $nodename =~ s/\..*$//; # strip domain part, if any
+
+ $self->{nodename} = $nodename;
+
+ return $self;
+};
+
+sub set_language {
+ my ($self, $lang) = @_;
+
+ # fixme: initialize I18N
+
+ $self->{language} = $lang;
+}
+
+sub set_user {
+ my ($self, $user) = @_;
+
+ # fixme: get ACLs
+
+ $self->{user} = $user;
+}
+
+sub get_user {
+ my ($self) = @_;
+
+ die "user name not set\n" if !$self->{user};
+
+ return $self->{user};
+}
+
+# UPID helper
+# WARN: $res->{filename} must not depend on PID, because we
+# use it before we know the PID
+
+sub upid_decode {
+ my $upid = shift;
+
+ my $res;
+
+ # "UPID:$node:$pid:$start:$type:$data"
+ if ($upid =~ m/^UPID:(\w+):(\d+)(-(\d+))?:(\d+):([^:\s]+):(.*)$/) {
+ $res->{node} = $1;
+ $res->{pid} = $2;
+ $res->{pstart} = $4 || 0;
+ $res->{starttime} = $5;
+ $res->{type} = $6;
+ $res->{data} = $7;
+
+ if ($res->{type} eq 'vmops') {
+ if ($res->{data} =~ m/^([^:\s]+):(\d+):(\S+)$/) {
+ $res->{command} = $1;
+ $res->{veid} = $2;
+ $res->{user} = $3;
+
+ $res->{filename} = "/tmp/vmops-$res->{veid}.out";
+ } else {
+ return undef;
+ }
+ } elsif ($res->{type} eq 'apldownload') {
+ if ($res->{data} =~ m/^([^:\s]+):(.+)$/) {
+ $res->{user} = $1;
+ $res->{apl} = $2;
+ $res->{filename} = "/tmp/apldownload-$res->{user}.out";
+ } else {
+ return undef;
+ }
+ }
+ }
+
+ return $res;
+}
+
+sub upid_encode {
+ my $uip_hash = shift;
+
+ my $d = $uip_hash; # shortcut
+
+ return "UPID:$d->{node}:$d->{pid}-$d->{pstart}:$d->{starttime}:$d->{type}:$d->{data}";
+}
+
+# start long running workers
+# $data append to the returned uniquely identifier, which
+# has the following format: "UPID:$node:$pid-$pstart:$startime:$dtype:$data"
+# STDIN is redirected to /dev/null
+# STDOUT,STDERR are redirected to the filename returned by upid_decode
+# that file is locked wit flock to make sure only one process
+# is writing it
+
+sub fork_worker {
+ my ($self, $dtype, $data, $function) = @_;
+
+ $dtype = 'unknown' if !defined ($dtype);
+
+ $data = '' if !defined ($data);
+
+ my $starttime = time ();
+
+ my @psync = POSIX::pipe();
+
+ my $node = $self->{nodename};
+
+ # detect filename with faked PID
+ my $tmp = upid_decode ("UPID:$node:0-0:0:$dtype:$data");
+ my $filename = $tmp->{filename};
+
+ my $lockfh;
+ # lock output file
+ if ($filename) {
+
+ $lockfh = IO::File->new ($filename, O_WRONLY|O_CREAT) ||
+ die "unable to open output file - $!\n";
+
+ my $wwwid = getpwnam('www-data');
+ chown $wwwid, $filename;
+
+ if (!flock ($lockfh, LOCK_EX|LOCK_NB)) {
+ undef $lockfh; # close
+ die "unable to lock output file\n";
+ }
+
+ if (!truncate ($lockfh, 0)) {
+ die "unable to truncate output file - $!\n";
+ }
+ }
+
+ my $cpid = fork();
+ if (!defined($cpid)) {
+ undef $lockfh; # close
+ die "unable to fork worker - $!";
+ }
+
+ if ($cpid == 0) {
+
+ $SIG{INT} = $SIG{QUIT} = $SIG{TERM} = sub { die "received interrupt\n"; };
+
+ $SIG{CHLD} = $SIG{PIPE} = 'DEFAULT';
+
+ # set sess/process group - we want to be able to kill the
+ # whole process group
+ POSIX::setsid();
+
+ POSIX::close ($psync[0]);
+
+ PVE::INotify::inotify_close();
+
+ if (my $atfork = $self->{atfork}) {
+ &$atfork();
+ }
+
+ # same algorythm as used inside SA
+
+ # STDIN = /dev/null
+ my $fd = fileno (STDIN);
+ close STDIN;
+ POSIX::close(0) if $fd != 0;
+
+ if (!open (STDIN, "</dev/null")) {
+ POSIX::_exit (1);
+ kill ('KILL', $$);
+ }
+
+ # redirect STDOUT
+ $fd = fileno(STDOUT);
+ close STDOUT;
+ POSIX::close (1) if $fd != 1;
+
+ if ($filename) {
+ if (!open (STDOUT, ">&", $lockfh)) {
+ POSIX::_exit (1);
+ kill ('KILL', $$);
+ }
+
+ STDOUT->autoflush (1);
+ } else {
+ if (!open (STDOUT, ">/dev/null")) {
+ POSIX::_exit (1);
+ kill ('KILL', $$);
+ }
+ }
+
+ # redirect STDERR to STDOUT
+ $fd = fileno (STDERR);
+ close STDERR;
+ POSIX::close(2) if $fd != 2;
+
+ if (!open (STDERR, ">&1")) {
+ POSIX::_exit (1);
+ kill ('KILL', $$);
+ }
+
+ STDERR->autoflush (1);
+
+ my $pstart = PVE::ProcFSTools::read_proc_starttime ($$) ||
+ die "unable to read process starttime";
+
+ my $upid = upid_encode ({
+ node => $node, pid => $$, pstart => $pstart,
+ starttime => $starttime, type => $dtype, data => $data });
+
+ # sync with parent
+ POSIX::write ($psync[1], $upid, length ($upid));
+ POSIX::close ($psync[1]);
+
+ eval { &$function ($upid); };
+ my $err = $@;
+ if ($err) {
+ my $msg = "worker function failed: $err";
+ syslog('err', $msg);
+ print STDERR "$msg\n";
+ POSIX::_exit(-1);
+ } else {
+ POSIX::_exit (0);
+ }
+ kill ('KILL', $$);
+ }
+
+ POSIX::close ($psync[1]);
+
+ # sync with child (wait until child starts)
+ my $upid = '';
+ POSIX::read($psync[0], $upid, 4096);
+ POSIX::close ($psync[0]);
+
+ undef $lockfh; # close
+
+ &$register_worker($cpid);
+
+ my $uh = upid_decode ($upid);
+ if (!$uh ||
+ !($uh->{node} eq $node && $uh->{pid} == $cpid &&
+ $uh->{starttime} == $starttime &&
+ $uh->{type} eq $dtype && $uh->{data} eq $data)) {
+ die "got strange worker upid '$upid'\n";
+ }
+
+ return $upid;
+}
+
+1;
More information about the pve-devel
mailing list