From owner-freebsd-current Sun Mar 5 18: 2: 8 2000 Delivered-To: freebsd-current@freebsd.org Received: from wall.polstra.com (rtrwan160.accessone.com [206.213.115.74]) by hub.freebsd.org (Postfix) with ESMTP id 5366B37B564 for ; Sun, 5 Mar 2000 18:01:43 -0800 (PST) (envelope-from jdp@polstra.com) Received: from vashon.polstra.com (vashon.polstra.com [206.213.73.13]) by wall.polstra.com (8.9.3/8.9.3) with ESMTP id SAA27249; Sun, 5 Mar 2000 18:01:38 -0800 (PST) (envelope-from jdp@polstra.com) From: John Polstra Received: (from jdp@localhost) by vashon.polstra.com (8.9.3/8.9.1) id SAA85727; Sun, 5 Mar 2000 18:01:37 -0800 (PST) (envelope-from jdp@polstra.com) Date: Sun, 5 Mar 2000 18:01:37 -0800 (PST) Message-Id: <200003060201.SAA85727@vashon.polstra.com> To: dmmiller@cvzoom.net Subject: Re: More "ld-elf.so.1: assert failed" messages In-Reply-To: <38BB1E9C.CF44EC93@cvzoom.net> References: <38BA5751.2396AE87@cvzoom.net> <200002290104.RAA60914@vashon.polstra.com> <38BB1E9C.CF44EC93@cvzoom.net> Organization: Polstra & Co., Seattle, WA Cc: current@freebsd.org Sender: owner-freebsd-current@FreeBSD.ORG Precedence: bulk X-Loop: FreeBSD.ORG Below is a patch for "src/libexec/rtld-elf" which should fix the assert failures in wine. I'd appreciate hearing from anybody who tests this with multithreaded packages such as wine, JDK, Mozilla, and linuxthreads. Just a reminder -- be extra careful when messing with the dynamic linker. It's easy to paint yourself into a corner if it's broken badly. Make a backup copy of your current working dynamic linker (/usr/libexec/ld-elf.so.1) before installing the experimental version. Then if things fall apart you can recover with something like this: cd /usr/libexec chflags 0 ld-elf.so.1* mv ld-elf.so.1.good ld-elf.so.1 Thanks in advance for any testing you folks can make time to do. John Index: Makefile =================================================================== RCS file: /home/ncvs/src/libexec/rtld-elf/Makefile,v retrieving revision 1.10 diff -u -r1.10 Makefile --- Makefile 2000/01/29 03:16:54 1.10 +++ Makefile 2000/03/01 02:39:13 @@ -3,7 +3,7 @@ # MAINTAINER= jdp PROG= ld-elf.so.1 -SRCS= rtld_start.S rtld.c lockdflt.c map_object.c malloc.c \ +SRCS= rtld_start.S rtld.c map_object.c malloc.c \ xmalloc.c debug.c reloc.c MAN1= rtld.1 CFLAGS+= -Wall -DFREEBSD_ELF -I${.CURDIR}/${MACHINE_ARCH} -I${.CURDIR} Index: lockdflt.c =================================================================== RCS file: lockdflt.c diff -N lockdflt.c --- /tmp/cvscXuMc22613 Sun Mar 5 17:48:37 2000 +++ /dev/null Sun Mar 5 02:02:18 2000 @@ -1,89 +0,0 @@ -/*- - * Copyright 1999, 2000 John D. Polstra. - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions - * are met: - * 1. Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * 2. Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * - * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR - * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES - * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. - * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, - * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT - * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, - * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY - * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT - * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF - * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - * - * $FreeBSD: src/libexec/rtld-elf/lockdflt.c,v 1.4 2000/01/25 01:32:56 jdp Exp $ - */ - -/* - * Default thread locking implementation for the dynamic linker. It - * is used until the client registers a different implementation with - * dllockinit(). The default implementation does mutual exclusion by - * blocking almost all signals. This is based on the observation that - * most userland thread packages use signals to support preemption. - */ - -#include -#include -#include - -#include "debug.h" -#include "rtld.h" - -typedef struct Struct_LockDflt { - sigset_t lock_mask; - sigset_t old_mask; - int depth; -} LockDflt; - -void -lockdflt_acquire(void *lock) -{ - LockDflt *l = (LockDflt *)lock; - sigprocmask(SIG_BLOCK, &l->lock_mask, &l->old_mask); - assert(l->depth == 0); - l->depth++; -} - -void * -lockdflt_create(void *context) -{ - LockDflt *l; - - l = NEW(LockDflt); - l->depth = 0; - sigfillset(&l->lock_mask); - sigdelset(&l->lock_mask, SIGTRAP); - sigdelset(&l->lock_mask, SIGABRT); - sigdelset(&l->lock_mask, SIGBUS); - sigdelset(&l->lock_mask, SIGSEGV); - sigdelset(&l->lock_mask, SIGKILL); - sigdelset(&l->lock_mask, SIGSTOP); - return l; -} - -void -lockdflt_destroy(void *lock) -{ - LockDflt *l = (LockDflt *)lock; - free(l); -} - -void -lockdflt_release(void *lock) -{ - LockDflt *l = (LockDflt *)lock; - assert(l->depth == 1); - l->depth--; - sigprocmask(SIG_SETMASK, &l->old_mask, NULL); -} Index: rtld.c =================================================================== RCS file: /home/ncvs/src/libexec/rtld-elf/rtld.c,v retrieving revision 1.43 diff -u -r1.43 rtld.c --- rtld.c 2000/01/29 01:26:59 1.43 +++ rtld.c 2000/03/06 01:44:11 @@ -61,6 +61,9 @@ typedef struct Struct_LockInfo { void *context; /* Client context for creating locks */ void *thelock; /* The one big lock */ + /* Debugging aids. */ + volatile int rcount; /* Number of readers holding lock */ + volatile int wcount; /* Number of writers holding lock */ /* Methods */ void (*rlock_acquire)(void *lock); void (*wlock_acquire)(void *lock); @@ -70,6 +73,16 @@ } LockInfo; /* + * This structure provides a reentrant way to keep a list of objects and + * check which ones have already been processed in some way. + */ +typedef struct Struct_DoneList { + Obj_Entry **objs; /* Array of object pointers */ + unsigned int num_alloc; /* Allocated size of the array */ + unsigned int num_used; /* Number of array slots used */ +} DoneList; + +/* * Function declarations. */ static const char *basename(const char *); @@ -77,6 +90,7 @@ static void digest_dynamic(Obj_Entry *); static Obj_Entry *digest_phdr(const Elf_Phdr *, int, caddr_t, const char *); static Obj_Entry *dlcheck(void *); +static bool donelist_check(DoneList *, Obj_Entry *); static char *find_library(const char *, const Obj_Entry *); static void funclist_call(Funclist *); static void funclist_clear(Funclist *); @@ -85,7 +99,7 @@ static void funclist_push_tail(Funclist *, InitFunc); static const char *gethints(void); static void init_dag(Obj_Entry *); -static void init_dag1(Obj_Entry *root, Obj_Entry *obj); +static void init_dag1(Obj_Entry *root, Obj_Entry *obj, DoneList *); static void init_rtld(caddr_t); static bool is_exported(const Elf_Sym *); static void linkmap_add(Obj_Entry *); @@ -93,6 +107,7 @@ static int load_needed_objects(Obj_Entry *); static int load_preload_objects(void); static Obj_Entry *load_object(char *); +static void lock_check(void); static void lock_nop(void *); static Obj_Entry *obj_from_addr(const void *); static void objlist_add(Objlist *, Obj_Entry *); @@ -104,7 +119,7 @@ static char *search_library_path(const char *, const char *); static void set_program_var(const char *, const void *); static const Elf_Sym *symlook_list(const char *, unsigned long, - Objlist *, const Obj_Entry **, bool in_plt); + Objlist *, const Obj_Entry **, bool in_plt, DoneList *); static void trace_loaded_objects(Obj_Entry *obj); static void unload_object(Obj_Entry *); static void unref_dag(Obj_Entry *); @@ -128,7 +143,7 @@ static Obj_Entry **obj_tail; /* Link field of last object in list */ static Obj_Entry *obj_main; /* The main program shared object */ static Obj_Entry obj_rtld; /* The dynamic linker shared object */ -static unsigned long curmark; /* Current mark value */ +static unsigned int obj_count; /* Number of objects in obj_list */ static Objlist list_global = /* Objects dlopened with RTLD_GLOBAL */ STAILQ_HEAD_INITIALIZER(list_global); @@ -167,21 +182,44 @@ char *__progname; char **environ; +/* + * Fill in a DoneList with an allocation large enough to hold all of + * the currently-loaded objects. Keep this as a macro since it calls + * alloca and we want that to occur within the scope of the caller. + */ +#define donelist_init(dlp) \ + ((dlp)->objs = alloca(obj_count * sizeof (dlp)->objs[0]), \ + assert((dlp)->objs != NULL), \ + (dlp)->num_alloc = obj_count, \ + (dlp)->num_used = 0) + static __inline void rlock_acquire(void) { lockinfo.rlock_acquire(lockinfo.thelock); + atomic_incr_int(&lockinfo.rcount); + lock_check(); } static __inline void wlock_acquire(void) { lockinfo.wlock_acquire(lockinfo.thelock); + atomic_incr_int(&lockinfo.wcount); + lock_check(); +} + +static __inline void +rlock_release(void) +{ + atomic_decr_int(&lockinfo.rcount); + lockinfo.lock_release(lockinfo.thelock); } static __inline void -lock_release(void) +wlock_release(void) { + atomic_decr_int(&lockinfo.wcount); lockinfo.lock_release(lockinfo.thelock); } @@ -316,6 +354,7 @@ /* Link the main program into the list of objects. */ *obj_tail = obj_main; obj_tail = &obj_main->next; + obj_count++; obj_main->refcount++; /* Initialize a fake symbol for resolving undefined weak references. */ @@ -366,7 +405,7 @@ funclist_call(&initlist); wlock_acquire(); funclist_clear(&initlist); - lock_release(); + wlock_release(); dbg("transferring control to program entry point = %p", obj_main->entry); @@ -385,7 +424,7 @@ Elf_Addr *where; Elf_Addr target; - wlock_acquire(); + rlock_acquire(); if (obj->pltrel) rel = (const Elf_Rel *) ((caddr_t) obj->pltrel + reloff); else @@ -403,7 +442,7 @@ (void *)target, basename(defobj->path)); reloc_jmpslot(where, target); - lock_release(); + rlock_release(); return target; } @@ -671,6 +710,28 @@ } /* + * If the given object is already in the donelist, return true. Otherwise + * add the object to the list and return false. + */ +static bool +donelist_check(DoneList *dlp, Obj_Entry *obj) +{ + unsigned int i; + + for (i = 0; i < dlp->num_used; i++) + if (dlp->objs[i] == obj) + return true; + /* + * Our donelist allocation should always be sufficient. But if a + * threads package hasn't set up its locking properly, more shared + * objects could have been loaded since we allocated the list. + */ + if (dlp->num_used < dlp->num_alloc) + dlp->objs[dlp->num_used++] = obj; + return false; +} + +/* * Hash function for symbol table lookup. Don't even think about changing * this. It is specified by the System V ABI. */ @@ -741,6 +802,7 @@ find_symdef(unsigned long symnum, Obj_Entry *refobj, const Obj_Entry **defobj_out, bool in_plt) { + DoneList donelist; const Elf_Sym *ref; const Elf_Sym *def; const Elf_Sym *symp; @@ -755,11 +817,11 @@ hash = elf_hash(name); def = NULL; defobj = NULL; - curmark++; + donelist_init(&donelist); - if (refobj->symbolic) { /* Look first in the referencing object */ + /* Look first in the referencing object if linked symbolically. */ + if (refobj->symbolic && !donelist_check(&donelist, refobj)) { symp = symlook_obj(name, hash, refobj, in_plt); - refobj->mark = curmark; if (symp != NULL) { def = symp; defobj = refobj; @@ -768,7 +830,7 @@ /* Search all objects loaded at program start up. */ if (def == NULL || ELF_ST_BIND(def->st_info) == STB_WEAK) { - symp = symlook_list(name, hash, &list_main, &obj, in_plt); + symp = symlook_list(name, hash, &list_main, &obj, in_plt, &donelist); if (symp != NULL && (def == NULL || ELF_ST_BIND(symp->st_info) != STB_WEAK)) { def = symp; @@ -780,7 +842,8 @@ STAILQ_FOREACH(elm, &refobj->dldags, link) { if (def != NULL && ELF_ST_BIND(def->st_info) != STB_WEAK) break; - symp = symlook_list(name, hash, &elm->obj->dagmembers, &obj, in_plt); + symp = symlook_list(name, hash, &elm->obj->dagmembers, &obj, in_plt, + &donelist); if (symp != NULL && (def == NULL || ELF_ST_BIND(symp->st_info) != STB_WEAK)) { def = symp; @@ -790,7 +853,7 @@ /* Search all RTLD_GLOBAL objects. */ if (def == NULL || ELF_ST_BIND(def->st_info) == STB_WEAK) { - symp = symlook_list(name, hash, &list_global, &obj, in_plt); + symp = symlook_list(name, hash, &list_global, &obj, in_plt, &donelist); if (symp != NULL && (def == NULL || ELF_ST_BIND(symp->st_info) != STB_WEAK)) { def = symp; @@ -919,23 +982,24 @@ static void init_dag(Obj_Entry *root) { - curmark++; - init_dag1(root, root); + DoneList donelist; + + donelist_init(&donelist); + init_dag1(root, root, &donelist); } static void -init_dag1(Obj_Entry *root, Obj_Entry *obj) +init_dag1(Obj_Entry *root, Obj_Entry *obj, DoneList *dlp) { const Needed_Entry *needed; - if (obj->mark == curmark) + if (donelist_check(dlp, obj)) return; - obj->mark = curmark; objlist_add(&obj->dldags, root); objlist_add(&root->dagmembers, obj); for (needed = obj->needed; needed != NULL; needed = needed->next) if (needed->obj != NULL) - init_dag1(root, needed->obj); + init_dag1(root, needed->obj, dlp); } /* @@ -971,6 +1035,7 @@ */ obj_list = &obj_rtld; obj_tail = &obj_rtld.next; + obj_count = 1; relocate_objects(&obj_rtld, true); } @@ -978,6 +1043,7 @@ /* Make the object list empty again. */ obj_list = NULL; obj_tail = &obj_list; + obj_count = 0; /* Replace the path with a dynamically allocated copy. */ obj_rtld.path = xstrdup(obj_rtld.path); @@ -1118,6 +1184,7 @@ *obj_tail = obj; obj_tail = &obj->next; + obj_count++; linkmap_add(obj); /* for GDB */ dbg(" %p .. %p: %s", obj->mapbase, @@ -1131,6 +1198,26 @@ return obj; } +/* + * Check for locking violations and die if one is found. + */ +static void +lock_check(void) +{ + int rcount, wcount; + + rcount = lockinfo.rcount; + wcount = lockinfo.wcount; + assert(rcount >= 0); + assert(wcount >= 0); + if (wcount > 1 || (wcount != 0 && rcount != 0)) { + _rtld_error("Application locking error: %d readers and %d writers" + " in dynamic linker. See DLLOCKINIT(3) in manual pages.", + rcount, wcount); + die(); + } +} + static void lock_nop(void *lock) { @@ -1317,7 +1404,7 @@ wlock_acquire(); root = dlcheck(handle); if (root == NULL) { - lock_release(); + wlock_release(); return -1; } @@ -1336,7 +1423,7 @@ if (obj->refcount == 0 && obj->fini != NULL) funclist_push_tail(&finilist, obj->fini); - lock_release(); + wlock_release(); funclist_call(&finilist); wlock_acquire(); funclist_clear(&finilist); @@ -1346,7 +1433,7 @@ unload_object(root); GDB_STATE(RT_CONSISTENT); } - lock_release(); + wlock_release(); return 0; } @@ -1373,10 +1460,8 @@ if (lock_create == NULL) { is_dflt = true; context = NULL; - lock_create = lockdflt_create; - rlock_acquire = wlock_acquire = lockdflt_acquire; - lock_release = lockdflt_release; - lock_destroy = lockdflt_destroy; + rlock_acquire = wlock_acquire = lock_release = lock_nop; + lock_destroy = NULL; context_destroy = NULL; } @@ -1394,17 +1479,15 @@ /* * Make sure the shared objects containing the locking methods are * fully bound, to avoid infinite recursion when they are called - * from the lazy binding code. + * from the lazy binding code. Then allocate the lock we will use. */ if (!is_dflt) { prebind((void *)rlock_acquire); prebind((void *)wlock_acquire); prebind((void *)lock_release); + lockinfo.thelock = lock_create(lockinfo.context); } - /* Allocate our lock. */ - lockinfo.thelock = lock_create(lockinfo.context); - /* Record the new method information. */ lockinfo.context = context; lockinfo.rlock_acquire = rlock_acquire; @@ -1482,11 +1565,11 @@ GDB_STATE(RT_CONSISTENT); /* Call the init functions with no locks held. */ - lock_release(); + wlock_release(); funclist_call(&initlist); wlock_acquire(); funclist_clear(&initlist); - lock_release(); + wlock_release(); return obj; } @@ -1502,14 +1585,14 @@ def = NULL; defobj = NULL; - wlock_acquire(); + rlock_acquire(); if (handle == NULL || handle == RTLD_NEXT) { void *retaddr; retaddr = __builtin_return_address(0); /* __GNUC__ only */ if ((obj = obj_from_addr(retaddr)) == NULL) { _rtld_error("Cannot determine caller's shared object"); - lock_release(); + rlock_release(); return NULL; } if (handle == NULL) { /* Just the caller's shared object. */ @@ -1525,14 +1608,17 @@ } } else { if ((obj = dlcheck(handle)) == NULL) { - lock_release(); + rlock_release(); return NULL; } if (obj->mainprog) { + DoneList donelist; + /* Search main program and all libraries loaded by it. */ - curmark++; - def = symlook_list(name, hash, &list_main, &defobj, true); + donelist_init(&donelist); + def = symlook_list(name, hash, &list_main, &defobj, true, + &donelist); } else { /* * XXX - This isn't correct. The search should include the whole @@ -1544,12 +1630,12 @@ } if (def != NULL) { - lock_release(); + rlock_release(); return defobj->relocbase + def->st_value; } _rtld_error("Undefined symbol \"%s\"", name); - lock_release(); + rlock_release(); return NULL; } @@ -1561,11 +1647,11 @@ void *symbol_addr; unsigned long symoffset; - wlock_acquire(); + rlock_acquire(); obj = obj_from_addr(addr); if (obj == NULL) { _rtld_error("No shared object contains address"); - lock_release(); + rlock_release(); return 0; } info->dli_fname = obj->path; @@ -1604,7 +1690,7 @@ if (info->dli_saddr == addr) break; } - lock_release(); + rlock_release(); return 1; } @@ -1695,7 +1781,7 @@ static const Elf_Sym * symlook_list(const char *name, unsigned long hash, Objlist *objlist, - const Obj_Entry **defobj_out, bool in_plt) + const Obj_Entry **defobj_out, bool in_plt, DoneList *dlp) { const Elf_Sym *symp; const Elf_Sym *def; @@ -1705,9 +1791,8 @@ def = NULL; defobj = NULL; STAILQ_FOREACH(elm, objlist, link) { - if (elm->obj->mark == curmark) + if (donelist_check(dlp, elm->obj)) continue; - elm->obj->mark = curmark; if ((symp = symlook_obj(name, hash, elm->obj, in_plt)) != NULL) { if (def == NULL || ELF_ST_BIND(symp->st_info) != STB_WEAK) { def = symp; @@ -1877,6 +1962,7 @@ munmap(obj->mapbase, obj->mapsize); linkmap_delete(obj); *linkp = obj->next; + obj_count--; obj_free(obj); } else linkp = &obj->next; Index: rtld.h =================================================================== RCS file: /home/ncvs/src/libexec/rtld-elf/rtld.h,v retrieving revision 1.15 diff -u -r1.15 rtld.h --- rtld.h 2000/01/29 01:26:59 1.15 +++ rtld.h 2000/03/01 02:39:27 @@ -149,7 +149,6 @@ Objlist dagmembers; /* DAG has these members (%) */ dev_t dev; /* Object's filesystem's device */ ino_t ino; /* Object's inode number */ - unsigned long mark; /* Set to "curmark" to avoid repeat visits */ } Obj_Entry; #define RTLD_MAGIC 0xd550b87a @@ -170,10 +169,6 @@ const Elf_Sym *find_symdef(unsigned long, Obj_Entry *, const Obj_Entry **, bool); void init_pltgot(Obj_Entry *); -void lockdflt_acquire(void *); -void *lockdflt_create(void *); -void lockdflt_destroy(void *); -void lockdflt_release(void *); void obj_free(Obj_Entry *); Obj_Entry *obj_new(void); int reloc_non_plt(Obj_Entry *, Obj_Entry *); Index: alpha/rtld_machdep.h =================================================================== RCS file: /home/ncvs/src/libexec/rtld-elf/alpha/rtld_machdep.h,v retrieving revision 1.3 diff -u -r1.3 rtld_machdep.h --- alpha/rtld_machdep.h 1999/08/28 00:10:13 1.3 +++ alpha/rtld_machdep.h 2000/03/06 01:37:22 @@ -34,4 +34,8 @@ void reloc_jmpslot(Elf_Addr *, Elf_Addr); +/* Atomically increment / decrement an int. */ +void atomic_incr_int(volatile int *); +void atomic_decr_int(volatile int *); + #endif Index: alpha/rtld_start.S =================================================================== RCS file: /home/ncvs/src/libexec/rtld-elf/alpha/rtld_start.S,v retrieving revision 1.3 diff -u -r1.3 rtld_start.S --- alpha/rtld_start.S 1999/08/28 00:10:13 1.3 +++ alpha/rtld_start.S 2000/03/06 01:37:58 @@ -166,6 +166,24 @@ jmp $31, ($27) .end _rtld_bind_start +/* Atomically increment an int. */ +LEAF(atomic_incr_int, 1) +0: ldl_l t0, 0(a0) + addq t0, 1, t0 + stl_c t0, 0(a0) + beq t0, 1f + mb + RET +1: br 0b + END(atomic_incr_int) - - +/* Atomically decrement an int. */ +LEAF(atomic_decr_int, 1) +0: ldl_l t0, 0(a0) + subq t0, 1, t0 + stl_c t0, 0(a0) + beq t0, 1f + mb + RET +1: br 0b + END(atomic_decr_int) Index: i386/rtld_machdep.h =================================================================== RCS file: /home/ncvs/src/libexec/rtld-elf/i386/rtld_machdep.h,v retrieving revision 1.3 diff -u -r1.3 rtld_machdep.h --- i386/rtld_machdep.h 1999/08/28 00:10:14 1.3 +++ i386/rtld_machdep.h 2000/03/06 01:40:47 @@ -41,4 +41,18 @@ (*(Elf_Addr *)(where) = (Elf_Addr)(target)); \ } while (0) +/* Atomically increment / decrement an int. */ + +static __inline void +atomic_incr_int(volatile int *p) +{ + __asm __volatile("lock; incl %0" : "=m" (*p) : "0" (*p)); +} + +static __inline void +atomic_decr_int(volatile int *p) +{ + __asm __volatile("lock; decl %0" : "=m" (*p) : "0" (*p)); +} + #endif To Unsubscribe: send mail to majordomo@FreeBSD.org with "unsubscribe freebsd-current" in the body of the message