Annotation of freem/src/jobtab.c, revision 1.11

1.1       snw         1: /*
1.11    ! snw         2:  *   $Id: jobtab.c,v 1.10 2025/05/12 18:18:00 snw Exp $
1.1       snw         3:  *    job table implementation
                      4:  *
                      5:  *  
1.3       snw         6:  *   Author: Serena Willis <snw@coherent-logic.com>
1.1       snw         7:  *    Copyright (C) 1998 MUG Deutschland
1.4       snw         8:  *    Copyright (C) 2021, 2023, 2025 Coherent Logic Development LLC
1.1       snw         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:  *
1.5       snw        26:  *   $Log: jobtab.c,v $
1.11    ! snw        27:  *   Revision 1.10  2025/05/12 18:18:00  snw
        !            28:  *   Further work on shared memory
        !            29:  *
1.10      snw        30:  *   Revision 1.9  2025/05/01 21:02:31  snw
                     31:  *   Documentation updates
                     32:  *
1.9       snw        33:  *   Revision 1.8  2025/04/30 20:03:09  snw
                     34:  *   Work on entryref parser
                     35:  *
1.8       snw        36:  *   Revision 1.7  2025/04/17 00:34:04  snw
                     37:  *   More logging improvements
                     38:  *
1.7       snw        39:  *   Revision 1.6  2025/04/10 01:24:38  snw
                     40:  *   Remove C++ style comments
                     41:  *
1.6       snw        42:  *   Revision 1.5  2025/03/24 02:57:49  snw
                     43:  *   Shared memory compatibility fixes for OS/2
                     44:  *
1.5       snw        45:  *   Revision 1.4  2025/03/09 19:14:25  snw
                     46:  *   First phase of REUSE compliance and header reformat
                     47:  *
1.4       snw        48:  *
                     49:  * SPDX-FileCopyrightText:  (C) 2025 Coherent Logic Development LLC
                     50:  * SPDX-License-Identifier: AGPL-3.0-or-later
1.1       snw        51:  **/
                     52: #include <stdlib.h>
                     53: #include <string.h>
                     54: #include <time.h>
                     55: #include <unistd.h>
1.7       snw        56: #include <errno.h>
1.1       snw        57: 
                     58: #include "mpsdef.h"
                     59: #include "shmmgr.h"
                     60: #include "jobtab.h"
