#ifdef public_domain_notice Copyright (c) 1996 David P. Murphy for Datametrics Systems Corporation --- please send all comments and bug reports to murphy@connor.datametrics.com Permission is granted to any individual or institution to use, copy, or redistribute this software so long as all of the original files are included, that it is not sold for profit, and that this copyright notice is retained. Use at your own risk. Neither Murphy nor Datametrics assumes responsibility. Do not taunt Happy Fun Ball. #endif /* the SHERLOCK utility */ #include "dscstd.h" #include #include #include #include #include #include #include #include #include #include #include #include "sherlock.h" #include "mycroft.h" /* macros specific to this module */ /* this condition might not be in the SSDEF header . . . */ #ifndef SS$_ILLRSDM # define SS$_ILLRSDM 3898 #endif /* got this from ICBDEF.MAR */ #define ICBFLAG_SYS_STB (1 << 7) /* got this from $FCBDEF in SYS$LIBRARY:LIB.MLB */ #define FCB$M_EXCL (1 << 3) #define SHERLOCK_MAX_TIMERS 128 /* internal data structures */ typedef struct IMAGE_DATA { struct IMAGE_DATA *next; char text[1024]; } ImageData; /* operating-system function declarations */ #ifdef __VAX #define sys$asctim SYS$ASCTIM #define sys$getmsg SYS$GETMSG #define lib$free_ef LIB$FREE_EF #define lib$get_ef LIB$GET_EF #define lib$signal LIB$SIGNAL #endif #include /* prototypes for the SYS$xxxxx() functions */ #include /* " " " LIB$xxxxx() " */ /* external function declarations */ /* internal function declarations */ /* external data declarations */ /* internal data declarations */ LOCALDATA CONDVAL (*CallbackFnc)(); LOCALDATA void *CallbackArg; LOCALDATA BOOL FancyLabels; MEP__ PRIVATE CONDVAL SH_I_Output( char *txtptr, FILE *fp ) { #ifdef bleah fwrite(txtptr, strlen(txtptr), 1, fp); #else fprintf(fp, "%s", txtptr); #endif return(SS$_NORMAL); } MEP__ PRIVATE CONDVAL SH_I_ShowFilesAndOrImages( BOOL showfiles, BOOL showimages, void (*imagecallback)(char *, void *, void *) ) { #ifdef SAMPLE_VAX_OUTPUT Process Channels: chan flags filespec ---- ------ --------------------------------------------------- 10 ( ) DKB0: 20 ( r ) _$1$DKB0:[PUBLIC.HOLMES]HOLMESTEST.EXE;42 30 ( rw ) _$1$DKB0:[USER.MURPHY]DPM.OUT;44 40 ( ) NLA0: 50 (sr ) _$1$DKB0:[VMS$COMMON.SYSLIB]UVMTHRTL.EXE;1 60 (sr ) _$1$DKB0:[VMS$COMMON.SYSLIB]LIBRTL.EXE;1 70 (sr ) _$1$DKB0:[VMS$COMMON.SYSLIB]VAXCRTL.EXE;1 80 ( ) NLA0: 90 ( rw ) _$1$DKB0:[USER.MURPHY]BWAAHAHA.TMP;2 A0 ( r ) _$1$DKB0:[PUBLIC.HOLMES]SHERLOCK.EXE;42 B0 (sr ) _$1$DKB0:[PUBLIC.HOLMES]MYCROFT.EXE;43 s = section file r = read access w = write access Process Images: chan name version linked codes gs ident virtual address patches ---- ------------------------- --------------- --------------- ------------- ----------- ----------------- ------- 20 HOLMESTEST V1.0 12-JAN-94 14:10 0,0 200: 9FF 50 MTHRTL V04-002 12-OCT-89 00:05 IOHS 129,32780 A00: 127FF 60 LIBRTL V05-001 12-OCT-89 00:02 IOHS 1,14 12800: 2B7FF 70 VAXCRTL V05-001 12-OCT-89 00:11 IOHS 4,3 2B800: 42DFF A0 SHERLOCK_HOLMES V1.0 12-JAN-94 14:10 0,0 63E00: 64FFF B0 MYCROFT_HOLMES V1.0 12-JAN-94 14:09 IOHS P 1,0 65000: 65FFF I = installed P = installed /PROTECTED D = linked /DEBUG O = installed /OPEN A = installed /ACCOUNTING T = linked /TRACEBACK H = installed /HEADER_RES N = installed /NOPURGE Y = linked against SYS.STB S = installed /SHARED W = installed /WRITEABLE V = installed /PRIVILEGED X = installed /EXECUTE_ONLY #endif #ifdef SAMPLE_AXP_OUTPUT Process Channels: chan flags filespec ---- ------ --------------------------------------------------- 10 ( ) DKA300: 20 ( r ) _CONNOR$DKA300:[PUBLIC.HOLMES]HOLMESTEST.EXE;69 30 ( rw ) _CONNOR$DKA300:[USER.MURPHY]DPM.OUT;14 40 ( ) NLA0: 50 (sr ) _CONNOR$DKA300:[VMS$COMMON.SYSEXE]DCL.EXE;1 60 (sr ) _CONNOR$DKA300:[VMS$COMMON.SYSLIB]LIBRTL.EXE;1 70 (sr ) _CONNOR$DKA300:[VMS$COMMON.SYSLIB]DECC$SHR.EXE;1 80 (sr ) _CONNOR$DKA300:[VMS$COMMON.SYSLIB]LIBOTS.EXE;1 90 (sr ) _CONNOR$DKA300:[VMS$COMMON.SYSLIB]DPML$SHR.EXE;1 A0 ( R ) _CONNOR$DKA300:[VMS$COMMON.SYSLIB]CMA$TIS_SHR.EXE;1 B0 ( ) NLA0: C0 ( R ) _CONNOR$DKA300:[PUBLIC.HOLMES]SHERLOCK.EXE;47 D0 (SR ) _CONNOR$DKA300:[PUBLIC.HOLMES]MYCROFT.EXE;68 s = section file r = read access w = write access x = exclusive access Process Images: chan name version linked codes gs ident virtual address patches ---- ------------------------- --------------- --------------- ------------- ----------- ----------------- ------- 20 HOLMESTEST V1.0 12-JAN-94 13:30 0,0 10000: 401FF 60 LIBRTL ALPHA X5K4-D3A 22-APR-93 15:26 IOHS 1,1 42000: 1233FF 70 DECC$SHR ALPHA X5K4-D3A 22-APR-93 15:40 IOHS 1,1 124000: 2255FF 80 LIBOTS LIBOTS V1.1-001 22-APR-93 15:19 IOHS 1,2 226000: 22E1FF 90 DPML$SHR ALPHA X5K4-D3A 22-APR-93 15:33 IOHS 1,0 230000: 27E5FF A0 CMA$TIS_SHR CMA V2.11-234 22-APR-93 15:19 I 1,1 280000: 2C01FF C0 SHERLOCK V1.0 12-JAN-94 13:30 0,0 2F4000: 3443FF D0 MYCROFT V1.0 12-JAN-94 13:30 IOHS P 1,0 346000: 398138 I = installed P = installed /PROTECTED D = linked /DEBUG O = installed /OPEN A = installed /ACCOUNTING T = linked /TRACEBACK H = installed /HEADER_RES N = installed /NOPURGE Y = linked against SYS.STB S = installed /SHARED W = installed /WRITEABLE V = installed /PRIVILEGED X = installed /EXECUTE_ONLY #endif /* local variables */ int j; CONDVAL cv0; char *nameptr; ImageData *idp; ImageData *firstImage; ImageData *currentImage; struct dsc$descriptor_s tmpsd; struct dsc$descriptor_s datesd; unsigned short retlen; MHMiscMask miscstate; MHInstalledMask instate; char sectflag; char readflag; char writflag; char exclflag; char codebuf[14]; char tmpbuf[256]; char linkedbuf[23 + 1]; /* arguments for the ChannelInfo() call */ unsigned long context; MHCheapChanInfo cheap; MHExpensiveChanInfo expensive; MHTipChanInfo tip; if (showimages) { datesd.dsc$a_pointer = linkedbuf; datesd.dsc$w_length = sizeof(linkedbuf) - 1; datesd.dsc$b_dtype = 0; datesd.dsc$b_class = 0; } if (showfiles) { if (FancyLabels) { (*CallbackFnc)("\n\tProcess Channels:\n", CallbackArg); (*CallbackFnc)("\n\t\t", CallbackArg); } (*CallbackFnc)("chan flags filespec", CallbackArg); if (FancyLabels) { (*CallbackFnc)("\n\t\t", CallbackArg); } (*CallbackFnc)("---- ------ ---------------------------------------------------", CallbackArg); } /* invoke an exec-mode user system service to copy all the data * for the active channels into this module's static area. */ context = 0; firstImage = NULL; currentImage = NULL; for (;;) { IFBAD(MH_GetChannelInfo(&context, 0, &cheap, &expensive, &tip, NULL)) { if (cv0 == SS$_NOMOREFILES) { /* this is okay, we've simply looked at all of the channels */ break; } else if (showfiles) { /* get error message which will be displayed later */ tmpsd.dsc$a_pointer = expensive.resfs; tmpsd.dsc$w_length = sizeof(expensive.resfs) - 1; tmpsd.dsc$b_dtype = 0; tmpsd.dsc$b_class = 0; sys$getmsg(cv0, &retlen, &tmpsd, 0, NULL); expensive.resfs[retlen] = '\0'; } else { lib$signal(cv0, 0); break; } } /* if this channel is open to an entry in the Image Activation List, * save the image-related information for the second printout */ if (showimages && cheap.icb != NULL) { if (imagecallback != NULL) { (*imagecallback)(cheap.imgname, cheap.vastart, cheap.vaend); } idp = calloc(1, sizeof(ImageData)); if (idp != NULL) { if (firstImage == NULL) firstImage = idp; if (currentImage != NULL) currentImage->next = idp; currentImage = idp; j = 0; while (j < sizeof(codebuf)) codebuf[j++] = ' '; codebuf[13] = '\0'; if (cheap.kfe != NULL) { instate.themask = cheap.kfemask; codebuf[0] = 'I'; /* ah ha! image is installed, but is it . . . */ if (instate.thebits.open) codebuf[1] = 'O'; /* . . . /OPEN? */ if (instate.thebits.hdrres) codebuf[2] = 'H'; /* . . . /HEADER_RES? */ if (instate.thebits.shared) codebuf[3] = 'S'; /* . . . /SHARED? */ if (instate.thebits.procpriv) codebuf[4] = 'V'; /* . . . /PRIVILEGED? */ if (instate.thebits.protect) codebuf[5] = 'P'; /* . . . /PROTECTED? */ if (instate.thebits.nopurge) codebuf[6] = 'N'; /* . . . /NOPURGE? */ if (instate.thebits.account) codebuf[7] = 'A'; /* . . . /ACCOUNTING? */ if (instate.thebits.writeable) codebuf[8] = 'W'; /* . . . /WRITEABLE? */ if (instate.thebits.exeonly) codebuf[9] = 'X'; /* . . . /EXECUTE_ONLY? */ } if ((cheap.imageflags & ICBFLAG_SYS_STB) != 0) codebuf[10] = 'Y'; if (tip.lnktrace) codebuf[11] = 'T'; if (tip.lnkdebug) codebuf[12] = 'D'; sys$asctim(NULL, &datesd, &tip.linktime[0], 0); sprintf(idp->text, "%4X %-25s %-15.15s %-7.7s%-8.8s %-13.13s %5d,%-5d %8X:%8X %s", cheap.chan, cheap.imgname, tip.imgid, linkedbuf, linkedbuf+9, codebuf, cheap.majorid, cheap.minorid, cheap.vastart, cheap.vaend, tip.patches); } } if (showfiles) { if (FancyLabels) { (*CallbackFnc)("\n\t\t", CallbackArg); } nameptr = expensive.resfs; if (*nameptr == '\0') nameptr = cheap.fulldevname; miscstate.themask = cheap.miscmask; sectflag = (miscstate.thebits.sectfile) ? 's' : ' '; readflag = (miscstate.thebits.readacc) ? 'r' : ' '; writflag = (miscstate.thebits.writeacc) ? 'w' : ' '; exclflag = ((cheap.status & FCB$M_EXCL) != 0) ? 'x' : ' '; sprintf(tmpbuf, "%4X (%c%c%c%c) %s", cheap.chan, sectflag, readflag, writflag, exclflag, nameptr); (*CallbackFnc)(tmpbuf, CallbackArg); } } if (showfiles) { if (FancyLabels) (*CallbackFnc)("\n\n\t\t\t", CallbackArg); (*CallbackFnc)("s = section file r = read access w = write access x = exclusive access", CallbackArg); if (FancyLabels) { (*CallbackFnc)("\n", CallbackArg); } } /* display the second printout and release the memory */ if (showimages) { if (FancyLabels) { (*CallbackFnc)("\n\tProcess Images:\n", CallbackArg); (*CallbackFnc)("\n\t\t", CallbackArg); } (*CallbackFnc)("chan name version linked codes gs ident virtual address patches", CallbackArg); if (FancyLabels) { (*CallbackFnc)("\n\t\t", CallbackArg); } (*CallbackFnc)("---- ------------------------- --------------- --------------- ------------- ----------- ----------------- -------", CallbackArg); while (firstImage != NULL) { idp = firstImage->next; if (FancyLabels) { (*CallbackFnc)("\n\t\t", CallbackArg); } (*CallbackFnc)(firstImage->text, CallbackArg); free(firstImage); firstImage = idp; } if (FancyLabels) (*CallbackFnc)("\n\n\t\t\t", CallbackArg); (*CallbackFnc)("I = installed P = installed /PROTECTED D = linked /DEBUG", CallbackArg); if (FancyLabels) (*CallbackFnc)("\n\t\t\t", CallbackArg); (*CallbackFnc)("O = installed /OPEN A = installed /ACCOUNTING T = linked /TRACEBACK", CallbackArg); if (FancyLabels) (*CallbackFnc)("\n\t\t\t", CallbackArg); (*CallbackFnc)("H = installed /HEADER_RES N = installed /NOPURGE Y = linked against SYS.STB", CallbackArg); if (FancyLabels) (*CallbackFnc)("\n\t\t\t", CallbackArg); (*CallbackFnc)("S = installed /SHARED W = installed /WRITEABLE", CallbackArg); if (FancyLabels) (*CallbackFnc)("\n\t\t\t", CallbackArg); (*CallbackFnc)("V = installed /PRIVILEGED X = installed /EXECUTE_ONLY", CallbackArg); if (FancyLabels) (*CallbackFnc)("\n", CallbackArg); } return(SS$_NORMAL); } MEP__ PRIVATE char *DSC_I_LookupLockAccess( int nAccess ) { char *pcAccess = "??"; switch (nAccess) { case PSL$C_KERNEL: pcAccess = "kernel"; break; case PSL$C_EXEC: pcAccess = "exec "; break; case PSL$C_SUPER: pcAccess = "super "; break; case PSL$C_USER: pcAccess = "user "; break; } return(pcAccess); } MEP__ PRIVATE char *DSC_I_LookupLockMode( int nMode ) { char *pcMode = "??"; switch (nMode) { case LCK$K_NLMODE: pcMode = "NL"; break; case LCK$K_CRMODE: pcMode = "CR"; break; case LCK$K_CWMODE: pcMode = "CW"; break; case LCK$K_PRMODE: pcMode = "PR"; break; case LCK$K_PWMODE: pcMode = "PW"; break; case LCK$K_EXMODE: pcMode = "EX"; break; } return(pcMode); } MEP__ PRIVATE char *DSC_I_LookupLockQueue( int nQueue ) { char *pcQueue = "??"; switch (nQueue) { case LKI$C_GRANTED: pcQueue = "granted "; break; case LKI$C_CONVERT: pcQueue = "converting"; break; case LKI$C_WAITING: pcQueue = "waiting "; break; } return(pcQueue); } /* i have to place the LKI$_VALBLK item into a separate list, * because the service returns * %SYSTEM-E-ILLRSDM, operation not allowed on resource domain * if the LKI$_VALBLK is specified during a wildcard get . . . * i have no idea why this is true, but it is, and i've got a workaround, * so i let it go at that. */ LOCALDATA struct { struct namspace namspace; struct statef state; unsigned long lockid; unsigned long pid; unsigned long parent; unsigned long valblk[4]; unsigned long rnamlen; char resnam[31+1]; } lkrec; LOCALDATA ItemList TheMainList[] = { { sizeof(lkrec.lockid), LKI$_LOCKID, &lkrec.lockid, NULL }, { sizeof(lkrec.pid), LKI$_PID, &lkrec.pid, NULL }, { sizeof(lkrec.parent), LKI$_PARENT, &lkrec.parent, NULL }, { sizeof(lkrec.resnam) - 1, LKI$_RESNAM, &lkrec.resnam[0], &lkrec.rnamlen }, { sizeof(lkrec.state), LKI$_STATE, &lkrec.state, NULL }, { sizeof(lkrec.namspace), LKI$_NAMSPACE, &lkrec.namspace, NULL }, { 0, 0, NULL, NULL } }; LOCALDATA ItemList TheValueBlockList[] = { { sizeof(lkrec.valblk), LKI$_VALBLK, &lkrec.valblk[0], NULL }, { 0, 0, NULL, NULL } }; MEP__ PRIVATE CONDVAL SH_I_ShowLocks( void ) { int j; CONDVAL cv0; long efn; unsigned long mypid; unsigned long nWildId; char groupbuf[16]; char tmpbuf[256]; if (FancyLabels) { (*CallbackFnc)("\n\tProcess Locks:\n", CallbackArg); (*CallbackFnc)("\n\t\t", CallbackArg); } (*CallbackFnc)("lockid parent resource group access gr rq state valueblock", CallbackArg); if (FancyLabels) { (*CallbackFnc)("\n\t\t", CallbackArg); } (*CallbackFnc)("-------- -------- ------------------------------- ------ ------ -- -- ---------- -----------------------------------", CallbackArg); /* invoke an kernel-mode user system service to obtain the desired data for each lock, * because $getlki() will only look at locks at the current access mode or higher :-( */ nWildId = 0; mypid = (unsigned long) getpid(); lib$get_ef(&efn); while (1) { memset(&lkrec, 0, sizeof(lkrec)); IFGOOD(MH_GetLockInfo(efn, &nWildId, &TheMainList[0], &lkrec.lockid, &TheValueBlockList[0])) { if (lkrec.pid == mypid) { lkrec.rnamlen &= 0x0000FFFF; for (j = 0; j < lkrec.rnamlen; j++) { if (! isprint(lkrec.resnam[j])) { lkrec.resnam[j] = '.'; } } lkrec.resnam[j] = '\0'; if (lkrec.namspace.lki$v_sysnam) { strcpy(groupbuf, "system"); } else { sprintf(groupbuf, "%06o", (int) lkrec.namspace.lki$w_group); } sprintf(tmpbuf, "%8X %8X %-31.31s %s %s %s %s %s %8X %8X %8X %8X", lkrec.lockid, lkrec.parent, lkrec.resnam, groupbuf, DSC_I_LookupLockAccess((int) lkrec.namspace.lki$b_rmod), DSC_I_LookupLockMode((int) lkrec.state.lki$b_state_grmode), DSC_I_LookupLockMode((int) lkrec.state.lki$b_state_rqmode), DSC_I_LookupLockQueue((int) lkrec.state.lki$b_state_queue), lkrec.valblk[0], lkrec.valblk[1], lkrec.valblk[2], lkrec.valblk[3]); if (FancyLabels) { (*CallbackFnc)("\n\t\t", CallbackArg); } (*CallbackFnc)(tmpbuf, CallbackArg); } } else if (cv0 == SS$_NOPRIV || cv0 == SS$_NOWORLD || cv0 == SS$_NOSYSLCK) { continue; } else if (cv0 == SS$_ILLRSDM) { continue; } else if (cv0 == SS$_NOMORELOCK) { break; } else { fprintf(stderr, "unable to obtain lock information: status=%08X\n", cv0); break; } } lib$free_ef(&efn); if (FancyLabels) (*CallbackFnc)("\n", CallbackArg); return(SS$_NORMAL); } MEP__ PRIVATE CONDVAL SH_I_ShowTimers( void ) { #ifdef SAMPLE_OUTPUT Process Timers: # expires reqidt pid efn astadr cpu? type ---- ----------------------- -------- -------- --- -------- ---- ----------------------------------------- 1 13-JAN-1994 16:12:00.17 298 230041 0 0 no normal timer request 2 13-JAN-1994 16:12:49.45 7FFBE3E0 230041 31 7FEDD850 no normal timer request 3 13-JAN-1994 16:13:00.17 29a 230041 0 0 no normal timer request 4 13-JAN-1994 16:14:00.17 29c 230041 0 0 no normal timer request #endif CONDVAL cv0; int j; int count; char *tmpptr; unsigned short timlen; char firebuf[23 + 1]; char descbuf[100]; char tmpbuf[256]; MHTimerFlags tmpmask; struct dsc$descriptor_s datesd; MHTimerInfo *foo; MHTimerInfo thearray[SHERLOCK_MAX_TIMERS]; datesd.dsc$a_pointer = firebuf; datesd.dsc$w_length = sizeof(firebuf) - 1; datesd.dsc$b_dtype = 0; datesd.dsc$b_class = 0; if (FancyLabels) { (*CallbackFnc)("\n\tProcess Timers:\n", CallbackArg); (*CallbackFnc)("\n\t\t", CallbackArg); } (*CallbackFnc)(" # expires reqidt pid efn astadr type", CallbackArg); if (FancyLabels) { (*CallbackFnc)("\n\t\t", CallbackArg); } (*CallbackFnc)("---- ----------------------- -------- -------- --- -------- ----------------------------------------------", CallbackArg); #ifdef __VAX count = MH_GetTimerInfo(0, SHERLOCK_MAX_TIMERS, thearray); foo = &thearray[0]; for (j = 0; j < count; j++, foo++) { tmpmask.themask = foo->rqtype; if (tmpmask.thebits.sssngl) tmpptr = "system subroutine request"; else if (tmpmask.thebits.wksngl) tmpptr = "wake entry request"; else tmpptr = "normal timer request"; strcpy(descbuf, tmpptr); if (tmpmask.thebits.repeat) { sys$asctim(&timlen, &datesd, &foo->delta[0], 1); firebuf[timlen] = '\0'; strcat(descbuf, " (repeated every "); strcat(descbuf, firebuf); strcat(descbuf, ")"); } if (tmpmask.thebits.chk_cputim) strcat(descbuf, " (cpu-based)"); if (tmpmask.thebits.absolute) ; sys$asctim(NULL, &datesd, &foo->time[0], 0); sprintf(tmpbuf, "%4d %-23.23s %8X %8X %3d %8X %s", j + 1, firebuf, foo->huh2.zz.astprm, foo->huh1.pid, foo->efn, foo->huh2.zz.ast, descbuf); if (FancyLabels) { (*CallbackFnc)("\n\t\t", CallbackArg); } (*CallbackFnc)(tmpbuf, CallbackArg); } #else /* the MH_GetTimerInfo() function access violates under AXP/VMS, * and i don't have time to debug it. sorry. */ if (FancyLabels) { (*CallbackFnc)("\n\t\t", CallbackArg); } (*CallbackFnc)("", CallbackArg); #endif if (j == 0) { if (FancyLabels) { (*CallbackFnc)("\n\t\t", CallbackArg); } (*CallbackFnc)("", CallbackArg); } if (FancyLabels) (*CallbackFnc)("\n", CallbackArg); return(SS$_NORMAL); } MEP__ PUBLIC CONDVAL SH_Show( unsigned long shmask, FILE *fp, CONDVAL (*rtnptr)(), void *rtnarg, void (*imagecallback)(char *, void *, void *) ) { BOOL sf; BOOL si; CONDVAL cv0; if (shmask == 0) shmask = SHERLOCK_M_ALL; if ((CallbackFnc = rtnptr) == NULL) { if (fp == NULL) fp = stdout; CallbackFnc = SH_I_Output; CallbackArg = (void *) fp; FancyLabels = TRUE; } else { CallbackArg = rtnarg; FancyLabels = FALSE; } sf = ((shmask & SHERLOCK_M_FILES) == 0) ? FALSE : TRUE; si = ((shmask & SHERLOCK_M_IMAGES) == 0) ? FALSE : TRUE; if (sf || si) { SH_I_ShowFilesAndOrImages(sf, si, imagecallback); } if ((shmask & SHERLOCK_M_TIMERS) != 0) { SH_I_ShowTimers(); } if ((shmask & SHERLOCK_M_LOCKS) != 0) { SH_I_ShowLocks(); } return(SS$_NORMAL); } MEP__ PUBLIC CONDVAL SH_Interactive( void ) { unsigned long themask; FILE *fp; char *fsptr; char *modeptr; char reply[132]; char filespec[255]; themask = 0; printf("\nDo you want to see OPEN FILE information? [Y]: "); if (fgets(reply, sizeof(reply) - 1, stdin) == NULL) return(SS$_NORMAL); if (reply[0] != 'n' && reply[0] != 'N') { themask |= SHERLOCK_M_FILES; } printf("\nDo you want to see IMAGE information? [N]: "); if (fgets(reply, sizeof(reply) - 1, stdin) == NULL) return(SS$_NORMAL); if (reply[0] == 'y' || reply[0] == 'Y') { themask |= SHERLOCK_M_IMAGES; } printf("\nDo you want to see TIMER information? [N]: "); if (fgets(reply, sizeof(reply) - 1, stdin) == NULL) return(SS$_NORMAL); if (reply[0] == 'y' || reply[0] == 'Y') { themask |= SHERLOCK_M_TIMERS; } printf("\nDo you want to see LOCK information? [N]: "); if (fgets(reply, sizeof(reply) - 1, stdin) == NULL) return(SS$_NORMAL); if (reply[0] == 'y' || reply[0] == 'Y') { themask |= SHERLOCK_M_LOCKS; } fp = NULL; if (themask != 0) { printf("\nWhere should the output go? [the terminal]: "); if (fgets(filespec, sizeof(reply) - 1, stdin) == NULL) return(SS$_NORMAL); if (filespec[0] != '\0') { fsptr = filespec; modeptr = "w"; if (*fsptr == '+') { ++fsptr; modeptr = "a"; } fp = fopen(fsptr, modeptr); } SH_Show(themask, fp, NULL, NULL, NULL); } if (fp == NULL) { printf("\nPress to continue: "); fgets(reply, sizeof(reply) - 1, stdin); } else { fclose(fp); } return(SS$_NORMAL); }