File:  [Coherent Logic Development] / freem / src / locktab.c
Revision 1.9: download - view: text, annotated - select for diffs
Thu May 15 02:49:11 2025 UTC (2 months, 2 weeks ago) by snw
Branches: MAIN
CVS tags: HEAD
Fix linker error on errno symbol in locktab.c

    1: /*
    2:  *   $Id: locktab.c,v 1.9 2025/05/15 02:49:11 snw Exp $
    3:  *    lock table implementation
    4:  *
    5:  *  
    6:  *   Author: Serena Willis <snw@coherent-logic.com>
    7:  *    Copyright (C) 1998 MUG Deutschland
    8:  *    Copyright (C) 2021, 2025 Coherent Logic Development LLC
    9:  *
   10:  *
   11:  *   This file is part of FreeM.
   12:  *
   13:  *   FreeM is free software: you can redistribute it and/or modify
   14:  *   it under the terms of the GNU Affero Public License as published by
   15:  *   the Free Software Foundation, either version 3 of the License, or
   16:  *   (at your option) any later version.
   17:  *
   18:  *   FreeM is distributed in the hope that it will be useful,
   19:  *   but WITHOUT ANY WARRANTY; without even the implied warranty of
   20:  *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   21:  *   GNU Affero Public License for more details.
   22:  *
   23:  *   You should have received a copy of the GNU Affero Public License
   24:  *   along with FreeM.  If not, see <https://www.gnu.org/licenses/>.
   25:  *
   26:  *   $Log: locktab.c,v $
   27:  *   Revision 1.9  2025/05/15 02:49:11  snw
   28:  *   Fix linker error on errno symbol in locktab.c
   29:  *
   30:  *   Revision 1.8  2025/05/14 15:28:55  snw
   31:  *   Get basic job table and lock table functionality working with new shared memory architecture
   32:  *
   33:  *   Revision 1.7  2025/05/01 21:02:31  snw
   34:  *   Documentation updates
   35:  *
   36:  *   Revision 1.6  2025/04/13 04:22:43  snw
   37:  *   Fix snprintf calls
   38:  *
   39:  *   Revision 1.5  2025/03/24 02:57:25  snw
   40:  *   Shared memory compatibility fixes for OS/2
   41:  *
   42:  *   Revision 1.4  2025/03/09 19:14:25  snw
   43:  *   First phase of REUSE compliance and header reformat
   44:  *
   45:  *
   46:  * SPDX-FileCopyrightText:  (C) 2025 Coherent Logic Development LLC
   47:  * SPDX-License-Identifier: AGPL-3.0-or-later
   48:  **/
   49: 
   50: #include <stdio.h>
   51: #include <stdlib.h>
   52: #include <unistd.h>
   53: #include <time.h>
   54: #include <string.h>
   55: #include <errno.h>
   56: 
   57: #include "mpsdef.h"
   58: #include "locktab.h"
   59: #include "shmmgr.h"
   60: #include "mref.h"
   61: #include "transact.h"
   62: #include "log.h"
   63: 
   64: #if !defined(__OpenBSD__) && !defined(__APPLE__) && !defined(__OS2__)
   65: union semun {
   66:     int              val;    /* Value for SETVAL */
   67:     struct semid_ds *buf;    /* Buffer for IPC_STAT, IPC_SET */
   68:     unsigned short  *array;  /* Array for GETALL, SETALL */
   69:     struct seminfo  *__buf;  /* Buffer for IPC_INFO
   70:                                 (Linux-specific) */
   71: };
   72: #endif
   73: 
   74: int semid_locktab;
   75: 
   76: int locktab_list_count(char *key);
   77: 
   78: void locktab_init(void)
   79: {
   80:     union semun arg;
   81:     key_t lt_sk;
   82: 
   83:     lt_sk = ftok (config_file, 3);
   84:     
   85:     if (first_process) {
   86: 
   87:         semid_locktab = semget (lt_sk, 1, 0666 | IPC_CREAT);
   88:         if (semid_locktab == -1) {
   89:             logprintf (FM_LOG_FATAL, "locktab_init:  failed to create lock table semaphore");
   90:         }
   91: 
   92:         arg.val = 1;
   93:         if (semctl (semid_locktab, 0, SETVAL, arg) == -1) {
   94:             logprintf (FM_LOG_FATAL, "locktab_init:  failed to initialize lock table semaphore");
   95:         }
   96:         
   97:     }
   98:     else {
   99: 
  100:         semid_locktab = semget (lt_sk, 1, 0);
  101:         if (semid_locktab == -1) {
  102:             logprintf (FM_LOG_FATAL, "locktab_init:  could not attach to lock table semaphore");
  103:         }
  104:         
  105:     }
  106:     
  107:     return;
  108:     
  109: }
  110: 
  111: short locktab_get_sem(void)
  112: {
  113:     int tries;
  114:     struct sembuf s = {0, -1, 0};
  115: 
  116:     logprintf (FM_LOG_DEBUG, "locktab_get_sem:  attempting to acquire lock table semaphore");
  117:     
  118:     for (tries = 0; tries < 5; tries++) {
  119: 
  120:         if (semop (semid_locktab, &s, 1) != -1) {
  121:             logprintf (FM_LOG_DEBUG, "locktab_get_sem:  acquired semaphore by virtue of calling semop on it");
  122:             return TRUE;
  123:         }
  124: 
  125:         logprintf (FM_LOG_INFO, "locktab_get_sem:  sleeping for retry [tries = %d; errno = %d; error = %s]", tries, errno, strerror (errno));
  126:         sleep (1);
  127: 
  128:     }
  129:     logprintf (FM_LOG_ERROR, "locktab_get_sem:  failed to acquire job table semaphore");
  130:     
  131:     return FALSE;
  132: }
  133: 
  134: void locktab_release_sem(void)
  135: {
  136:     struct sembuf s = {0, 1, 0};
  137: 
  138:     logprintf (FM_LOG_DEBUG, "locktab_release_sem:  trying to release lock table semaphore");
  139:     if(semop (semid_locktab, &s, 1) != - 1) {
  140:         logprintf (FM_LOG_DEBUG, "locktab_release_sem:  released lock table semaphore by means of semop");
  141:     }
  142:     else {
  143:         logprintf (FM_LOG_FATAL, "locktab_release_sem:  failed to release lock table semaphore (error code %d [%s])", errno, strerror (errno));
  144:     }
  145: 
  146: }
  147: 
  148: void lock(char *lockarg, long time_out, char type)
  149: {
  150:     char *key = &(lockarg[1]);
  151:     char a = lockarg[0];
  152:     
  153:     if (shm_config == NULL) {
  154:         fprintf (stderr, "lock:  global LOCK operation attemped before shared memory available.\r\n");
  155:         return;
  156:     }
  157: 
  158:     switch (a) {
  159: 
  160:         case '+':
  161:             locktab_increment (key, time_out, FALSE);
  162:             break;
  163:             
  164:         case '-':
  165:             locktab_decrement (key, time_out);
  166:             break;
  167:             
  168:         case SP:
  169:             locktab_unlock_all ();
  170:             locktab_increment (key, time_out, TRUE);
  171:             break;
  172: 
  173:         default:
  174:             break;
  175:             
  176:     }
  177: 
  178:     return;
  179:     
  180: }
  181: 
  182: void locktab_increment(char *key, long lck_timeout, short old_lock)
  183: {
  184:     short lck_action;
  185:     char chk_ns[256];
  186:     int nref_ct = locktab_list_count (key);
  187: 
  188:     if (old_lock) {
  189:         lck_action = lock_old;
  190:     }
  191:     else {
  192:         lck_action = lock_inc;
  193:     }
  194: 
  195:     strncpy (chk_ns, nsname, 256);
  196:     
  197:     switch (lck_timeout) {
  198:             
  199:         case -1: /* blocking lock (no timeout) */
  200: 
  201:             if (nref_ct > 1) {
  202:                 /* this is a lock list */
  203:                 char *nref;
  204:                 char tmps[255];
  205: 
  206:                 int i;        
  207:                 int successes = 0;
  208:                 int attempts = 0;
  209:                 int list_pos = 0;
  210:                 
  211:                 char *attempt_status = (char *) malloc (nref_ct * sizeof (char));
  212:                 NULLPTRCHK(attempt_status,"locktab_increment");
  213: 
  214:                 for (i = 0; i < nref_ct; i++) attempt_status[i] = (char) FALSE;
  215:                 
  216:                 stcpy (tmps, key);
  217:                 stcnv_m2c (tmps);                               
  218:                 
  219:                 nref = strtok (tmps, "\001\201");
  220: 
  221:                 do {
  222:                     
  223:                     list_pos = 0;                    
  224:                     attempts = 0;
  225:                     successes = 0;
  226:                     
  227:                     for (;;) {                    
  228: 
  229:                         attempts++;
  230:                        
  231:                         if (attempt_status[list_pos] == FALSE) {
  232:                             
  233:                             if (locktab_insert (nref) != NULL) {
  234:                                 successes++;
  235:                                 attempt_status[list_pos] = TRUE;
  236:                             }
  237:                             else {
  238:                                 locktab_decrement (nref, -1L);
  239:                                 attempt_status[list_pos] = FALSE;
  240:                             }
  241: 
  242:                         }
  243:                         
  244:                         nref = strtok (NULL, "\001\201");                    
  245:                         if (nref == NULL) break;
  246: 
  247:                         list_pos++;
  248:                         
  249:                     }                        
  250: 
  251:                 } while (successes < nref_ct);
  252: 
  253:                 free (attempt_status);
  254:                 if (tp_level > 0) tp_add_op (TRUE, lck_action, key, chk_ns);
  255:                 return;
  256:                 
  257:             }
  258:             else {
  259:                 int sigint_ct = 0;
  260:                 
  261:                 for (;;) {
  262:                     
  263:                     if (locktab_insert (key) != NULL) {
  264:                         if (tp_level > 0) tp_add_op (TRUE, lck_action, key, chk_ns);
  265:                         return;
  266:                     }
  267:                     else {
  268:                         if (merr () == INRPT) {
  269:                             sigint_ct++;
  270: 
  271:                             if (sigint_ct == 1) {
  272:                                 printf ("\r\nlocktab_insert:  got Ctrl-C in blocking LOCK; Ctrl-C again to force the process to halt\r\n");
  273:                                 merr_clear ();
  274:                             }
  275:                             else {
  276:                                 printf ("\r\n");
  277:                                 logprintf (FM_LOG_FATAL, "locktab_insert:  blocking LOCK was interrupted; forcing halt");
  278:                             }
  279:                         }
  280:                         sleep (1);
  281:                     }
  282:                 
  283:                 }
  284:             }
  285: 
  286:             
  287:         case 0: /* lock that returns immediately */
  288: 
  289:             if (nref_ct > 1) {
  290:                 /* this is a lock list */
  291:                 char *nref;
  292:                 char tmps[255];
  293:                 
  294:                 int successes = 0;
  295:                 int attempts = 0;
  296:                 
  297:                 stcpy (tmps, key);
  298:                 stcnv_m2c (tmps);                               
  299:                 
  300:                 nref = strtok (tmps, "\001\201");
  301: 
  302:                 for (;;) {                    
  303:                     attempts++;
  304: 
  305:                     if (locktab_insert (nref) != NULL) {
  306:                         successes++;
  307:                     }
  308:                     else {
  309:                         locktab_decrement (nref, 0L);
  310:                         test = 0;
  311:                         return;
  312:                     }
  313:                     
  314:                     nref = strtok (NULL, "\001\201");                    
  315:                     if (nref == NULL) break;
  316:                 }                        
  317: 
  318:                 test = 1;
  319:                 if (tp_level > 0) tp_add_op (TRUE, lck_action, key, chk_ns);      
  320:                 return;
  321:               
  322:             }
  323:             else {
  324:             
  325:                 if (locktab_insert (key) != NULL) {
  326:                     test = 1;
  327:                     if (tp_level > 0) tp_add_op (TRUE, lck_action, key, chk_ns);
  328:                 }
  329:                 else {
  330:                     test = 0;
  331:                 }
  332: 
  333:             }
  334: 
  335:             break;
  336: 
  337:         case 1: /* special case: lock with 1-second timeout */
  338: 
  339:             if (locktab_insert (key) != NULL) {
  340:                 test = 1;            
  341:             }
  342:             else {
  343:                 sleep (1);
  344: 
  345:                 if (locktab_insert (key) != NULL) {
  346:                     test = 1;
  347:                     if (tp_level > 0) tp_add_op (TRUE, lck_action, key, chk_ns);
  348:                 }
  349:                 else {
  350:                     test = 0;
  351:                 }
  352:             }
  353: 
  354:             return;
  355:             
  356:         default: /* lock with timeout */
  357:         {
  358:             time_t start_secs;
  359:             time_t end_secs;
  360:             time_t elapsed;
  361:             locktab_ent_t *lck = NULL;
  362:             
  363:             start_secs = time (NULL);
  364: 
  365:             if (nref_ct > 1) {
  366:                 /* this is a lock-list */
  367:                 printf ("lock-list with timeout\r\n");
  368:             }
  369:             else {
  370:                 
  371:             
  372:                 for (;;) {
  373:                     
  374:                     
  375:                     lck = locktab_insert (key);
  376:                     
  377:                     end_secs = time (NULL);
  378:                     elapsed = end_secs - start_secs;
  379:                     
  380:                     if (lck != NULL) {
  381:                         test = 1;
  382:                         return;
  383:                     }
  384:                     
  385:                     if (elapsed >= lck_timeout) {
  386:                         
  387:                         if (lck == NULL) {
  388:                             test = 0;          
  389:                         }
  390:                         else {
  391:                             if (tp_level > 0) tp_add_op (TRUE, lck_action, key, chk_ns);
  392:                             test = 1;
  393:                         }
  394:                         
  395:                         return;
  396:                         
  397:                     }
  398: 
  399:                     sleep (1); /* prevent CPU pegging */
  400:                 
  401:                 } /* timeout loop */
  402: 
  403:                 return;
  404:             }
  405:             
  406:         } /* lock with timeout */
  407:     } /* switch (timeout) */
  408:             
  409:             
  410: }
  411: 
  412: void locktab_decrement(char *key, long lck_timeout)
  413: {
  414: 
  415:     locktab_ent_t *lck = locktab_find (key);
  416:     
  417:     if (lck != NULL) {
  418: 
  419:         if (tp_level > lck->tp_level) {
  420:             merr_raise (M41);
  421:             return;
  422:         }
  423:         
  424:         if (lck->ct > 0) lck->ct--;       
  425:         
  426:         if (lck->ct == 0) {
  427:             lck->owner_job = 0;
  428:             strcpy (lck->namespace, "<REUSABLE>");
  429: 
  430:             ssvn_lock_remove (lck->nref);
  431:         }
  432: 
  433:         if (lck->owner_job != 0) {
  434:             ssvn_lock_add (lck->nref, lck->owner_job, lck->ct);
  435:         }
  436:         
  437:     }
  438: 
  439:     if (lck_timeout > -1) test = 1;
  440:     
  441: }
  442: 
  443: void locktab_unlock_all(void)
  444: {
  445:     locktab_unlock_all_by_pid (pid);
  446: }
  447: 
  448: void locktab_unlock_all_by_pid(const pid_t target_pid)
  449: {
  450:     locktab_ent_t *lck;
  451:     int remove_ct;
  452: 
  453:     remove_ct = 0;
  454:     if (target_pid != pid) {
  455:         logprintf (FM_LOG_INFO, "locktab_unlock_all_by_pid:  removing all locks for pid %ld", target_pid);
  456:     }
  457: 
  458:     
  459:     for (lck = SOA(shm_config->hdr->locktab_head); lck != NULL; lck = SOA(lck->next)) {
  460: 
  461:         if (lck->owner_job == target_pid) {
  462:             remove_ct++;
  463:             
  464:             if (tp_level > lck->tp_level) {
  465:                 merr_raise (M41);
  466:                 return;
  467:             }
  468:             
  469:             lck->ct = 0;
  470:             lck->owner_job = 0;
  471:             strcpy (lck->namespace, "<REUSABLE>");
  472: 
  473:             ssvn_lock_remove (lck->nref);
  474:             
  475:         }
  476: 
  477:     }
  478: 
  479:     if (target_pid != pid) {
  480:         logprintf (FM_LOG_INFO, "locktab_unlock_all_by_pid:  removed %ld lock(s) for pid %ld", remove_ct, target_pid);
  481:     }
  482: 
  483:     
  484: }
  485: 
  486: locktab_ent_t *locktab_find(char *key)
  487: {
  488: 
  489:     locktab_ent_t *lck;
  490:     char chk_ns[255];
  491: 
  492:     if (key[1] == '%') {
  493:         snprintf (chk_ns, sizeof (chk_ns) - 1, "SYSTEM");
  494:     }
  495:     else {
  496:         snprintf (chk_ns, sizeof (chk_ns) - 1, "%s", nsname);
  497:     }
  498: 
  499:     for (lck = SOA(shm_config->hdr->locktab_head); lck != NULL; lck = SOA(lck->next)) {
  500: 
  501:         if ((stcmp (lck->nref, key) == 0) && (strcmp (lck->namespace, chk_ns) == 0)) {
  502: 
  503:             if (lck->owner_job != pid) {
  504:                 return (locktab_ent_t *) NULL;
  505:             }
  506:             else {
  507:                 return lck;
  508:             }
  509:             
  510:         }
  511:         
  512:     }
  513: 
  514:     return (locktab_ent_t *) NULL;
  515:     
  516: }
  517: 
  518: locktab_ent_t *locktab_insert(char *key)
  519: {
  520:     locktab_ent_t *l;
  521:     char chk_ns[255];
  522: 
  523:     freem_ref_t *ik;
  524:     freem_ref_t *ok;
  525:     
  526:     ik = malloc (sizeof (freem_ref_t));
  527:     NULLPTRCHK(ik,"locktab_insert");
  528: 
  529:     ik = mref_init (ik, MREF_RT_GLOBAL, "");
  530: 
  531:     ok = malloc (sizeof (freem_ref_t));
  532:     NULLPTRCHK(ok,"locktab_insert");    
  533: 
  534:     ik = internal_to_mref (ik, key);
  535:     
  536:     if (key[1] == '%') {
  537:         snprintf (chk_ns, sizeof (chk_ns) - 1, "SYSTEM");
  538:     }
  539:     else {
  540:         snprintf (chk_ns, sizeof (chk_ns) - 1, "%s", nsname);
  541:     }
  542:     
  543:     for (l = SOA(shm_config->hdr->locktab_head); l != NULL; l = SOA(l->next)) {
  544:         ok = mref_init (ok, MREF_RT_GLOBAL, "");
  545:         ok = internal_to_mref (ok, l->nref);
  546:         
  547:         if (((stcmp (l->nref, key) == 0) || (mref_is_descendant (ok, ik) == TRUE)) && (strcmp (l->namespace, chk_ns) == 0)) {
  548:             
  549:             /* nref already owned by another job */
  550:             if ((l->owner_job != pid) && (l->ct > 0)) {
  551:                 free (ik);
  552:                 free (ok);
  553:                 return NULL;
  554:             }
  555:             else {
  556:                 if ((mref_is_descendant (ok, ik)) && (pid == l->owner_job)) {
  557:                     if (locktab_find (key) == NULL) goto new_insert;
  558:                 }
  559:                 
  560:                 /* increment the lock and return */
  561:                 l->ct++;
  562: 
  563:                 /* if this was a lock with a counter of zero belonging to another pid,
  564:                  * re-use it and take ownership of it.
  565:                  */
  566:                 if (l->owner_job != pid) l->owner_job = pid;
  567: 
  568:                 l->tp_level = tp_level;
  569:                 
  570:                 ssvn_lock_add (l->nref, l->owner_job, l->ct);
  571: 
  572:                 free (ik);
  573:                 free (ok);
  574:                 
  575:                 return l;
  576:             }
  577:             
  578:         }
  579: 
  580:     }
  581: 
  582: new_insert:
  583:     /* no lock exists for key: this is a new insert */            
  584:     l = (locktab_ent_t *) shm_alloc (sizeof (locktab_ent_t));
  585:     if (l == (locktab_ent_t *) NULL) {
  586:         free (ik);
  587:         free (ok);
  588:         return (locktab_ent_t *) NULL;
  589:     }
  590: 
  591:     stcpy (l->nref, key);
  592:     snprintf (l->namespace, sizeof (l->namespace) - 1, "%s", chk_ns);
  593:         
  594:     l->owner_job = pid;
  595:     l->ct = 1;
  596:     
  597:     l->next = SBM(SOA(shm_config->hdr->locktab_head));
  598:     shm_config->hdr->locktab_head = SBM(l);
  599: 
  600:     ssvn_lock_add (l->nref, l->owner_job, l->ct);
  601: 
  602:     free (ik);
  603:     free (ok);
  604:     
  605:     return l;
  606: }
  607: 
  608: 
  609: int locktab_count(char *key)
  610: {
  611:     locktab_ent_t *l;
  612:     int ct = 0;
  613: 
  614:     for (l = SOA(shm_config->hdr->locktab_head); l != NULL; l = SOA(l->next)) {
  615:         if (stcmp (l->nref, key) == 0) ct++;
  616:     }
  617: 
  618:     return ct;
  619: }
  620: 
  621: int locktab_list_count(char *key)
  622: {
  623:     int i;
  624:     int lct = 0;
  625:     
  626:     for (i = 0; i < stlen (key); i++) {
  627:         if (key[i] == '\001') lct++;        
  628:     }
  629: 
  630:     return lct;
  631: }
  632: 
  633: unsigned long locktab_pages(void)
  634: {
  635: 
  636:     locktab_ent_t *l;
  637:     unsigned long bytes = 0;
  638:     unsigned long pages = 0;
  639:     float extra;
  640:     
  641:     for (l = SOA(shm_config->hdr->locktab_head); l != NULL; l = SOA(l->next)) {
  642:         bytes += sizeof (locktab_ent_t);
  643:     }
  644: 
  645:     pages = bytes / PG_SIZE;
  646:     extra = bytes % PG_SIZE;
  647:     
  648:     if (extra > 0) {
  649:         pages++;
  650:     }
  651: 
  652:     return pages;
  653:   
  654: }
  655: 
  656: unsigned long locktab_bytes(void)
  657: {
  658: 
  659:     locktab_ent_t *l;
  660:     unsigned int ct = 0;
  661:     unsigned long bytes = 0;
  662: 
  663:     for (l = SOA(shm_config->hdr->locktab_head); l != NULL; l = SOA(l->next)) {
  664:         ct++;
  665:         bytes += sizeof (locktab_ent_t);
  666:     }
  667: 
  668:     return bytes;
  669: 
  670: }
  671: 
  672: void locktab_dump(void)
  673: {
  674:     
  675:     locktab_ent_t *l;
  676:     unsigned long ct = 0;
  677:     unsigned long tot = 0;
  678:     freem_ref_t *r;
  679:     char *ref_ext;
  680:     
  681:     r = (freem_ref_t *) malloc (sizeof (freem_ref_t));
  682:     NULLPTRCHK(r,"locktab_dump");
  683: 
  684:     ref_ext = (char *) malloc (STRLEN * sizeof (char));
  685:     NULLPTRCHK(ref_ext,"locktab_dump");
  686:     
  687:     printf ("%-20s%-20s%-20s%s\r\n", "NAMESPACE", "PID", "COUNT", "KEY");
  688:     printf ("%-20s%-20s%-20s%s\r\n", "---------", "---", "-----", "---");
  689: 
  690:     if (SOA(shm_config->hdr->locktab_head) == NULL) {
  691:         printf ("\r\n*** lock table empty ***\r\n");
  692:         free (r);
  693:         return;
  694:     }
  695: 
  696:     
  697:     for (l = SOA(shm_config->hdr->locktab_head); l != NULL; l = SOA(l->next)) {
  698: 
  699:         mref_init (r, MREF_RT_GLOBAL, "");
  700:         internal_to_mref (r, l->nref);
  701:         mref_to_external (r, ref_ext);
  702: 
  703:         if (l->owner_job) {
  704:             printf ("%-20s%-20d%-20d%s\r\n", l->namespace, l->owner_job, l->ct, ref_ext);
  705:             ct++;
  706:         }
  707:         
  708:         tot++;
  709:         
  710:     }
  711: 
  712:     printf ("\r\n\tActive LOCK table entries:            %ld\r\n", ct);
  713:     printf (    "\tReusable LOCK table entries:          %ld\r\n", tot - ct);
  714:     printf (    "\tShared memory pages:                  %ld\r\n", locktab_pages ());
  715:     printf (    "\tShared memory bytes:                  %ld\r\n", locktab_bytes ());
  716: 
  717:     free (r);
  718:     free (ref_ext);
  719:     
  720:     return;
  721:     
  722: }

FreeBSD-CVSweb <freebsd-cvsweb@FreeBSD.org>