1.7       snw        61: #include "log.h"
1.1       snw        62: 
1.5       snw        63: #if !defined(__OpenBSD__) && !defined(__APPLE__) && !defined(__OS2__)
1.1       snw        64: union semun {
                     65:     int              val;    /* Value for SETVAL */
                     66:     struct semid_ds *buf;    /* Buffer for IPC_STAT, IPC_SET */
                     67:     unsigned short  *array;  /* Array for GETALL, SETALL */
                     68:     struct seminfo  *__buf;  /* Buffer for IPC_INFO
                     69:                                 (Linux-specific) */
                     70: };
                     71: #endif
                     72: 
                     73: int semid_jobtab;
                     74: short have_jobtab_sem = FALSE;
                     75: 
                     76: void jobtab_init(void)
                     77: {
                     78:     union semun arg;
                     79:     key_t jt_sk;
                     80: 
                     81:     jt_sk = ftok (config_file, 2);
                     82:     if (first_process) {
                     83: 
                     84:         semid_jobtab = semget (jt_sk, 1, 0666 | IPC_CREAT);
                     85:         if (semid_jobtab == -1) {
1.7       snw        86:             logprintf (FM_LOG_FATAL, "jobtab_init:  failed to create job table semaphore (error code %d [%s])", errno, strerror (errno));
1.1       snw        87:         }
                     88:         else {
1.7       snw        89:             logprintf (FM_LOG_INFO, "jobtab_init:  job table semaphore created with semid %d", semid_jobtab);
1.1       snw        90:         }
                     91: 
                     92:         arg.val = 1;
                     93:         if (semctl (semid_jobtab, 0, SETVAL, arg) == -1) {
1.7       snw        94:             logprintf (FM_LOG_FATAL, "jobtab_init:  failed to initialize job table semaphore (error code %d [%s])", errno, strerror (errno));
1.1       snw        95:         }
                     96:         else {
1.7       snw        97:             logprintf (FM_LOG_INFO, "jobtab_init:  job table semaphore initialized");
1.1       snw        98:         }
                     99:         
                    100:     }
                    101:     else {
                    102: 
                    103:         semid_jobtab = semget (jt_sk, 1, 0);
                    104:         if (semid_jobtab == -1) {
1.7       snw       105:             logprintf (FM_LOG_FATAL, "jobtab_init:  could not attach to job table semaphore (error code %d [%s])", errno, strerror (errno));
                    106:         }
                    107:         else {
                    108:             logprintf (FM_LOG_INFO, "jobtab_init:  attached to job table semaphore (semid %d)", semid_jobtab);
1.1       snw       109:         }
                    110:         
                    111:     }
                    112:     
                    113:     return;
                    114: }
                    115: 
                    116: short jobtab_get_sem(void)
                    117: {
                    118:     int tries;
                    119:     struct sembuf s = {0, -1, IPC_NOWAIT};
                    120: 
1.11    ! snw       121:     logprintf (FM_LOG_DEBUG, "jobtab_get_sem:  attempting to acquire job table semaphore");
        !           122:     
1.1       snw       123:     if (have_jobtab_sem) {
1.11    ! snw       124:         logprintf (FM_LOG_DEBUG, "jobtab_get_sem:  acquired semaphore by virtue of already owning it");
1.1       snw       125:         return TRUE;
                    126:     }
                    127:     
                    128:     for (tries = 0; tries < 5; tries++) {
                    129: 
                    130:         if (semop (semid_jobtab, &s, 1) != -1) {
1.11    ! snw       131:             logprintf (FM_LOG_DEBUG, "jobtab_get_sem:  acquired semaphore by virtue of calling semop on it");
1.1       snw       132:             have_jobtab_sem = TRUE;           
                    133:             return TRUE;
                    134:         }
1.11    ! snw       135:         
        !           136:         logprintf (FM_LOG_INFO, "jobtab_get_sem:  sleeping for retry [tries = %d; errno = %d; error = %s]", tries, errno, strerror (errno));
1.1       snw       137:         sleep (1);
                    138: 
                    139:     }
1.7       snw       140:     logprintf (FM_LOG_ERROR, "jobtab_get_sem:  failed to acquire job table semaphore");
1.11    ! snw       141: 
1.1       snw       142:     have_jobtab_sem = FALSE;
                    143:     return FALSE;
                    144: }
                    145: 
                    146: void jobtab_release_sem(void)
                    147: {
                    148:     struct sembuf s = {0, 1, 0};
                    149: 
1.11    ! snw       150:     logprintf (FM_LOG_DEBUG, "jobtab_release_sem:  trying to release job table semaphore");
        !           151:     if (semop (semid_jobtab, &s, 1) != -1) {
        !           152:         logprintf (FM_LOG_DEBUG, "jobtab_release_sem:   released job table semaphore by means of semop");
        !           153:         have_jobtab_sem = FALSE;
        !           154:     }
        !           155:     else {        
        !           156:         logprintf (FM_LOG_FATAL, "jobtab_release_sem:  failed to release job table semaphore (error code %d [%s])", errno, strerror (errno));
        !           157:     }
1.1       snw       158: }
                    159: 
                    160: 
                    161: job_slot_t *job_init(short is_fmadm)
                    162: {
                    163: 
                    164:     job_slot_t *s;
                    165:         
                    166:     if (jobtab_get_sem () == FALSE) {
1.7       snw       167:         logprintf (FM_LOG_FATAL, "job_init:  failed to get job table semaphore");
1.1       snw       168:     }
                    169: 
1.10      snw       170:     for (s = SOA(shm_config->hdr->jobtab_head); s != NULL; s = SOA(s->next)) {
1.1       snw       171: 
                    172:         if (((s->flags & JFLG_DEFUNCT) == JFLG_DEFUNCT) || (s->pid == pid)) {
                    173:             goto skip_alloc;
                    174:         }
                    175:         
                    176:     }
                    177: 
                    178:     s = (job_slot_t *) shm_alloc (sizeof (job_slot_t));
                    179:     NULLPTRCHK(s,"job_init");
                    180: 
                    181:     
                    182: skip_alloc:    
                    183:     
                    184:     s->pid = pid;    
                    185:     s->ipc_head = (ipc_slot_t *) NULL;
                    186:     s->status = JSTAT_IDLE;
                    187:     s->start_time = time (NULL);
                    188:     s->stop_requested = 0;
                    189:     
                    190:     if (first_process) {
                    191:         s->flags = JFLG_DAEMON | JFLG_NEW;
                    192:     }
                    193:     else {
                    194:         if (is_fmadm == TRUE) {
                    195:             s->flags = JFLG_NEW | JFLG_FMADM;
                    196:         }
                    197:         else {
                    198:             s->flags = JFLG_NEW;
                    199:         }
                    200:     }
                    201:     
1.10      snw       202:     s->next = SBM(shm_config->hdr->jobtab_head);
                    203:     shm_config->hdr->jobtab_head = SBM(s);
1.1       snw       204: 
                    205:     jobtab_release_sem ();
                    206:     
                    207:     return s;
                    208:     
                    209: }
                    210: 
                    211: void job_remove(const pid_t pid)
                    212: {
                    213:     
1.10      snw       214:     job_slot_t *t = SOM(shm_config->hdr->jobtab_head);
1.1       snw       215:     job_slot_t *p = NULL;
                    216: 
1.7       snw       217:     if (jobtab_get_sem () == FALSE) {
                    218:         logprintf (FM_LOG_FATAL, "job_remove:  failed to get job table semaphore");
1.1       snw       219:     }
                    220:     
                    221:     if ((t != (job_slot_t *) NULL) && (t->pid == pid)) {
                    222: 
1.10      snw       223:         shm_config->hdr->jobtab_head = SBM(t->next);
1.1       snw       224:         shm_free (t);
                    225: 
                    226:         jobtab_release_sem ();
                    227: 
                    228:         return;
                    229:         
                    230:     }
                    231: 
                    232:     while ((t != NULL) && (t->pid != pid)) {
                    233:         p = t;
1.11    ! snw       234:         t = SOA(t->next);
1.1       snw       235:     }
                    236: 
                    237:     if (t == NULL) {
                    238:         jobtab_release_sem ();
                    239:         return;
                    240:     }
                    241: 
1.10      snw       242:     p->next = SBA(t->next);
1.1       snw       243:     shm_free (t);
                    244: 
                    245:     jobtab_release_sem ();
                    246:     
1.9       snw       247:     locktab_unlock_all_by_pid (pid);
1.1       snw       248: }
                    249: 
                    250: void job_request_stop(const pid_t target_pid)
                    251: {
                    252: 
                    253:     job_slot_t *s;
                    254: 
                    255:     if (jobtab_get_sem() == FALSE) {
1.7       snw       256:         logprintf (FM_LOG_FATAL, "job_request_stop:  failed to get job table semaphore");
1.1       snw       257:     }
                    258:     
1.10      snw       259:     for (s = SOA(shm_config->hdr->jobtab_head); s != NULL; s = SOA(s->next)) {
1.1       snw       260: 
                    261:         if (s->pid == target_pid) {
                    262:             s->stop_requested = pid;
                    263: 
                    264:             jobtab_release_sem ();
                    265:             job_set_status (target_pid, JSTAT_SHUTDOWN);
                    266:             
                    267:             return;
                    268:         }
                    269:         
                    270:     }
                    271: 
                    272:     jobtab_release_sem ();
                    273:     
                    274: }
                    275: 
                    276: void job_set_ecode(const pid_t target_pid, const char *ecode)
                    277: {
                    278: 
                    279:     job_slot_t *s;
                    280: 
1.7       snw       281:     if (jobtab_get_sem () == FALSE) {
                    282:         logprintf (FM_LOG_FATAL, "job_set_ecode:  failed to get job table semaphore");
1.1       snw       283:     }
                    284:     
1.11    ! snw       285:     for (s = SOA(shm_config->hdr->jobtab_head); s != NULL; s = SOA(s->next)) {
1.1       snw       286: 
                    287:         if (s->pid == target_pid) {
                    288:             strncpy (s->last_ecode, ecode, 20);
                    289:             jobtab_release_sem ();
                    290:             return;
                    291:         }
                    292:         
                    293:     }
                    294: 
                    295:     jobtab_release_sem ();
                    296:     
                    297: }
                    298: 
                    299: pid_t job_stop_requested (const pid_t target_pid)
                    300: {
                    301: 
                    302:     job_slot_t *s;
                    303:     
1.11    ! snw       304:     for (s = SOA(shm_config->hdr->jobtab_head); s != NULL; s = SOA(s->next)) {
1.1       snw       305:         if (s->pid == target_pid) {
                    306:             return s->stop_requested;
                    307:         }
                    308:     }
                    309:     
                    310:     return (pid_t) 0;
                    311:     
                    312: }
                    313: 
                    314: void job_request_all_stop(void)
                    315: {
                    316: 
                    317:     job_slot_t *s;
                    318: 
                    319:     
1.11    ! snw       320:     for (s = SOA(shm_config->hdr->jobtab_head); s != NULL; s = SOA(s->next)) {
1.1       snw       321: 
                    322:         if ((s->flags & JFLG_DAEMON) != JFLG_DAEMON) {
                    323:             job_request_stop (s->pid);
                    324:         }
                    325: 
                    326:     }
                    327: 
                    328:     
                    329: }
                    330: 
                    331: void job_signal_all(const int sig)
                    332: {
                    333: 
                    334:     job_slot_t *s;
                    335: 
1.11    ! snw       336:     for (s = SOA(shm_config->hdr->jobtab_head); s != NULL; s = SOA(s->next)) {
1.1       snw       337: 
                    338:         if ((s->flags & JFLG_DAEMON) != JFLG_DAEMON) {
                    339:             kill (s->pid, sig);
                    340:         }
                    341: 
                    342:     }
                    343:     
                    344: }
                    345:     
                    346: 
                    347: int job_count(void)
                    348: {
                    349: 
                    350:     job_slot_t *s;
                    351:     int ct = 0;
                    352: 
1.11    ! snw       353:     for (s = SOA(shm_config->hdr->jobtab_head); s != NULL; s = SOA(s->next)) {
1.1       snw       354: 
                    355:         if ((s->flags & JFLG_DEFUNCT) != JFLG_DEFUNCT) {
                    356:             ct++;
                    357:         }
                    358: 
                    359:     }
                    360: 
                    361:     return ct;
                    362: 
                    363: }
                    364: 
                    365: job_slot_t *job_set_status(const pid_t target_pid, const unsigned short status)
                    366: {
                    367: 
                    368:     job_slot_t *s;
                    369: 
1.7       snw       370:     if (jobtab_get_sem () == FALSE) {
                    371:         logprintf (FM_LOG_FATAL, "job_set_status:  failed to get job table semaphore");
1.1       snw       372:     }    
                    373:     
1.11    ! snw       374:     for (s = SOA(shm_config->hdr->jobtab_head); s != NULL; s = SOA(s->next)) {
1.1       snw       375: 
                    376:         if (s->pid == target_pid) {
                    377:             s->status = status;
                    378:             jobtab_release_sem ();
                    379:             return s;
                    380:         }
                    381:         
                    382:     }
                    383: 
                    384:     jobtab_release_sem ();
                    385: 
                    386:     return (job_slot_t *) NULL;
                    387: 
                    388: }
                    389: 
                    390: void job_gc_mark(void)
                    391: {
                    392: 
                    393:     job_slot_t *s;
1.7       snw       394:     int jobstat;
1.1       snw       395: 
                    396:     if (jobtab_get_sem () == FALSE) {
1.7       snw       397:         logprintf (FM_LOG_FATAL, "job_gc_mark:  failed to get job table semaphore");
1.1       snw       398:     }
                    399: 
1.11    ! snw       400:     for (s = SOA(shm_config->hdr->jobtab_head); s != NULL; s = SOA(s->next)) {
        !           401:         logprintf (FM_LOG_WARNING, "job_gc_mark looking at pid %ld", s->pid);
1.7       snw       402:         if ((jobstat = kill (s->pid, 0)) != 0) {
                    403:             
                    404:             switch (jobstat) {
                    405:                 
                    406:                 case EPERM:
                    407:                     logprintf (FM_LOG_WARNING, "job_gc_mark:  environment daemon lacks permissions to pid %ld", (long) s->pid);
                    408:                     break;
                    409:                     
                    410:                 case ESRCH:
                    411:                     logprintf (FM_LOG_INFO, "job_gc_mark:  marking pid %ld DEFUNCT", (long) s->pid);
                    412:                     s->flags = JFLG_DEFUNCT;
                    413:                     break;
                    414:                     
                    415:             }
                    416:             
1.1       snw       417:         }       
                    418: 
                    419:         if ((s->flags & JFLG_NEW) == JFLG_NEW) {
                    420:             if ((s->flags & JFLG_DAEMON) == JFLG_DAEMON) {
1.7       snw       421:                 logprintf (FM_LOG_INFO, "job_gc_mark:  registering new daemon %ld", (long) s->pid);
1.1       snw       422:                 s->flags = JFLG_ALIVE | JFLG_DAEMON;
                    423:             }
                    424:             else {
                    425:                 if ((s->flags & JFLG_FMADM) == JFLG_FMADM) {
1.7       snw       426:                     logprintf (FM_LOG_INFO, "job_gc_mark:  registering new fmadm process %ld", (long) s->pid);
1.1       snw       427:                     s->flags = JFLG_ALIVE | JFLG_FMADM;
                    428:                 }
                    429:                 else {
1.7       snw       430:                     logprintf (FM_LOG_INFO, "job_gc_mark:  registering new interpreter process %ld", (long) s->pid);
1.1       snw       431:                     s->flags = JFLG_ALIVE;
                    432:                 }
                    433:             }
                    434:         }
                    435:         
                    436:     }
                    437: 
                    438:     jobtab_release_sem ();
                    439:     
                    440: }
                    441: 
                    442: void job_gc_sweep(void)
                    443: {
                    444: 
                    445:     job_slot_t *s;
                    446:     char *k_buf = (char *) malloc (STRLEN * sizeof (char));
                    447:     NULLPTRCHK(k_buf,"job_gc_sweep");
                    448: 
                    449:     if (jobtab_get_sem () == FALSE) {
1.7       snw       450:         logprintf (FM_LOG_FATAL, "job_gc_sweep:  failed to get job table semaphore");
1.1       snw       451:     }
                    452: 
                    453:     
1.11    ! snw       454:     for (s = SOA(shm_config->hdr->jobtab_head); s != NULL; s = SOA(s->next)) {
1.1       snw       455: 
                    456:         if ((s->flags & JFLG_DEFUNCT) == JFLG_DEFUNCT) {
                    457: 
1.7       snw       458:             logprintf (FM_LOG_INFO, "job_gc_sweep:  sweeping DEFUNCT pid %ld", (long) s->pid);
1.1       snw       459: 
                    460:             snprintf (k_buf, STRLEN - 1, "^$JOB\202%d\201", s->pid);
                    461: 
                    462:             symtab_shm (kill_sym, k_buf, " \201");
                    463:             merr_clear ();
                    464:             
                    465:             job_remove (s->pid);
                    466: 
                    467:             free (k_buf);
                    468: 
                    469:             jobtab_release_sem ();
                    470:             return;                     
                    471:             
                    472:         }
                    473:         
                    474:     }
                    475: 
                    476:     free (k_buf);
                    477: 
                    478:     jobtab_release_sem ();
                    479:     
                    480: }
                    481: 
                    482: ipc_slot_t *job_send_ipc(const pid_t receiver_pid, const void *object)
                    483: {
                    484: 
                    485:     job_slot_t *j;
                    486:     ipc_slot_t *s = (ipc_slot_t *) shm_alloc (sizeof (ipc_slot_t));
                    487:     NULLPTRCHK(s,"job_send_ipc");
                    488: 
1.7       snw       489:     if (jobtab_get_sem () == FALSE) {
                    490:         logprintf (FM_LOG_FATAL, "job_send_ipc:  failed to get job table semaphore");
1.1       snw       491:     }
                    492: 
                    493:     
1.11    ! snw       494:     for (j = SOA(shm_config->hdr->jobtab_head); j != NULL; j = SOA(j->next)) {
1.1       snw       495: 
                    496:         if (j->pid == receiver_pid) {
                    497:             
                    498:             s->flags = JIPCFLG_PENDING;
                    499:             s->sender_pid = pid;
                    500:             s->object = (void *) object;
                    501: 
                    502:             s->next = j->ipc_head;
                    503:             j->ipc_head = s;
                    504: 
                    505:             jobtab_release_sem ();
                    506:             
                    507:             return s;
                    508:             
                    509:         }
                    510: 
                    511:     }
                    512: 
                    513:     shm_free (s);
                    514: 
                    515:     jobtab_release_sem ();
                    516:     
                    517:     return (ipc_slot_t *) NULL;
                    518:     
                    519: }
                    520: 
                    521: job_slot_t *job_get(const pid_t target_pid)
                    522: {
                    523:     
                    524:     job_slot_t *s;
                    525: 
1.11    ! snw       526:     for (s = SOA(shm_config->hdr->jobtab_head); s != NULL; s = SOA(s->next)) {
1.1       snw       527:         if (s->pid == target_pid) return s;        
                    528:     }
                    529: 
                    530:     return (job_slot_t *) NULL;
                    531: 
                    532: }
                    533: 
                    534: void job_dump(void)
                    535: {
                    536:     char time_buf[20];
                    537:     char *flag_s = (char *) malloc (256 * sizeof (char));
                    538:     char *stat_s = (char *) malloc (256 * sizeof (char));    
                    539:     
                    540:     job_slot_t *s;
                    541: 
                    542:     flag_s[0] = '\0';
                    543:     stat_s[0] = '\0';
                    544:     
                    545:     printf ("%-10s%-15s%-20s%-22s%s\r\n", "PID", "STATUS", "LAST ECODE", "STARTED", "FLAGS");
                    546:     printf ("%-10s%-15s%-20s%-22s%s\r\n", "---", "------", "----------", "-------", "-----");
                    547:     
1.11    ! snw       548:     for (s = SOA(shm_config->hdr->jobtab_head); s != NULL; s = SOA(s->next)) {
1.1       snw       549: 
                    550:         strftime (time_buf, 20, "%Y-%m-%d %H:%M:%S", localtime (&(s->start_time)));
                    551:         
                    552:         flag_s[0] = '\0';
                    553:         stat_s[0] = '\0';
                    554:         
                    555:         if ((s->flags & JFLG_ALIVE) == JFLG_ALIVE) strcat (flag_s, "ALIVE ");
                    556:         if ((s->flags & JFLG_DEFUNCT) == JFLG_DEFUNCT) strcat (flag_s, "DEFUNCT ");
                    557:         if ((s->flags & JFLG_REPLSENDER) == JFLG_REPLSENDER) strcat (flag_s, "REPLSENDER ");
                    558:         if ((s->flags & JFLG_REPLRECEIVER) == JFLG_REPLRECEIVER) strcat (flag_s, "REPLRECEIVER ");
                    559:         if ((s->flags & JFLG_FMADM) == JFLG_FMADM) strcat (flag_s, "FMADM ");
                    560:         if ((s->flags & JFLG_NEW) == JFLG_NEW) strcat (flag_s, "NEW ");
                    561:         if ((s->flags & JFLG_DAEMON) == JFLG_DAEMON) strcat (flag_s, "DAEMON ");
                    562: 
                    563:         if (s->status == JSTAT_IDLE) strcat (stat_s, "IDLE");
                    564:         if (s->status == JSTAT_INTERPRETER) strcat (stat_s, "INTERPRETER");
                    565:         if (s->status == JSTAT_HOUSEKEEPING) strcat (stat_s, "HOUSEKEEPING");
                    566:         if (s->status == JSTAT_DIRECTMODE) strcat (stat_s, "DIRECTMODE");
                    567:         if (s->status == JSTAT_ERROR) strcat (stat_s, "ERROR");
                    568:         if (s->status == JSTAT_SHUTDOWN) strcat (stat_s, "SHUTDOWN");
                    569: 
                    570:         printf ("%-10d%-15s%-20s%-22s%s\r\n", s->pid, stat_s, s->last_ecode, time_buf, flag_s);
                    571:        
                    572:         
                    573:     }
                    574: 
                    575:     free (stat_s);
                    576:     free (flag_s);
                    577:     
                    578: }
                    579: 

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