1 /*
2  * (C) Copyright 2002, Brian Knittel.
3  * You may freely use this program, but: it offered strictly on an AS-IS, AT YOUR OWN
4  * RISK basis, there is no warranty of fitness for any purpose, and the rest of the
5  * usual yada-yada. Please keep this notice and the copyright in any distributions
6  * or modifications.
7  *
8  * This is not a supported product, but I welcome bug reports and fixes.
9  * Mail to sim@ibm1130.org
10  */
11 
12 // DISKVIEW - lists contents of an 1130 system disk image file. Not finished yet.
13 // needs LET/SLET listing routine.
14 //
15 // usage:
16 //		diskview -v diskfile
17 
18 #include <stdio.h>
19 #include <stdlib.h>
20 #include <string.h>
21 #include <stdarg.h>
22 #include "util_io.h"
23 
24 #define BETWEEN(v,a,b) (((v) >= (a)) && ((v) <= (b)))
25 #define MIN(a,b)       (((a) <= (b)) ? (a) : (b))
26 #define MAX(a,b)       (((a) >= (b)) ? (a) : (b))
27 
28 #ifndef TRUE
29 #   define TRUE  1
30 #   define FALSE 0
31 #   define BOOL  int
32 #endif
33 
34 #define NOT_DEF	0x0658				// defective cylinder table entry means no defect
35 
36 #define DSK_NUMWD	321				/* words/sector */
37 #define DSK_NUMCY	203				/* cylinders/drive */
38 #define DSK_SECCYL	  8				/* sectors per cylinder */
39 #define SECLEN		320				/* data words per sector */
40 #define SLETLEN		((3*SECLEN)/4)	/* length of slet in records */
41 
42 typedef unsigned short WORD;
43 
44 FILE *fp;
45 WORD buf[DSK_NUMWD];
46 WORD dcom[DSK_NUMWD];
47 
48 #pragma pack(2)
49 struct tag_slet {
50 	WORD	phid;
51 	WORD	addr;
52 	WORD	nwords;
53 	WORD	sector;
54 } slet[SLETLEN];
55 
56 #pragma pack()
57 
58 WORD dcyl[3];
59 BOOL verbose = FALSE;
60 
61 void checksectors (void);
62 void dump_id      (void);
63 void dump_dcom    (void);
64 void dump_resmon  (void);
65 void dump_slet    (void);
66 void dump_hdng    (void);
67 void dump_scra    (void);
68 void dump_let     (void);
69 void dump_flet    (void);
70 void dump_cib     (void);
71 void getsector    (int sec, WORD *sbuf);
72 void getdcyl      (void);
73 char *lowcase (char *str);
74 
75 void bail(char *fmt, ...);
76 char *trim (char *s);
77 
main(int argc,char ** argv)78 int main (int argc, char **argv)
79 {
80 	char *fname = NULL, *arg;
81 	static char usestr[] = "Usage: diskview [-v] filename";
82 	int i;
83 
84 	for (i = 1; i < argc;) {
85 		arg = argv[i++];
86 		if (*arg == '-') {
87 			arg++;
88 			lowcase(arg);
89 			while (*arg) {
90 				switch (*arg++) {
91 					case 'v':
92 						verbose = TRUE;
93 						break;
94 
95 					default:
96 						bail(usestr);
97 				}
98 			}
99 		}
100 		else if (fname == NULL)
101 			fname = arg;
102 		else
103 			bail(usestr);
104 	}
105 
106 	if (fname == NULL)
107 		bail(usestr);
108 
109 	if ((fp = fopen(fname, "rb")) == NULL) {
110 		perror(fname);
111 		return 2;
112 	}
113 
114 	printf("%s:\n", fname);
115 
116 	checksectors();
117 	getdcyl();
118 
119 	dump_id();				// ID & coldstart
120 	dump_dcom();			// DCOM
121 	dump_resmon();			// resident image
122 	dump_slet();			// SLET
123 	dump_hdng();			// heading sector
124 	dump_scra();
125 	dump_flet();
126 	dump_cib();
127 	dump_let();
128 
129 	fclose(fp);
130 	return 0;
131 }
132 
133 // checksectors - verify that all sectors are properly numbered
134 
checksectors()135 void checksectors ()
136 {
137 	WORD sec = 0;
138 
139 	fseek(fp, 0, SEEK_SET);
140 
141 	for (sec = 0; sec < DSK_NUMCY*DSK_SECCYL; sec++) {
142 		if (fxread(buf, sizeof(WORD), DSK_NUMWD, fp) != DSK_NUMWD)
143 			bail("File read error or not a disk image file");
144 
145 		if (buf[0] != sec)
146 			bail("Sector /%x is misnumbered, run checkdisk [-f]", sec);
147 	}
148 }
149 
150 // get defective cylinder list
151 
getdcyl(void)152 void getdcyl (void)
153 {
154 	fseek(fp, sizeof(WORD), SEEK_SET);	// skip sector count
155 	if (fxread(dcyl, sizeof(WORD), 3, fp) != 3)
156 		bail("Unable to read defective cylinder table");
157 }
158 
159 // getsector - read specified absolute sector
160 
getsector(int sec,WORD * sbuf)161 void getsector (int sec, WORD *sbuf)
162 {
163 	int i, cyl, ssec;
164 
165 	sec &= 0x7FF;					// mask of drive bits, if any
166 
167 	cyl  = sec / DSK_SECCYL;		// get cylinder
168 	ssec = sec & ~(DSK_SECCYL-1);	// mask to get starting sector of cylinder
169 	for (i = 0; i < 3; i++) {		// map through defective cylinder table
170 		if (dcyl[i] == ssec) {
171 			sec &= (DSK_SECCYL-1);	// mask to get base sector
172 			cyl  = DSK_NUMCY-3+i;	// replacements are last three on disk
173 			sec += cyl*DSK_SECCYL;	// add new cylinder offset
174 			break;
175 		}
176 	}
177 									// read the sector
178 	if (fseek(fp, (sec*DSK_NUMWD+1)*sizeof(WORD), SEEK_SET) != 0)
179 		bail("File seek failed");
180 
181 	if (fxread(sbuf, sizeof(WORD), DSK_NUMWD, fp) != DSK_NUMWD)
182 		bail("File read error or not a disk image file");
183 }
184 
dump(int nwords)185 void dump (int nwords)
186 {
187 	int i, nline = 0;
188 
189 	for (i = 0; i < nwords; i++) {
190 		if (nline == 16) {
191 			putchar('\n');
192 			nline = 0;
193 		}
194 
195 		printf("%04x", buf[i]);
196 		nline++;
197 	}
198 	putchar('\n');
199 }
200 
showmajor(char * label)201 void showmajor (char *label)
202 {
203 	int i;
204 
205 	printf("\n--- %s ", label);
206 
207 	for (i = strlen(label); i < 40; i++)
208 		putchar('-');
209 
210 	putchar('\n');
211 	putchar('\n');
212 }
213 
name(char * label)214 void name (char *label)
215 {
216 	printf("%-32.32s ", label);
217 }
218 
pbf(char * label,WORD * buf,int nwords)219 void pbf (char *label, WORD *buf, int nwords)
220 {
221 	int i, nout;
222 
223 	name(label);
224 
225 	for (i = nout = 0; i < nwords; i++, nout++) {
226 		if (nout == 8) {
227 			putchar('\n');
228 			name("");
229 			nout = 0;
230 		}
231 		printf(" %04x", buf[i]);
232 	}
233 
234 	putchar('\n');
235 }
236 
prt(char * label,char * fmt,...)237 void prt (char *label, char *fmt, ...)
238 {
239 	va_list args;
240 
241 	name(label);
242 
243 	putchar(' ');
244 	va_start(args, fmt);
245 	vprintf(fmt, args);
246 	va_end(args);
247 
248 	putchar('\n');
249 }
250 
dump_id(void)251 void dump_id (void)
252 {
253 	showmajor("Sector 0 - ID & coldstart");
254 	getsector(0, buf);
255 
256 	pbf("DCYL  def cyl table", buf+  0, 3);
257 	pbf("CIDN  cart id",       buf+  3, 1);
258 	pbf("      copy code",     buf+  4, 1);
259 	pbf("DTYP  disk type",     buf+  7, 1);
260 	pbf("      diskz copy",    buf+ 30, 8);
261 	pbf("      cold start pgm",buf+270, 8);
262 }
263 
264 // EQUIVALENCES FOR DCOM PARAMETERS
265 #define NAME  4   // NAME OF PROGRAM/CORE LOAD
266 #define DBCT  6   // BLOCK CT OF PROGRAM/CORE LOAD
267 #define FCNT  7   // FILES SWITCH
268 #define SYSC  8   // SYSTEM/NON-SYSTEM CARTRIDGE INDR
269 #define JBSW  9   // JOBT SWITCH
270 #define CBSW 10   // CLB-RETURN SWITCH
271 #define LCNT 11   // NO. OF LOCALS
272 #define MPSW 12   // CORE MAP SWITCH
273 #define MDF1 13   // NO. DUP CTRL RECORDS (MODIF)
274 #define MDF2 14   // ADDR OF MODIF BUFFER
275 #define NCNT 15   // NO. OF NOCALS
276 #define ENTY 16   // RLTV ENTRY ADDR OF PROGRAM
277 #define RP67 17   // 1442-5 SWITCH
278 #define TODR 18   // OBJECT WORK STORAGE DRIVE CODE
279 #define FHOL 20   // ADDR LARGEST HOLE IN FIXED AREA
280 #define FSZE 21   // BLK CNT LARGEST HOLE IN FXA
281 #define UHOL 22   // ADDR LAST HOLE IN USER AREA 2-10
282 #define USZE 23   // BLK CNT LAST HOLE IN UA     2-10
283 #define DCSW 24   // DUP CALL SWITCH
284 #define PIOD 25   // PRINCIPAL I/O DEVICE INDICATOR
285 #define PPTR 26   // PRINCIPAL PRINT DEVICE INDICATOR
286 #define CIAD 27   // RLTV ADDR IN @STRT OF CIL ADDR
287 #define ACIN 28   // AVAILABLE CARTRIDGE INDICATOR
288 #define GRPH 29   // 2250 INDICATOR               2G2
289 #define GCNT 30   // NO. G2250 RECORDS            2G2
290 #define LOSW 31   // LOCAL-CALLS-LOCAL SWITCH     2-2
291 #define X3SW 32   // SPECIAL ILS SWITCH           2-2
292 #define ECNT 33   // NO. OF *EQUAT RCDS           2-4
293 #define ANDU 35   // 1+BLK ADDR END OF UA (ADJUSTED)
294 #define BNDU 40   // 1+BLK ADDR END OF UA (BASE)
295 #define FPAD 45   // FILE PROTECT ADDR
296 #define PCID 50   // CARTRIDGE ID, PHYSICAL DRIVE
297 #define CIDN 55   // CARTRIDGE ID, LOGICAL DRIVE
298 #define CIBA 60   // SCTR ADDR OF CIB
299 #define SCRA 65   // SCTR ADDR OF SCRA
300 #define FMAT 70   // FORMAT OF PROG IN WORKING STG
301 #define FLET 75   // SCTR ADDR 1ST SCTR OF FLET
302 #define ULET 80   // SCTR ADDR 1ST SCTR OF LET
303 #define WSCT 85   // BLK CNT OF PROG IN WORKING STG
304 #define CSHN 90   // NO. SCTRS IN CUSHION AREA
305 
306 struct tag_dcominfo {
307 	char *nm;
308 	int offset;
309 	char *descr;
310 } dcominfo[] = {
311 	"NAME",  4, "NAME OF PROGRAM/CORE LOAD",
312 	"DBCT",  6, "BLOCK CT OF PROGRAM/CORE LOAD",
313 	"FCNT",  7, "FILES SWITCH",
314 	"SYSC",  8, "SYSTEM/NON-SYSTEM CARTRIDGE INDR",
315 	"JBSW",  9, "JOBT SWITCH",
316 	"CBSW", 10, "CLB-RETURN SWITCH",
317 	"LCNT", 11, "NO. OF LOCALS",
318 	"MPSW", 12, "CORE MAP SWITCH",
319 	"MDF1", 13, "NO. DUP CTRL RECORDS (MODIF)",
320 	"MDF2", 14, "ADDR OF MODIF BUFFER",
321 	"NCNT", 15, "NO. OF NOCALS",
322 	"ENTY", 16, "RLTV ENTRY ADDR OF PROGRAM",
323 	"RP67", 17, "1442-5 SWITCH",
324 	"TODR", 18, "OBJECT WORK STORAGE DRIVE CODE",
325 	"FHOL", 20, "ADDR LARGEST HOLE IN FIXED AREA",
326 	"FSZE", 21, "BLK CNT LARGEST HOLE IN FXA",
327 	"UHOL", 22, "ADDR LAST HOLE IN USER AREA",
328 	"USZE", 23, "BLK CNT LAST HOLE IN UA",
329 	"DCSW", 24, "DUP CALL SWITCH",
330 	"PIOD", 25, "PRINCIPAL I/O DEVICE INDICATOR",
331 	"PPTR", 26, "PRINCIPAL PRINT DEVICE INDICATOR",
332 	"CIAD", 27, "RLTV ADDR IN @STRT OF CIL ADDR",
333 	"ACIN", 28, "AVAILABLE CARTRIDGE INDICATOR",
334 	"GRPH", 29, "2250 INDICATOR",
335 	"GCNT", 30, "NO. G2250 RECORDS",
336 	"LOSW", 31, "LOCAL-CALLS-LOCAL SWITCH",
337 	"X3SW", 32, "SPECIAL ILS SWITCH",
338 	"ECNT", 33, "NO. OF *EQUAT RCDS",
339 	"ANDU", 35, "1+BLK ADDR END OF UA (ADJUSTED)",
340 	"BNDU", 40, "1+BLK ADDR END OF UA (BASE)",
341 	"FPAD", 45, "FILE PROTECT ADDR",
342 	"PCID", 50, "CARTRIDGE ID, PHYSICAL DRIVE",
343 	"CIDN", 55, "CARTRIDGE ID, LOGICAL DRIVE",
344 	"CIBA", 60, "SCTR ADDR OF CIB",
345 	"SCRA", 65, "SCTR ADDR OF SCRA",
346 	"FMAT", 70, "FORMAT OF PROG IN WORKING STG",
347 	"FLET", 75, "SCTR ADDR 1ST SCTR OF FLET",
348 	"ULET", 80, "SCTR ADDR 1ST SCTR OF LET",
349 	"WSCT", 85, "BLK CNT OF PROG IN WORKING STG",
350 	"CSHN", 90, "NO. SCTRS IN CUSHION AREA",
351 	NULL
352 };
353 
dump_dcom(void)354 void dump_dcom (void)
355 {
356 	struct tag_dcominfo *d;
357 	char txt[50];
358 
359 	showmajor("Sector 1 - DCOM");
360 	getsector(1, dcom);
361 
362 	for (d = dcominfo; d->nm != NULL; d++) {
363 		sprintf(txt, "%-4.4s %s", d->nm, d->descr);
364 		pbf(txt, dcom+d->offset, 1);
365 	}
366 }
367 
dump_resmon(void)368 void dump_resmon (void)
369 {
370 	showmajor("Sector 2 - Resident Image");
371 	getsector(2, buf);
372 	dump(verbose ? SECLEN : 32);
373 }
374 
375 struct {
376 	int pfrom, pto;
377 	int printed;
378 	char *name;
379 } sletinfo[] = {
380 	0x01,	0x12,	FALSE, "DUP",
381 	0x1F,	0x39,	FALSE, "Fortran",
382 	0x51,	0x5C,	FALSE, "Cobol",
383 	0x6E,	0x74,	FALSE, "Supervisor",
384 	0x78,	0x84,	FALSE, "Core Load Builder",
385 	0x8C,	0x8C,	FALSE, "Sys 1403 prt",
386 	0x8D,	0x8D,	FALSE, "Sys 1132 prt",
387 	0x8E,	0x8E,	FALSE, "Sys console prt",
388 	0x8F,	0x8F,	FALSE, "Sys 2501 rdr",
389 	0x90,	0x90,	FALSE, "Sys 1442 rdr/pun",
390 	0x91,	0x91,	FALSE, "Sys 1134 paper tape",
391 	0x92,	0x92,	FALSE, "Sys kbd",
392 	0x93,	0x93,	FALSE, "Sys 2501/1442 conv",
393 	0x94,	0x94,	FALSE, "Sys 1134 conv",
394 	0x95,	0x95,	FALSE, "Sys kbd conv",
395 	0x96,	0x96,	FALSE, "Sys diskz",
396 	0x97,	0x97,	FALSE, "Sys disk1",
397 	0x98,	0x98,	FALSE, "Sys diskn",
398 	0x99,	0x99,	FALSE, "(primary print)",
399 	0x9A,	0x9A,	FALSE, "(primary input)",
400 	0x9B,	0x9B,	FALSE, "(primary input excl kbd)",
401 	0x9C,	0x9C,	FALSE, "(primary sys conv)",
402 	0x9D,	0x9D,	FALSE, "(primary conv excl kbd)",
403 	0xA0,	0xA1,	FALSE, "Core Image Loader",
404 	0xB0,	0xCC,	FALSE, "RPG",
405 	0xCD,	0xCE,	FALSE, "Dup Part 2",
406 	0xCF,	0xF6,	FALSE, "Macro Assembler",
407 	0
408 };
409 
dump_slet(void)410 void dump_slet (void)
411 {
412 	int i, j, iphase, nsecs, sec, max_sec = 0;
413 	char sstr[16], *smark;
414 
415 	showmajor("Sectors 3-5 - SLET");
416 	for (i = 0; i < 3; i++) {
417 		getsector(3+i, buf);
418 		memmove(((WORD *) slet)+SECLEN*i, buf, SECLEN*sizeof(WORD));
419 	}
420 
421     printf("#   PHID      Addr  Len Sector        Secs\n");
422 	printf("------------------------------------------\n");
423 	for (i = 0; i < SLETLEN; i++) {
424 		if (slet[i].phid == 0)
425 			break;
426 
427 		sec    = slet[i].sector;
428 		iphase = (int) (signed short) slet[i].phid;
429 		nsecs  = (slet[i].nwords + SECLEN-1)/SECLEN;
430 
431 		if (sec & 0xF800) {
432 			smark = "*";
433 			sec  &= 0x7FF;
434 		}
435 		else
436 			smark = " ";
437 
438 		for (j = 0; sletinfo[j].pfrom != 0; j++)
439 			if (sletinfo[j].pfrom <= iphase && sletinfo[j].pto >= iphase)
440 				break;
441 
442 		sprintf(sstr, "(%d.%d)", sec / DSK_SECCYL, slet[i].sector % DSK_SECCYL);
443 
444 		printf("%3d %04x %4d %04x %04x %04x %s %-7s %3x",
445 			i, slet[i].phid, iphase, slet[i].addr, slet[i].nwords, slet[i].sector, smark, sstr, nsecs);
446 
447 		if (iphase < 0)
448 			iphase = -iphase;
449 
450 		if (sletinfo[j].pfrom == 0)
451 			printf(" ???");
452 		else if (! sletinfo[j].printed) {
453 			printf(" %s", sletinfo[j].name);
454 			sletinfo[j].printed = TRUE;
455 		}
456 
457 		for (j = 0; j < i; j++) {
458 			if (sec == (slet[j].sector & 0x7FF)) {
459 				printf(" (same as %04x)", slet[j].phid);
460 				break;
461 			}
462 		}
463 
464 		max_sec = MAX(max_sec, sec+nsecs-1);		// find last sector used
465 
466 		putchar('\n');
467 
468 		if (i >= 15 && ! verbose) {
469 			printf("...\n");
470 			break;
471 		}
472 	}
473 }
474 
475 int ascii_to_ebcdic_table[128] =
476 {
477 	0x00,0x01,0x02,0x03,0x37,0x2d,0x2e,0x2f, 0x16,0x05,0x25,0x0b,0x0c,0x0d,0x0e,0x0f,
478 	0x10,0x11,0x12,0x13,0x3c,0x3d,0x32,0x26, 0x18,0x19,0x3f,0x27,0x1c,0x1d,0x1e,0x1f,
479 	0x40,0x5a,0x7f,0x7b,0x5b,0x6c,0x50,0x7d, 0x4d,0x5d,0x5c,0x4e,0x6b,0x60,0x4b,0x61,
480 	0xf0,0xf1,0xf2,0xf3,0xf4,0xf5,0xf6,0xf7, 0xf8,0xf9,0x7a,0x5e,0x4c,0x7e,0x6e,0x6f,
481 
482 	0x7c,0xc1,0xc2,0xc3,0xc4,0xc5,0xc6,0xc7, 0xc8,0xc9,0xd1,0xd2,0xd3,0xd4,0xd5,0xd6,
483 	0xd7,0xd8,0xd9,0xe2,0xe3,0xe4,0xe5,0xe6, 0xe7,0xe8,0xe9,0xba,0xe0,0xbb,0xb0,0x6d,
484 	0x79,0x81,0x82,0x83,0x84,0x85,0x86,0x87, 0x88,0x89,0x91,0x92,0x93,0x94,0x95,0x96,
485 	0x97,0x98,0x99,0xa2,0xa3,0xa4,0xa5,0xa6, 0xa7,0xa8,0xa9,0xc0,0x4f,0xd0,0xa1,0x07,
486 };
487 
ebcdic_to_ascii(int ch)488 int ebcdic_to_ascii (int ch)
489 {
490 	int j;
491 
492 	for (j = 32; j < 128; j++)
493 		if (ascii_to_ebcdic_table[j] == ch)
494 			return j;
495 
496 	return '?';
497 }
498 
499 #define HDR_LEN 120
500 
dump_hdng(void)501 void dump_hdng(void)
502 {
503 	int i;
504 	char str[HDR_LEN+1], *p = str;
505 
506 	showmajor("Sector 7 - Heading");
507 	getsector(7, buf);
508 
509 	for (i = 0; i < (HDR_LEN/2); i++) {
510 		*p++ = ebcdic_to_ascii((buf[i] >> 8) & 0xFF);
511 		*p++ = ebcdic_to_ascii( buf[i]       & 0xFF);
512 	}
513 
514 	*p = '\0';
515 	trim(str);
516 	printf("%s\n", str);
517 }
518 
mget(int offset,char * name)519 BOOL mget (int offset, char *name)
520 {
521 	char title[80];
522 
523 	if (dcom[offset] == 0)
524 		return FALSE;
525 
526 	getsector(dcom[offset], buf);
527 	sprintf(title, "Sector %x - %s", dcom[offset], name);
528 	showmajor(title);
529 	return TRUE;
530 }
531 
dump_scra(void)532 void dump_scra (void)
533 {
534 	if (! mget(SCRA, "SCRA"))
535 		return;
536 
537 	dump(verbose ? SECLEN : 32);
538 }
539 
dump_let(void)540 void dump_let (void)
541 {
542 	if (! mget(ULET, "LET"))
543 		return;
544 }
545 
dump_flet(void)546 void dump_flet (void)
547 {
548 	if (! mget(FLET, "FLET"))
549 		return;
550 }
551 
dump_cib(void)552 void dump_cib (void)
553 {
554 	if (! mget(CIBA, "CIB"))
555 		return;
556 
557 	dump(verbose ? SECLEN : 32);
558 }
559 
560 #define LFHD 5    // WORD COUNT OF LET/FLET HEADER    PMN09970
561 #define LFEN 3    // NO OF WDS PER LET/FLET ENTRY     PMN09980
562 #define SCTN 0    // RLTY ADDR OF LET/FLET SCTR NO.   PMN09990
563 #define UAFX 1    // RLTV ADDR OF SCTR ADDR OF UA/FXA PMN10000
564 #define WDSA 3    // RLTV ADDR OF WDS AVAIL IN SCTR   PMN10010
565 #define NEXT 4    // RLTV ADDR OF ADDR NEXT SCTR      PMN10020
566 #define LFNM 0    // RLTV ADDR OF LET/FLET ENTRY NAME PMN10030
567 #define BLCT 2    // RLTV ADDR OF LET/FLET ENTRY DBCT PMN10040
568 
bail(char * fmt,...)569 void bail (char *fmt, ...)
570 {
571 	va_list args;
572 
573 	va_start(args, fmt);
574 	fprintf(stderr, fmt, args);
575 	va_end(args);
576 	putchar('\n');
577 
578 	exit(1);
579 }
580 
581 // ---------------------------------------------------------------------------------
582 // trim - remove trailing whitespace from string s
583 // ---------------------------------------------------------------------------------
584 
trim(char * s)585 char *trim (char *s)
586 {
587 	char *os = s, *nb;
588 
589 	for (nb = s-1; *s; s++)
590 		if (*s > ' ')
591 			nb = s;
592 
593 	nb[1] = '\0';
594 	return os;
595 }
596 
597 /* ------------------------------------------------------------------------
598  * lowcase - force a string to lowercase (ASCII)
599  * ------------------------------------------------------------------------ */
600 
lowcase(char * str)601 char *lowcase (char *str)
602 {
603 	char *s;
604 
605 	for (s = str; *s; s++) {
606 		if (*s >= 'A' && *s <= 'Z')
607 			*s += 32;
608 	}
609 
610 	return str;
611 }
612 
613