[28091] trunk/base/src

source_changes at macosforge.org source_changes at macosforge.org
Mon Aug 20 07:48:12 PDT 2007


Revision: 28091
          http://trac.macosforge.org/projects/macports/changeset/28091
Author:   epimenov at macports.org
Date:     2007-08-20 07:48:10 -0700 (Mon, 20 Aug 2007)

Log Message:
-----------
Port trace: dep check, SDK redirect

Modified Paths:
--------------
    trunk/base/src/darwintracelib1.0/darwintrace.c
    trunk/base/src/pextlib1.0/tracelib.c
    trunk/base/src/pextlib1.0/tracelib.h
    trunk/base/src/port1.0/porttrace.tcl
    trunk/base/src/port1.0/portutil.tcl

Modified: trunk/base/src/darwintracelib1.0/darwintrace.c
===================================================================
--- trunk/base/src/darwintracelib1.0/darwintrace.c	2007-08-20 13:11:37 UTC (rev 28090)
+++ trunk/base/src/darwintracelib1.0/darwintrace.c	2007-08-20 14:48:10 UTC (rev 28091)
@@ -443,6 +443,23 @@
 }
 
 /*
+ * return 1 if path is directory or not exists
+ * return 0 otherwise
+ */
+static int is_directory(const char * path)
+{
+#define stat(path, sb) syscall(SYS_stat, path, sb)
+	struct stat s;
+	if(stat(path, &s)==-1)
+		/* Actually is not directory, but anyway, we shouldn't test a dependency unless file exists */
+		return 1;
+	
+	return S_ISDIR(s.st_mode);
+#undef stat
+}
+
+
+/*
  * return 1 if path allowed, 0 otherwise
  */
 static int ask_for_dependency(char * path)
@@ -450,9 +467,12 @@
 	char buffer[BUFFER_SIZE], *p;
 	int result=0;
 	
+	if(is_directory(path))
+		return 1;
+	
 	strcpy(buffer, "dep_check\t");
 	strcat(buffer, path);
-	p=exchange_with_port(buffer, strlen(buffer), 1);
+	p=exchange_with_port(buffer, strlen(buffer)+1, 1);
 	if((int)p==-1||!p)
 		return 0;
 	
@@ -504,6 +524,10 @@
 	int result=-1;
 	
 	__darwintrace_setup();
+	
+	if(!filemap)
+		return 1;
+	
 	if(*path=='/')
 		p=strdup(path);
 	else
@@ -516,15 +540,14 @@
 		strncpy(_, path, BUFFER_SIZE-(_-p));
 	}
 	__darwintrace_cleanup_path(p);
