[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