[PATCH] Fixes to the ia32 ABI (and amd64/Linux)
John Baldwin
jhb at FreeBSD.org
Mon Feb 7 15:25:57 PST 2005
Can people test the patch below, it includes various and sundry fixes to the
FreeBSD 32 compat ABI (i.e. FreeBSD/i386 binaries on FreeBSD/amd64 and
FreeBSD/ia64) and the amd64 Linux/i386 ABI as well. I don't expect it to
make anything start working that was broken before, but there shouldn't be
any regressions:
--- //depot/projects/smpng/sys/amd64/linux32/linux32_machdep.c 2004/10/05
19:15:26
+++ //depot/user/jhb/proc/amd64/linux32/linux32_machdep.c 2005/02/04 16:32:42
@@ -34,6 +34,7 @@
#include <sys/param.h>
#include <sys/kernel.h>
#include <sys/systm.h>
+#include <sys/imgact.h>
#include <sys/lock.h>
#include <sys/malloc.h>
#include <sys/mman.h>
@@ -49,6 +50,8 @@
#include <vm/vm.h>
#include <vm/pmap.h>
+#include <vm/vm_extern.h>
+#include <vm/vm_kern.h>
#include <vm/vm_map.h>
#include <amd64/linux32/linux.h>
@@ -89,72 +92,120 @@
return (lsa);
}
-int
-linux_execve(struct thread *td, struct linux_execve_args *args)
+/*
+ * Custom version of exec_copyin_args() so that we can translate
+ * the pointers.
+ */
+static int
+linux_exec_copyin_args(struct image_args *args, char *fname,
+ enum uio_seg segflg, char **argv, char **envv)
{
- struct execve_args ap;
- caddr_t sg;
+ char *argp, *envp;
+ u_int32_t *p32, arg;
+ size_t length;
int error;
- u_int32_t *p32, arg;
- char **p, *p64;
- int count;
+
+ bzero(args, sizeof(*args));
+ if (argv == NULL)
+ return (EFAULT);
- sg = stackgap_init();
- CHECKALTEXIST(td, &sg, args->path);
+ /*
+ * Allocate temporary demand zeroed space for argument and
+ * environment strings
+ */
+ args->buf = (char *) kmem_alloc_wait(exec_map, PATH_MAX + ARG_MAX);
+ if (args->buf == NULL)
+ return (ENOMEM);
+ args->begin_argv = args->buf;
+ args->endp = args->begin_argv;
+ args->stringspace = ARG_MAX;
-#ifdef DEBUG
- if (ldebug(execve))
- printf(ARGS(execve, "%s"), args->path);
-#endif
+ args->fname = args->buf + ARG_MAX;
- ap.fname = args->path;
+ /*
+ * Copy the file name.
+ */
+ error = (segflg == UIO_SYSSPACE) ?
+ copystr(fname, args->fname, PATH_MAX, &length) :
+ copyinstr(fname, args->fname, PATH_MAX, &length);
+ if (error != 0)
+ return (error);
- if (args->argp != NULL) {
- count = 0;
- p32 = (u_int32_t *)args->argp;
- do {
- error = copyin(p32++, &arg, sizeof(arg));
- if (error)
- return error;
- count++;
- } while (arg != 0);
- p = stackgap_alloc(&sg, count * sizeof(char *));
- ap.argv = p;
- p32 = (u_int32_t *)args->argp;
- do {
- error = copyin(p32++, &arg, sizeof(arg));
- if (error)
- return error;
- p64 = PTRIN(arg);
- error = copyout(&p64, p++, sizeof(p64));
- if (error)
- return error;
- } while (arg != 0);
+ /*
+ * extract arguments first
+ */
+ p32 = (u_int32_t *)argv;
+ for (;;) {
+ error = copyin(p32++, &arg, sizeof(arg));
+ if (error)
+ return (error);
+ if (arg == 0)
+ break;
+ argp = PTRIN(arg);
+ error = copyinstr(argp, args->endp, args->stringspace, &length);
+ if (error) {
+ if (error == ENAMETOOLONG)
+ return (E2BIG);
+ else
+ return (error);
+ }
+ args->stringspace -= length;
+ args->endp += length;
+ args->argc++;
}
- if (args->envp != NULL) {
- count = 0;
- p32 = (u_int32_t *)args->envp;
- do {
+
+ args->begin_envv = args->endp;
+
+ /*
+ * extract environment strings
+ */
+ if (envv) {
+ p32 = (u_int32_t *)envv;
+ for (;;) {
error = copyin(p32++, &arg, sizeof(arg));
if (error)
- return error;
- count++;
- } while (arg != 0);
- p = stackgap_alloc(&sg, count * sizeof(char *));
- ap.envv = p;
- p32 = (u_int32_t *)args->envp;
- do {
- error = copyin(p32++, &arg, sizeof(arg));
- if (error)
- return error;
- p64 = PTRIN(arg);
- error = copyout(&p64, p++, sizeof(p64));
- if (error)
- return error;
- } while (arg != 0);
+ return (error);
+ if (arg == 0)
+ break;
+ envp = PTRIN(arg);
+ error = copyinstr(envp, args->endp, args->stringspace,
+ &length);
+ if (error) {
+ if (error == ENAMETOOLONG)
+ return (E2BIG);
+ else
+ return (error);
+ }
+ args->stringspace -= length;
+ args->endp += length;
+ args->envc++;
+ }
}
- return (execve(td, &ap));
+ return (0);
+}
+
+int
+linux_execve(struct thread *td, struct linux_execve_args *args)
+{
+ struct image_args eargs;
+ char *path;
+ int error;
+
+ LCONVPATHEXIST(td, args->path, &path);
+
+#ifdef DEBUG
+ if (ldebug(execve))
+ printf(ARGS(execve, "%s"), path);
+#endif
+
+ error = linux_exec_copyin_args(&eargs, path, UIO_SYSSPACE, args->argp,
+ args->envp);
+ free(path, M_TEMP);
+ if (error == 0)
+ error = kern_execve(td, &eargs, NULL);
+ exec_free_args(&eargs);
+ return (error);
}
struct iovec32 {
@@ -903,36 +954,20 @@
int
linux_nanosleep(struct thread *td, struct linux_nanosleep_args *uap)
{
- struct timespec ats;
+ struct timespec rqt, rmt;
struct l_timespec ats32;
- struct nanosleep_args bsd_args;
int error;
- caddr_t sg;
- caddr_t sarqts, sarmts;
- sg = stackgap_init();
error = copyin(uap->rqtp, &ats32, sizeof(ats32));
if (error != 0)
return (error);
- ats.tv_sec = ats32.tv_sec;
- ats.tv_nsec = ats32.tv_nsec;
- sarqts = stackgap_alloc(&sg, sizeof(ats));
- error = copyout(&ats, sarqts, sizeof(ats));
- if (error != 0)
- return (error);
- sarmts = stackgap_alloc(&sg, sizeof(ats));
- bsd_args.rqtp = (void *)sarqts;
- bsd_args.rmtp = (void *)sarmts;
- error = nanosleep(td, &bsd_args);
+ rqt.tv_sec = ats32.tv_sec;
+ rqt.tv_nsec = ats32.tv_nsec;
+ error = kern_nanosleep(td, &rqt, &rmt);
if (uap->rmtp != NULL) {
- error = copyin(sarmts, &ats, sizeof(ats));
- if (error != 0)
- return (error);
- ats32.tv_sec = ats.tv_sec;
- ats32.tv_nsec = ats.tv_nsec;
+ ats32.tv_sec = rmt.tv_sec;
+ ats32.tv_nsec = rmt.tv_nsec;
error = copyout(&ats32, uap->rmtp, sizeof(ats32));
- if (error != 0)
- return (error);
}
return (error);
}
--- //depot/projects/smpng/sys/amd64/linux32/linux32_sysvec.c 2005/01/31
22:15:49
+++ //depot/user/jhb/proc/amd64/linux32/linux32_sysvec.c 2005/02/04 16:32:42
@@ -744,7 +744,8 @@
exec_linux_imgact_try(struct image_params *imgp)
{
const char *head = (const char *)imgp->image_header;
- int error = -1;
+ char *rpath;
+ int error = -1, len;
/*
* The interpreter for shell scripts run from a linux binary needs
@@ -758,12 +759,10 @@
* path is found, use our stringspace to store it.
*/
if ((error = exec_shell_imgact(imgp)) == 0) {
- char *rpath = NULL;
-
- linux_emul_find(FIRST_THREAD_IN_PROC(imgp->proc), NULL,
- imgp->interpreter_name, &rpath, 0);
- if (rpath != imgp->interpreter_name) {
- int len = strlen(rpath) + 1;
+ linux_emul_convpath(FIRST_THREAD_IN_PROC(imgp->proc),
+ imgp->interpreter_name, UIO_SYSSPACE, &rpath, 0);
+ if (rpath != NULL) {
+ len = strlen(rpath) + 1;
if (len <= MAXSHELLCMDLEN) {
memcpy(imgp->interpreter_name, rpath, len);
--- //depot/projects/smpng/sys/compat/freebsd32/freebsd32_misc.c 2005/01/31
22:15:49
+++ //depot/user/jhb/proc/compat/freebsd32/freebsd32_misc.c 2005/02/04
16:32:42
@@ -222,65 +222,111 @@
return (error);
}
-int
-freebsd32_execve(struct thread *td, struct freebsd32_execve_args *uap)
+/*
+ * Custom version of exec_copyin_args() so that we can translate
+ * the pointers.
+ */
+static int
+freebsd32_exec_copyin_args(struct image_args *args, char *fname,
+ enum uio_seg segflg, u_int32_t *argv, u_int32_t *envv)
{
+ char *argp, *envp;
+ u_int32_t *p32, arg;
+ size_t length;
int error;
- caddr_t sg;
- struct execve_args ap;
- u_int32_t *p32, arg;
- char **p, *p64;
- int count;
+
+ bzero(args, sizeof(*args));
+ if (argv == NULL)
+ return (EFAULT);
+
+ /*
+ * Allocate temporary demand zeroed space for argument and
+ * environment strings
+ */
+ args->buf = (char *) kmem_alloc_wait(exec_map, PATH_MAX + ARG_MAX);
+ if (args->buf == NULL)
+ return (ENOMEM);
+ args->begin_argv = args->buf;
+ args->endp = args->begin_argv;
+ args->stringspace = ARG_MAX;
+
+ args->fname = args->buf + ARG_MAX;
- sg = stackgap_init();
- ap.fname = uap->fname;
+ /*
+ * Copy the file name.
+ */
+ error = (segflg == UIO_SYSSPACE) ?
+ copystr(fname, args->fname, PATH_MAX, &length) :
+ copyinstr(fname, args->fname, PATH_MAX, &length);
+ if (error != 0)
+ return (error);
- if (uap->argv) {
- count = 0;
- p32 = uap->argv;
- do {
- error = copyin(p32++, &arg, sizeof(arg));
- if (error)
- return error;
- count++;
- } while (arg != 0);
- p = stackgap_alloc(&sg, count * sizeof(char *));
- ap.argv = p;
- p32 = uap->argv;
- do {
- error = copyin(p32++, &arg, sizeof(arg));
- if (error)
- return error;
- p64 = PTRIN(arg);
- error = copyout(&p64, p++, sizeof(p64));
- if (error)
- return error;
- } while (arg != 0);
+ /*
+ * extract arguments first
+ */
+ p32 = argv;
+ for (;;) {
+ error = copyin(p32++, &arg, sizeof(arg));
+ if (error)
+ return (error);
+ if (arg == 0)
+ break;
+ argp = PTRIN(arg);
+ error = copyinstr(argp, args->endp, args->stringspace, &length);
+ if (error) {
+ if (error == ENAMETOOLONG)
+ return (E2BIG);
+ else
+ return (error);
+ }
+ args->stringspace -= length;
+ args->endp += length;
+ args->argc++;
}
- if (uap->envv) {
- count = 0;
- p32 = uap->envv;
- do {
+
+ args->begin_envv = args->endp;
+
+ /*
+ * extract environment strings
+ */
+ if (envv) {
+ p32 = envv;
+ for (;;) {
error = copyin(p32++, &arg, sizeof(arg));
if (error)
- return error;
- count++;
- } while (arg != 0);
- p = stackgap_alloc(&sg, count * sizeof(char *));
- ap.envv = p;
- p32 = uap->envv;
- do {
- error = copyin(p32++, &arg, sizeof(arg));
- if (error)
- return error;
- p64 = PTRIN(arg);
- error = copyout(&p64, p++, sizeof(p64));
- if (error)
- return error;
- } while (arg != 0);
+ return (error);
+ if (arg == 0)
+ break;
+ envp = PTRIN(arg);
+ error = copyinstr(envp, args->endp, args->stringspace,
+ &length);
+ if (error) {
+ if (error == ENAMETOOLONG)
+ return (E2BIG);
+ else
+ return (error);
+ }
+ args->stringspace -= length;
+ args->endp += length;
+ args->envc++;
+ }
}
- return execve(td, &ap);
+ return (0);
+}
+
+int
+freebsd32_execve(struct thread *td, struct freebsd32_execve_args *uap)
+{
+ struct image_args eargs;
+ int error;
+
+ error = freebsd32_exec_copyin_args(&eargs, uap->fname, UIO_USERSPACE,
+ uap->argv, uap->envv);
+ if (error == 0)
+ error = kern_execve(td, &eargs, NULL);
+ exec_free_args(&eargs);
+ return (error);
}
#ifdef __ia64__
@@ -437,99 +483,63 @@
int
freebsd32_setitimer(struct thread *td, struct freebsd32_setitimer_args *uap)
{
+ struct itimerval itv, oitv, *itvp;
+ struct itimerval32 i32;
int error;
- caddr_t sg;
- struct itimerval32 *p32, *op32, s32;
- struct itimerval *p = NULL, *op = NULL, s;
- p32 = uap->itv;
- if (p32) {
- sg = stackgap_init();
- p = stackgap_alloc(&sg, sizeof(struct itimerval));
- uap->itv = (struct itimerval32 *)p;
- error = copyin(p32, &s32, sizeof(s32));
+ if (uap->itv != NULL) {
+ error = copyin(uap->itv, &i32, sizeof(i32));
if (error)
return (error);
- TV_CP(s32, s, it_interval);
- TV_CP(s32, s, it_value);
- error = copyout(&s, p, sizeof(s));
- if (error)
- return (error);
- }
- op32 = uap->oitv;
- if (op32) {
- sg = stackgap_init();
- op = stackgap_alloc(&sg, sizeof(struct itimerval));
- uap->oitv = (struct itimerval32 *)op;
- }
- error = setitimer(td, (struct setitimer_args *) uap);
- if (error)
+ TV_CP(i32, itv, it_interval);
+ TV_CP(i32, itv, it_value);
+ itvp = &itv;
+ } else
+ itvp = NULL;
+ error = kern_setitimer(td, uap->which, itvp, &oitv);
+ if (error || uap->oitv == NULL)
return (error);
- if (op32) {
- error = copyin(op, &s, sizeof(s));
- if (error)
- return (error);
- TV_CP(s, s32, it_interval);
- TV_CP(s, s32, it_value);
- error = copyout(&s32, op32, sizeof(s32));
- }
- return (error);
+ TV_CP(oitv, i32, it_interval);
+ TV_CP(oitv, i32, it_value);
+ return (copyout(&i32, uap->oitv, sizeof(i32)));
}
int
freebsd32_getitimer(struct thread *td, struct freebsd32_getitimer_args *uap)
{
+ struct itimerval itv;
+ struct itimerval32 i32;
int error;
- caddr_t sg;
- struct itimerval32 *p32, s32;
- struct itimerval *p = NULL, s;
- p32 = uap->itv;
- if (p32) {
- sg = stackgap_init();
- p = stackgap_alloc(&sg, sizeof(struct itimerval));
- uap->itv = (struct itimerval32 *)p;
- }
- error = getitimer(td, (struct getitimer_args *) uap);
- if (error)
+ error = kern_getitimer(td, uap->which, &itv);
+ if (error || uap->itv == NULL)
return (error);
- if (p32) {
- error = copyin(p, &s, sizeof(s));
- if (error)
- return (error);
- TV_CP(s, s32, it_interval);
- TV_CP(s, s32, it_value);
- error = copyout(&s32, p32, sizeof(s32));
- }
- return (error);
+ TV_CP(itv, i32, it_interval);
+ TV_CP(itv, i32, it_value);
+ return (copyout(&i32, uap->itv, sizeof(i32)));
}
int
freebsd32_select(struct thread *td, struct freebsd32_select_args *uap)
{
+ struct timeval32 tv32;
+ struct timeval tv, *tvp;
int error;
- caddr_t sg;
- struct timeval32 *p32, s32;
- struct timeval *p = NULL, s;
- p32 = uap->tv;
- if (p32) {
- sg = stackgap_init();
- p = stackgap_alloc(&sg, sizeof(struct timeval));
- uap->tv = (struct timeval32 *)p;
- error = copyin(p32, &s32, sizeof(s32));
+ if (uap->tv != NULL) {
+ error = copyin(uap->tv, &tv32, sizeof(tv32));
if (error)
return (error);
- CP(s32, s, tv_sec);
- CP(s32, s, tv_usec);
- error = copyout(&s, p, sizeof(s));
- if (error)
- return (error);
- }
+ CP(tv32, tv, tv_sec);
+ CP(tv32, tv, tv_usec);
+ tvp = &tv;
+ } else
+ tvp = NULL;
/*
* XXX big-endian needs to convert the fd_sets too.
+ * XXX Do pointers need PTRIN()?
*/
- return (select(td, (struct select_args *) uap));
+ return (kern_select(td, uap->nd, uap->in, uap->ou, uap->ex, tvp));
}
struct kevent32 {
@@ -799,28 +809,22 @@
int
freebsd32_utimes(struct thread *td, struct freebsd32_utimes_args *uap)
{
+ struct timeval32 s32[2];
+ struct timeval s[2], *sp;
int error;
- caddr_t sg;
- struct timeval32 *p32, s32[2];
- struct timeval *p = NULL, s[2];
- p32 = uap->tptr;
- if (p32) {
- sg = stackgap_init();
- p = stackgap_alloc(&sg, 2*sizeof(struct timeval));
- uap->tptr = (struct timeval32 *)p;
- error = copyin(p32, s32, sizeof(s32));
+ if (uap->tptr != NULL) {
+ error = copyin(uap->tptr, s32, sizeof(s32));
if (error)
return (error);
CP(s32[0], s[0], tv_sec);
CP(s32[0], s[0], tv_usec);
CP(s32[1], s[1], tv_sec);
CP(s32[1], s[1], tv_usec);
- error = copyout(s, p, sizeof(s));
- if (error)
- return (error);
- }
- return (utimes(td, (struct utimes_args *) uap));
+ sp = s;
+ } else
+ sp = NULL;
+ return (kern_utimes(td, uap->path, UIO_USERSPACE, sp, UIO_SYSSPACE));
}
int
@@ -851,7 +855,7 @@
op = stackgap_alloc(&sg, sizeof(struct timeval));
uap->olddelta = (struct timeval32 *)op;
}
- error = utimes(td, (struct utimes_args *) uap);
+ error = adjtime(td, (struct adjtime_args *) uap);
if (error)
return error;
if (op32) {
@@ -869,28 +873,15 @@
int
freebsd4_freebsd32_statfs(struct thread *td, struct
freebsd4_freebsd32_statfs_args *uap)
{
+ struct statfs32 s32;
+ struct statfs s;
int error;
- caddr_t sg;
- struct statfs32 *p32, s32;
- struct statfs *p = NULL, s;
- p32 = uap->buf;
- if (p32) {
- sg = stackgap_init();
- p = stackgap_alloc(&sg, sizeof(struct statfs));
- uap->buf = (struct statfs32 *)p;
- }
- error = statfs(td, (struct statfs_args *) uap);
+ error = kern_statfs(td, uap->path, UIO_USERSPACE, &s);
if (error)
return (error);
- if (p32) {
- error = copyin(p, &s, sizeof(s));
- if (error)
- return (error);
- copy_statfs(&s, &s32);
- error = copyout(&s32, p32, sizeof(s32));
- }
- return (error);
+ copy_statfs(&s, &s32);
+ return (copyout(&s32, uap->buf, sizeof(s32)));
}
#endif
@@ -898,28 +889,15 @@
int
freebsd4_freebsd32_fstatfs(struct thread *td, struct
freebsd4_freebsd32_fstatfs_args *uap)
{
+ struct statfs32 s32;
+ struct statfs s;
int error;
- caddr_t sg;
- struct statfs32 *p32, s32;
- struct statfs *p = NULL, s;
- p32 = uap->buf;
- if (p32) {
- sg = stackgap_init();
- p = stackgap_alloc(&sg, sizeof(struct statfs));
- uap->buf = (struct statfs32 *)p;
- }
- error = fstatfs(td, (struct fstatfs_args *) uap);
+ error = kern_fstatfs(td, uap->fd, &s);
if (error)
return (error);
- if (p32) {
- error = copyin(p, &s, sizeof(s));
- if (error)
- return (error);
- copy_statfs(&s, &s32);
- error = copyout(&s32, p32, sizeof(s32));
- }
- return (error);
+ copy_statfs(&s, &s32);
+ return (copyout(&s32, uap->buf, sizeof(s32)));
}
#endif
@@ -927,28 +905,18 @@
int
freebsd4_freebsd32_fhstatfs(struct thread *td, struct
freebsd4_freebsd32_fhstatfs_args *uap)
{
+ struct statfs32 s32;
+ struct statfs s;
+ fhandle_t fh;
int error;
- caddr_t sg;
- struct statfs32 *p32, s32;
- struct statfs *p = NULL, s;
- p32 = uap->buf;
- if (p32) {
- sg = stackgap_init();
- p = stackgap_alloc(&sg, sizeof(struct statfs));
- uap->buf = (struct statfs32 *)p;
- }
- error = fhstatfs(td, (struct fhstatfs_args *) uap);
+ if ((error = copyin(uap->u_fhp, &fh, sizeof(fhandle_t))) != 0)
+ return (error);
+ error = kern_fhstatfs(td, fh, &s);
if (error)
return (error);
- if (p32) {
- error = copyin(p, &s, sizeof(s));
- if (error)
- return (error);
- copy_statfs(&s, &s32);
- error = copyout(&s32, p32, sizeof(s32));
- }
- return (error);
+ copy_statfs(&s, &s32);
+ return (copyout(&s32, uap->buf, sizeof(s32)));
}
#endif
@@ -1124,20 +1092,8 @@
struct stat sb;
struct stat32 sb32;
int error;
- struct nameidata nd;
-#ifdef LOOKUP_SHARED
- NDINIT(&nd, LOOKUP, FOLLOW | LOCKSHARED | LOCKLEAF | NOOBJ,
- UIO_USERSPACE, uap->path, td);
-#else
- NDINIT(&nd, LOOKUP, FOLLOW | LOCKLEAF | NOOBJ, UIO_USERSPACE,
- uap->path, td);
-#endif
- if ((error = namei(&nd)) != 0)
- return (error);
- error = vn_stat(nd.ni_vp, &sb, td->td_ucred, NOCRED, td);
- NDFREE(&nd, NDF_ONLY_PNBUF);
- vput(nd.ni_vp);
+ error = kern_stat(td, uap->path, UIO_USERSPACE, &sb);
if (error)
return (error);
copy_stat(&sb, &sb32);
@@ -1148,17 +1104,11 @@
int
freebsd32_fstat(struct thread *td, struct freebsd32_fstat_args *uap)
{
- struct file *fp;
struct stat ub;
struct stat32 ub32;
int error;
- if ((error = fget(td, uap->fd, &fp)) != 0)
- return (error);
- mtx_lock(&Giant);
- error = fo_stat(fp, &ub, td->td_ucred, td);
- mtx_unlock(&Giant);
- fdrop(fp, td);
+ error = kern_fstat(td, uap->fd, &ub);
if (error)
return (error);
copy_stat(&ub, &ub32);
@@ -1169,20 +1119,11 @@
int
freebsd32_lstat(struct thread *td, struct freebsd32_lstat_args *uap)
{
- int error;
- struct vnode *vp;
struct stat sb;
struct stat32 sb32;
- struct nameidata nd;
+ int error;
- NDINIT(&nd, LOOKUP, NOFOLLOW | LOCKLEAF | NOOBJ, UIO_USERSPACE,
- uap->path, td);
- if ((error = namei(&nd)) != 0)
- return (error);
- vp = nd.ni_vp;
- error = vn_stat(vp, &sb, td->td_ucred, NOCRED, td);
- NDFREE(&nd, NDF_ONLY_PNBUF);
- vput(vp);
+ error = kern_lstat(td, uap->path, UIO_USERSPACE, &sb);
if (error)
return (error);
copy_stat(&sb, &sb32);
--
John Baldwin <jhb at FreeBSD.org> <>< http://www.FreeBSD.org/~jhb/
"Power Users Use the Power to Serve" = http://www.FreeBSD.org
More information about the freebsd-amd64
mailing list