-	if(!filemap)
-		return 1;
+			
 	do
 	{
 		for(t=filemap; *t;)
 		{
 			if(__darwintrace_strbeginswith(p, t))
 			{
-				t+=strlen(t);
+				t+=strlen(t)+1;
 				switch(*t)
 				{
 				case 0:
@@ -536,7 +559,7 @@
 						result=0;
 						break;
 					}
-					strcpy(newpath, p+1);
+					strcpy(newpath, t+1);
 					_=newpath+strlen(newpath);
 					if(_[-1]!='/')
 						*_++='/';
@@ -581,6 +604,9 @@
 	mode_t mode;
 	int result;
 	va_list args;
+	struct stat sb;
+	char newpath[MAXPATHLEN];
+	int isInSandbox;	
 
 	/* Why mode here ? */
 	va_start(args, flags);
@@ -588,10 +614,9 @@
 	va_end(args);
 	
 	result = 0;
-	if (flags & (O_CREAT | O_APPEND | O_RDWR | O_WRONLY | O_TRUNC)) {
-		char newpath[MAXPATHLEN];
-		int isInSandbox;
-		
+	
+	if((stat(path, &sb)!=-1 && !(sb.st_mode&S_IFDIR)) || flags & O_CREAT )
+	{
 		*newpath=0;
 		__darwintrace_setup();
 		isInSandbox = __darwintrace_is_in_sandbox(path, newpath);
@@ -641,10 +666,8 @@
 #define __execve(x,y,z) syscall(SYS_execve, (x), (y), (z))
 #define open(x,y,z) syscall(SYS_open, (x), (y), (z))
 #define close(x) syscall(SYS_close, (x))
+#define lstat(x, y) syscall(SYS_lstat, (x), (y))
 	int result;
-#if DARWINTRACE_SHOW_PROCESS
-	int saved_pid;
-#endif
 	__darwintrace_setup();
 	if (__darwintrace_fd >= 0) {
 	  struct stat sb;
@@ -663,8 +686,18 @@
 		
 		fd = open(path, O_RDONLY, 0);
 		if (fd > 0) {
-		  char buffer[MAXPATHLEN+1];
+		  char buffer[MAXPATHLEN+1], newpath[MAXPATHLEN+1];
 		  ssize_t bytes_read;
+		
+		  *newpath=0;
+		  if(__darwintrace_is_in_sandbox(path, newpath)==0)
+		  {
+			close(fd);
+			errno=ENOENT;
+		    return -1;
+		  }
+		  if(*newpath)
+		    path=newpath;
 	
 		  /* once we have an open fd, if a full path was requested, do it */
 		  __darwintrace_log_op("execve", path, fd);
@@ -706,6 +739,7 @@
 	/* call the original execve function, but fix the environment if required. */
 	result = __execve(path, argv, __darwintrace_restore_env(envp));
 	return result;
+#undef lstat
 #undef close
 #undef open
 #undef execve
@@ -726,7 +760,6 @@
 #undef close
 }
 
-#if DARWINTRACE_SANDBOX
 /* Trap attempts to unlink a file outside the sandbox.
  */
 int unlink(const char* path) {
@@ -748,9 +781,7 @@
 	
 	return result;
 }
-#endif
 
-#if DARWINTRACE_SANDBOX
 /* Trap attempts to create directories outside the sandbox.
  */
 int mkdir(const char* path, mode_t mode) {
@@ -780,9 +811,7 @@
 	
 	return result;
 }
-#endif
 
-#if DARWINTRACE_SANDBOX
 /* Trap attempts to remove directories outside the sandbox.
  */
 int rmdir(const char* path) {
@@ -804,9 +833,7 @@
 	
 	return result;
 }
-#endif
 
-#if DARWINTRACE_SANDBOX
 /* Trap attempts to rename files/directories outside the sandbox.
  */
 int rename(const char* from, const char* to) {
@@ -840,4 +867,49 @@
 	
 	return result;
 }
-#endif
+
+int stat(const char * path, struct stat * sb)
+{
+#define stat(path, sb) syscall(SYS_stat, path, sb)
+	int result=0;
+	char newpath[260];
+		
+	*newpath=0;
+	if(!is_directory(path)&&__darwintrace_is_in_sandbox(path, newpath)==0)
+	{
+		errno=ENOENT;
+		result=-1;
+	}else
+	{
+		if(*newpath)
+			path=newpath;
+			
+		result=stat(path, sb);
+	}
+	
+	return result;
+#undef stat
+}
+
+int lstat(const char * path, struct stat * sb)
+{
+#define stat(path, sb) syscall(SYS_lstat, path, sb)
+	int result=0;
+	char newpath[260];
+	
+	*newpath=0;
+	if(!is_directory(path)&&__darwintrace_is_in_sandbox(path, newpath)==0)
+	{
+		errno=ENOENT;
+		result=-1;
+	}else
+	{
+		if(*newpath)
+			path=newpath;
+			
+		result=stat(path, sb);
+	}
+	
+	return result;
+#undef stat
+}
\ No newline at end of file

Modified: trunk/base/src/pextlib1.0/tracelib.c
===================================================================
--- trunk/base/src/pextlib1.0/tracelib.c	2007-08-20 13:11:37 UTC (rev 28090)
+++ trunk/base/src/pextlib1.0/tracelib.c	2007-08-20 14:48:10 UTC (rev 28091)
@@ -50,14 +50,24 @@
 static char * filemap, * filemap_end;
 static char * depends;	
 static int sock=-1;
+static int enable_fence=0;
 static Tcl_Interp * interp;
 static pthread_mutex_t sock_mutex=PTHREAD_MUTEX_INITIALIZER;
 static int cleanuping=0;
+static char * sdk=
+#ifdef TRACE_SDK
+	/*"MacOSX10.4u.sdk"*/
+	TRACE_SDK
+#else
+	0
+#endif
+;
 
 static void send_file_map(int sock);
 static void dep_check(int sock, const char * path);
 static void sandbox_violation(int sock, const char * path);
 static void ui_warn(const char * format, ...);
+static void ui_info(const char * format, ...);
 
 #define MAX_SOCKETS ((FD_SETSIZE)-1)
 
@@ -112,27 +122,6 @@
 }
 
 /*
- * Check if file in sandbox or not
- * Return:
- *  0 - not in sandbox
- *  1 - in sandbox
- */
-static char is_in_sandbox(char * file)
-{
-	char * t;
-	int flen=strlen(file);
-	
-	for(t=sandbox; *t; t+=strlen(t)+1)
-	{
-		int tlen=strlen(t);
-		if(!strncmp(file, t, tlen<flen?tlen:flen))
-			return 1;
-	}
-	return 0;
-}
-
-
-/*
  * receive line from socket, parse it and send answer
  */
 static char process_line(int sock)
@@ -146,11 +135,14 @@
 		return 0;
 	buf[len]=0;
 	/* sometimes two messages come in one recv.. I ain't caring about it now, but it can be a problem */
-	for(t=buf;*t&&t-buf<sizeof(buf);t+=strlen(f)+1)
+	for(t=buf;*t&&t-buf<(int)sizeof(buf);t=f+strlen(f)+1)
 	{
 		f=strchr(t, '\t');
 		if(!f)
+		{
+			ui_warn("malformed command %s", t);
 			break;
+		}
 		*f++=0;
 		if(!strcmp(t, "filemap"))
 		{
@@ -176,24 +168,44 @@
 
 static void send_file_map(int sock)
 {
-	/*
-	 * TODO: redirect for SDK here 
-	 * TODO: /opt -> path from config
-	 */
 	if(!filemap)
 	{
-		char * t;
-		char * _;
+		char * t, * _;
 		
 		filemap=(char*)malloc(1024);
 		t=filemap;
 		
 		#define append_allow(path, resolution) do{strcpy(t, path); t+=strlen(t)+1; *t++=resolution; *t++=0;}while(0);
-		for(_=sandbox; *_; _+=strlen(_)+1)
-			append_allow(_, 0);
-		append_allow("/opt", 2);
-		/*Allow /usr for now*/
-		append_allow("/usr", 0);
+		if(enable_fence)
+		{
+			for(_=sandbox; *_; _+=strlen(_)+1)
+				append_allow(_, 0);
+			
+			append_allow("/bin", 0);
+			append_allow("/sbin", 0);
+			append_allow("/dev", 0)
+			append_allow(Tcl_GetVar(interp, "macports::prefix", TCL_GLOBAL_ONLY), 2);
+			/* If there is no SDK we will allow everything in /usr /System/Library etc, else add binaries to allow, and redirect root to SDK. */
+			if(sdk&&*sdk)
+			{
+				char buf[260]="/Developer/SDKs/";
+				strcat(buf, sdk);
+			
+				append_allow("/usr/bin", 0);
+				append_allow("/usr/sbin", 0);
+				append_allow("/usr/libexec/gcc", 0);
+				append_allow("/", 1);
+				strcpy(t-1, buf);
+				t+=strlen(t)+1;
+			}else
+			{
+				append_allow("/usr", 0);
+				append_allow("/System/Library", 0);
+				append_allow("/Library", 0);
+				append_allow("/Developer/Headers", 0);
+			}
+		}else
+			append_allow("/", 0);
 		filemap_end=t;
 		#undef append_allow
 	}
@@ -205,32 +217,90 @@
 	}
 }
 
-static void sandbox_violation(int sock, const char * path)
+static void sandbox_violation(int sock UNUSED, const char * path)
 {
-	char buf[1024];
-	sprintf(buf, "slave_add_sandbox_violation {%s}", path);
-	Tcl_Eval(interp, buf);
+	Tcl_SetVar(interp, "path", path, 0);
+	Tcl_Eval(interp, "slave_add_sandbox_violation $path");
+	Tcl_UnsetVar(interp, "path", 0);
 }
 
 static void dep_check(int sock, const char * path)
 {
+	char * port=0;
 	size_t len=1;
-	send(sock, &len, sizeof(len), 0);
-	send(sock, "+", 1, 0);
+	char resolution; 
+	
+	/* If there aren't deps then allow anything. (Useful for extract) */
+	if(!depends)
+		resolution='+';
+	else
+	{
+		resolution='!';
+		
+		Tcl_SetVar(interp, "path", path, 0);
+		Tcl_Eval(interp, "registry::file_registered $path");
+		port=strdup(Tcl_GetStringResult(interp));
+		Tcl_UnsetVar(interp, "path", 0);
+	
+		if(*port!='0'||port[1])
+		{
+			char * t;
+		
+			t=depends;
+			for(;*t;t+=strlen(t)+1)
+			{
+				if(!strcmp(t, port))
+				{
+					resolution='+';
+					break;
+				}
+			}
+		}
+	}
+	
+	if(resolution!='+')
+		ui_info("trace: access denied to %s (%s)", path, port);
+
+	if(port)
+		free(port);
+	
+	if(send(sock, &len, sizeof(len), 0)==-1)
+		ui_warn("tracelib send failed");
+	if(send(sock, &resolution, 1, 0)==-1)
+		ui_warn("tracelib send failed");
 }
 
+static void ui_msg(const char * severity, const char * format, va_list va)
+{
+	char buf[1024], tclcmd[32];
+	
+	vsprintf(buf, format, va);
+	
+	sprintf(tclcmd, "ui_%s $warn", severity);
+	
+	Tcl_SetVar(interp, "warn", buf, 0);
+	
+	Tcl_Eval(interp, tclcmd);
+	Tcl_UnsetVar(interp, "warn", 0);
+	
+}
+
 static void ui_warn(const char * format, ...)
 {
-	char buf[1024];
 	va_list va;
 	
-	strcpy(buf, "ui_warn {");
 	va_start(va, format);
-		vsprintf(buf+strlen(buf), format, va);
+		ui_msg("warn", format, va);
 	va_end(va);
-	strcat(buf, "}");
+}
+
+static void ui_info(const char * format, ...)
+{
+	va_list va;
 	
-	Tcl_Eval(interp, buf);
+	va_start(va, format);
+		ui_msg("msg", format, va);
+	va_end(va);
 }
 
 static int TracelibRunCmd(Tcl_Interp * in)
@@ -257,6 +327,7 @@
 	{
 		ui_warn("setrlimit failed (%d)", errno);
 	}
+
 	
 	sun.sun_family=AF_UNIX;
 	strcpy(sun.sun_path, name);
@@ -311,7 +382,10 @@
 			if(i==max_used)
 			{
 				if(max_used==MAX_SOCKETS-1)
+				{
+					ui_warn("There is no place to store socket");
 					close(s);
+				}
 				else
 					socks[max_used++]=s;
 			}
@@ -352,7 +426,7 @@
 	pthread_mutex_lock(&sock_mutex);
 	if(sock!=-1)
 	{
-		shutdown(sock, SHUT_RDWR);
+		/* shutdown(sock, SHUT_RDWR);*/
 		close(sock);
 		sock=-1;
 	}
@@ -364,6 +438,7 @@
 	}
 	if(filemap)
 		safe_free(filemap);
+	enable_fence=0;
 	#undef safe_free
 	cleanuping=0;
 	return TCL_OK;
@@ -375,7 +450,7 @@
 	pthread_mutex_lock(&sock_mutex);
 	if(sock!=-1)
 	{
-		shutdown(sock, SHUT_RDWR);
+		/*shutdown(sock, SHUT_RDWR);*/
 		close(sock);
 		sock=-1;
 	}
@@ -383,17 +458,50 @@
 	return TCL_OK;
 }
 
+static int TracelibSetDeps(Tcl_Interp * interp UNUSED, int objc, Tcl_Obj* CONST objv[])
+{
+	char * t, * d;
+	size_t l;
+	if(objc!=3)
+	{
+		Tcl_WrongNumArgs(interp, 2, objv, "number of arguments should be exactly 3");
+		return TCL_ERROR;
+	}
+	
+	d=Tcl_GetString(objv[2]);
+	l=strlen(d);
+	depends=malloc(l+2);
+	depends[l+1]=0;
+	strcpy(depends, d);
+	for(t=depends;*t;++t)
+		if(*t==' ')
+			*t++=0;
+	
+	return TCL_OK;
+}
+
+static int TracelibEnableFence(Tcl_Interp * interp UNUSED)
+{
+	enable_fence=1;
+	if(filemap)
+		free(filemap);
+	filemap=0;
+	return TCL_OK;
+}
+
 int TracelibCmd(ClientData clientData UNUSED, Tcl_Interp* interp, int objc, Tcl_Obj* CONST objv[])
 {
 	int result=TCL_OK;
-	static const char * options[]={"setname", "run", "clean", "setsandbox", "closesocket", 0};
+	static const char * options[]={"setname", "run", "clean", "setsandbox", "closesocket", "setdeps", "enablefence", 0};
 	typedef enum 
 	{
 		kSetName,
 		kRun,
 		kClean,
 		kSetSandbox,
-		kCloseSocket
+		kCloseSocket,
+		kSetDeps,
+		kEnableFence
 	} EOptions;
 	EOptions current_option;
 	
@@ -424,6 +532,12 @@
 		case kSetSandbox:
 			result=TracelibSetSandboxCmd(interp, objc, objv);
 			break;
+		case kSetDeps:
+			result=TracelibSetDeps(interp, objc, objv);
+			break;
+		case kEnableFence:
+			result=TracelibEnableFence(interp);
+			break;
 		}
 	}
 	

Modified: trunk/base/src/pextlib1.0/tracelib.h
===================================================================
--- trunk/base/src/pextlib1.0/tracelib.h	2007-08-20 13:11:37 UTC (rev 28090)
+++ trunk/base/src/pextlib1.0/tracelib.h	2007-08-20 14:48:10 UTC (rev 28091)
@@ -48,6 +48,14 @@
  *			- run select, create a socket
  *		tracelib clean
  *			- cleanup everything
+ * 		tracelib setsandbox
+ *			- set sandbox bounds
+ *  	tracelib closesocket
+ *			- close socket. This makes main thread to quit from it's loop
+ *		tracelib setdeps
+ * 			- set deps for current port
+ * 		tracelib enablefence
+ *			- enable dep/sandbox checking
  */
 int TracelibCmd(ClientData clientData, Tcl_Interp* interp, int objc, Tcl_Obj* CONST objv[]);
 

Modified: trunk/base/src/port1.0/porttrace.tcl
===================================================================
--- trunk/base/src/port1.0/porttrace.tcl	2007-08-20 13:11:37 UTC (rev 28090)
+++ trunk/base/src/port1.0/porttrace.tcl	2007-08-20 14:48:10 UTC (rev 28091)
@@ -45,7 +45,7 @@
 			# Create a fifo.
 			# path in unix socket limited to 109 chars
 			# # set trace_fifo "$workpath/trace_fifo"
-			set trace_fifo "/tmp/macports/[pid]" 
+			set trace_fifo "/tmp/macports/[pid]_[expr {int(rand()*1000)}]" 
 			file mkdir "/tmp/macports"
 			file delete -force $trace_fifo
 			
@@ -86,7 +86,8 @@
 # Only done for targets that should only happen in the sandbox.
 proc trace_enable_fence {} {
 	global env trace_sandboxbounds
-	set env(DARWINTRACE_SANDBOX_BOUNDS) $trace_sandboxbounds	
+	set env(DARWINTRACE_SANDBOX_BOUNDS) $trace_sandboxbounds
+	tracelib enablefence
 }
 
 # Disable the fence.

Modified: trunk/base/src/port1.0/portutil.tcl
===================================================================
--- trunk/base/src/port1.0/portutil.tcl	2007-08-20 13:11:37 UTC (rev 28090)
+++ trunk/base/src/port1.0/portutil.tcl	2007-08-20 14:48:10 UTC (rev 28091)
@@ -1095,7 +1095,15 @@
 		# otherwise execute the task.
 		if {$skipped == 0} {
 			set target [ditem_key $ditem provides]
-			if {([info exists ports_trace]
+			
+			# Execute pre-run procedure
+			if {[ditem_contains $ditem prerun]} {
+			set result [catch {[ditem_key $ditem prerun] $name} errstr]
+			}		
+			
+			#start tracelib
+			if {($result ==0 
+			    && [info exists ports_trace]
 				&& $ports_trace == "yes"
 				&& $target != "clean")} {
 				trace_start $workpath
@@ -1108,49 +1116,8 @@
 					&& $target != "install"} {
 					trace_enable_fence
 				}
-			}
-
-			# Execute pre-run procedure
-			if {[ditem_contains $ditem prerun]} {
-			set result [catch {[ditem_key $ditem prerun] $name} errstr]
-			}
 			
-			if {$result == 0} {
-			foreach pre [ditem_key $ditem pre] {
-				ui_debug "Executing $pre"
-				set result [catch {$pre $name} errstr]
-				if {$result != 0} { break }
-			}
-			}
-			
-			if {$result == 0} {
-			ui_debug "Executing $name ($portname)"
-			set result [catch {$procedure $name} errstr]
-			}
-			
-			if {$result == 0} {
-			foreach post [ditem_key $ditem post] {
-				ui_debug "Executing $post"
-				set result [catch {$post $name} errstr]
-				if {$result != 0} { break }
-			}
-			}
-			# Execute post-run procedure
-			if {[ditem_contains $ditem postrun] && $result == 0} {
-			set postrun [ditem_key $ditem postrun]
-			ui_debug "Executing $postrun"
-			set result [catch {$postrun $name} errstr]
-			}
-
-			# *** move it to good position
-			#   Why do we need it? It closes socket, and exit from our C thread
-			tracelib closesocket
-			# ***
-			
-			# Check dependencies & file creations outside workpath.
-			if {[info exists ports_trace]
-				&& $ports_trace == "yes"
-				&& $target != "clean"} {
+				# collect deps
 				
 				# Don't check dependencies for extract (they're not honored
 				# anyway). This avoids warnings about bzip2.
@@ -1192,9 +1159,50 @@
 						set dep_portname [lindex [split $depspec :] end]
 						lappend depsPorts $dep_portname
 					}
-					trace_check_deps $target $depsPorts
+					
+					set portlist [recursive_collect_deps $portname $deptypes]
+					#uniquer from http://aspn.activestate.com/ASPN/Cookbook/Tcl/Recipe/147663
+					array set a [split "[join $portlist {::}]:" {:}]
+					set depsPorts [array names a]
+					
+					if {[llength $deptypes] > 0} {tracelib setdeps $depsPorts}
 				}
+			}
+			
+			if {$result == 0} {
+			foreach pre [ditem_key $ditem pre] {
+				ui_debug "Executing $pre"
+				set result [catch {$pre $name} errstr]
+				if {$result != 0} { break }
+			}
+			}
+			
+			if {$result == 0} {
+			ui_debug "Executing $name ($portname)"
+			set result [catch {$procedure $name} errstr]
+			}
+			
+			if {$result == 0} {
+			foreach post [ditem_key $ditem post] {
+				ui_debug "Executing $post"
+				set result [catch {$post $name} errstr]
+				if {$result != 0} { break }
+			}
+			}
+			# Execute post-run procedure
+			if {[ditem_contains $ditem postrun] && $result == 0} {
+			set postrun [ditem_key $ditem postrun]
+			ui_debug "Executing $postrun"
+			set result [catch {$postrun $name} errstr]
+			}
+
+			# Check dependencies & file creations outside workpath.
+			if {[info exists ports_trace]
+				&& $ports_trace == "yes"
+				&& $target!="clean"} {
 				
+				tracelib closesocket
+				
 				trace_check_violations
 				
 				# End of trace.
@@ -1225,6 +1233,38 @@
     return $result
 }
 
+# recursive found depends for portname
+# It isn't ideal, because it scan many ports multiple time
+proc recursive_collect_deps {portname deptypes} \
+{
+	set res [mport_search ^$portname\$]
+    if {[llength $res] < 2} \
+	{
+        return {}
+    }
+
+	set depends {}
+
+	array set portinfo [lindex $res 1]
+	foreach deptype $deptypes \
+	{
+		if {[info exists portinfo($deptype)] && $portinfo($deptype) != ""} \
+		{
+			set depends [concat $depends $portinfo($deptype)]
+		}
+	}
+	
+	set portdeps {}
+	foreach depspec $depends \
+	{
+		set portname [lindex [split $depspec :] end]
+		lappend portdeps $portname
+		set portdeps [concat $portdeps [recursive_collect_deps $portname $deptypes]]
+	}
+	return $portdeps
+}
+
+
 proc eval_targets {target} {
     global targets target_state_fd portname
     set dlist $targets

-------------- next part --------------
An HTML attachment was scrubbed...
URL: http://lists.macosforge.org/pipermail/macports-changes/attachments/20070820/0289d6d7/attachment.html


More information about the macports-changes mailing list