Check for SYS/GL during library init. Reason is that
[AROS.git] / workbench / devs / ramdrive_device.c
blob70a36f01b9207556f16fbecf9325cef762572172
1 /*
2 Copyright © 1995-2007, The AROS Development Team. All rights reserved.
3 $Id$
4 */
6 /****************************************************************************************/
8 #define NUM_HEADS 2
9 #define NUM_CYL 80
10 #define NUM_SECS 11
11 #define BLOCKSIZE 512
13 #define DISKSIZE (NUM_HEADS * NUM_CYL * NUM_SECS * BLOCKSIZE)
14 #define NUM_TRACKS (NUM_CYL * NUM_HEADS)
16 /****************************************************************************************/
18 #include <devices/trackdisk.h>
19 #include <devices/newstyle.h>
20 #include <exec/resident.h>
21 #include <exec/errors.h>
22 #include <exec/memory.h>
23 #include <exec/initializers.h>
24 #include <proto/exec.h>
25 #include <dos/dosextens.h>
26 #include <dos/dostags.h>
27 #include <proto/dos.h>
28 #include <aros/macros.h>
29 #include <aros/libcall.h>
30 #include <aros/symbolsets.h>
31 #include <string.h>
33 #if defined(__GNUC__) || defined(__INTEL_COMPILER)
34 #include "ramdrive_device_gcc.h"
35 #endif
37 #define DEBUG 0
38 #include <aros/debug.h>
40 #include LC_LIBDEFS_FILE
42 /****************************************************************************************/
44 #define NEWSTYLE_DEVICE 1
46 #if NEWSTYLE_DEVICE
48 static const UWORD SupportedCommands[] =
50 CMD_FLUSH,
51 CMD_READ,
52 CMD_WRITE,
53 CMD_UPDATE,
54 CMD_CLEAR,
55 TD_CHANGENUM,
56 TD_CHANGESTATE,
57 TD_PROTSTATUS,
58 TD_REMOVE,
59 TD_ADDCHANGEINT,
60 TD_REMCHANGEINT,
61 TD_GETDRIVETYPE,
62 TD_GETNUMTRACKS,
63 TD_FORMAT,
64 TD_RAWREAD,
65 TD_RAWWRITE,
66 TD_SEEK,
67 TD_MOTOR,
68 ETD_READ,
69 ETD_WRITE,
70 ETD_UPDATE,
71 ETD_CLEAR,
72 ETD_MOTOR,
73 ETD_SEEK,
74 ETD_FORMAT,
75 ETD_RAWREAD,
76 ETD_RAWWRITE,
77 NSCMD_DEVICEQUERY,
81 #endif
83 /****************************************************************************************/
85 static void FormatOFS(UBYTE *mem, ULONG number, struct unit *unit);
87 /****************************************************************************************/
89 static int GM_UNIQUENAME(Init)(LIBBASETYPEPTR ramdrivebase)
91 D(bug("ramdrive_device: in libinit func\n"));
93 InitSemaphore(&ramdrivebase->sigsem);
94 NEWLIST((struct List *)&ramdrivebase->units);
95 ramdrivebase->port.mp_Node.ln_Type = NT_MSGPORT;
96 ramdrivebase->port.mp_Flags = PA_SIGNAL;
97 ramdrivebase->port.mp_SigBit = SIGB_SINGLE;
98 NEWLIST((struct List *)&ramdrivebase->port.mp_MsgList);
100 D(bug("ramdrive_device: in libinit func. Returning %x (success) :-)\n", ramdrivebase));
101 return TRUE;
104 /****************************************************************************************/
106 AROS_UFP3(LONG, unitentry,
107 AROS_UFPA(STRPTR, argstr, A0),
108 AROS_UFPA(ULONG, arglen, D0),
109 AROS_UFPA(struct ExecBase *, SysBase, A6));
111 /****************************************************************************************/
113 static int GM_UNIQUENAME(Open)
115 LIBBASETYPEPTR ramdrivebase,
116 struct IOExtTD *iotd,
117 ULONG unitnum,
118 ULONG flags
121 static const struct TagItem tags[] =
123 { NP_Name , (IPTR)"Ram Drive Unit Process"},
124 { NP_Input , 0 },
125 { NP_Output , 0 },
126 { NP_Error , 0 },
127 { NP_CurrentDir , 0 },
128 { NP_Priority , 0 },
129 { NP_HomeDir , 0 },
130 { NP_CopyVars , 0 },
131 { NP_Entry , (IPTR)unitentry },
132 { TAG_END , 0 }
134 struct unit *unit;
136 D(bug("ramdrive_device: in libopen func.\n"));
138 if (iotd->iotd_Req.io_Message.mn_Length < sizeof(struct IOExtTD))
140 D(bug("ramdrive.device/open: IORequest structure passed to OpenDevice is too small!\n"));
141 iotd->iotd_Req.io_Error = IOERR_OPENFAIL;
142 return FALSE;
145 D(bug("ramdrive_device: in libopen func. Looking if unit is already open\n"));
147 ObtainSemaphore(&ramdrivebase->sigsem);
149 for(unit = (struct unit *)ramdrivebase->units.mlh_Head;
150 unit->msg.mn_Node.ln_Succ != NULL;
151 unit = (struct unit *)unit->msg.mn_Node.ln_Succ)
152 if(unit->unitnum == unitnum)
154 unit->usecount++;
155 ReleaseSemaphore(&ramdrivebase->sigsem);
157 iotd->iotd_Req.io_Unit = (struct Unit *)unit;
158 iotd->iotd_Req.io_Error = 0;
159 iotd->iotd_Req.io_Message.mn_Node.ln_Type = NT_REPLYMSG;
161 D(bug("ramdrive_device: in libopen func. Yep. Unit is already open\n"));
163 return TRUE;
166 D(bug("ramdrive_device: in libopen func. No, it is not. So creating new unit ...\n"));
168 unit = (struct unit *)AllocMem(sizeof(struct unit), MEMF_PUBLIC);
169 if(unit != NULL)
171 D(bug("ramdrive_device: in libopen func. Allocation of unit memory okay. Setting up unit and calling CreateNewProc ...\n"));
173 unit->usecount = 1;
174 unit->ramdrivebase = ramdrivebase;
175 unit->unitnum = unitnum;
176 unit->msg.mn_ReplyPort = &ramdrivebase->port;
177 unit->msg.mn_Length = sizeof(struct unit);
178 unit->port.mp_Node.ln_Type = NT_MSGPORT;
179 unit->port.mp_Flags = PA_IGNORE;
180 unit->port.mp_SigTask = CreateNewProc((struct TagItem *)tags);
182 D(bug("ramdrive_device: in libopen func. CreateNewProc called. Proc = %x\n", unit->port.mp_SigTask));
184 if(unit->port.mp_SigTask != NULL)
186 NEWLIST((struct List *)&unit->port.mp_MsgList);
188 /* setup replyport to point to active task */
189 ramdrivebase->port.mp_SigTask = FindTask(NULL);
190 SetSignal(0, SIGF_SINGLE);
192 D(bug("ramdrive_device: in libopen func. Sending startup msg\n"));
193 PutMsg(&((struct Process *)unit->port.mp_SigTask)->pr_MsgPort, &unit->msg);
195 D(bug("ramdrive_device: in libopen func. Waiting for replymsg\n"));
196 WaitPort(&ramdrivebase->port);
197 (void)GetMsg(&ramdrivebase->port);
198 D(bug("ramdrive_device: in libopen func. Received replymsg\n"));
200 if(unit->mem)
202 AddTail((struct List *)&ramdrivebase->units, &unit->msg.mn_Node);
203 iotd->iotd_Req.io_Unit = (struct Unit *)unit;
204 /* Set returncode */
205 iotd->iotd_Req.io_Error = 0;
206 ReleaseSemaphore(&ramdrivebase->sigsem);
207 return TRUE;
208 }else
209 iotd->iotd_Req.io_Error = TDERR_NotSpecified;
210 }else
211 iotd->iotd_Req.io_Error = TDERR_NoMem;
212 FreeMem(unit, sizeof(struct unit));
213 }else
214 iotd->iotd_Req.io_Error = TDERR_NoMem;
216 ReleaseSemaphore(&ramdrivebase->sigsem);
218 return FALSE;
221 /****************************************************************************************/
223 static int GM_UNIQUENAME(Close)
225 LIBBASETYPEPTR ramdrivebase,
226 struct IOExtTD *iotd
229 struct unit *unit;
231 ObtainSemaphore(&ramdrivebase->sigsem);
232 unit = (struct unit *)iotd->iotd_Req.io_Unit;
233 if(!--unit->usecount)
235 Remove(&unit->msg.mn_Node);
236 ramdrivebase->port.mp_SigTask = FindTask(NULL);
237 SetSignal(0, SIGF_SINGLE);
238 PutMsg(&unit->port, &unit->msg);
239 WaitPort(&ramdrivebase->port);
240 (void)GetMsg(&ramdrivebase->port);
241 FreeMem(unit, sizeof(struct unit));
243 ReleaseSemaphore(&ramdrivebase->sigsem);
245 return TRUE;
248 /****************************************************************************************/
250 ADD2INITLIB(GM_UNIQUENAME(Init), 0)
251 ADD2OPENDEV(GM_UNIQUENAME(Open), 0)
252 ADD2CLOSEDEV(GM_UNIQUENAME(Close), 0)
254 /****************************************************************************************/
256 AROS_LH1(void, beginio,
257 AROS_LHA(struct IOExtTD *, iotd, A1),
258 struct ramdrivebase *, ramdrivebase, 5, Ramdrive)
260 AROS_LIBFUNC_INIT
262 switch(iotd->iotd_Req.io_Command)
264 #if NEWSTYLE_DEVICE
265 case NSCMD_DEVICEQUERY:
266 if(iotd->iotd_Req.io_Length < ((LONG)OFFSET(NSDeviceQueryResult, SupportedCommands)) + sizeof(UWORD *))
268 iotd->iotd_Req.io_Error = IOERR_BADLENGTH;
270 else
272 struct NSDeviceQueryResult *d;
274 d = (struct NSDeviceQueryResult *)iotd->iotd_Req.io_Data;
276 d->DevQueryFormat = 0;
277 d->SizeAvailable = sizeof(struct NSDeviceQueryResult);
278 d->DeviceType = NSDEVTYPE_TRACKDISK;
279 d->DeviceSubType = 0;
280 d->SupportedCommands = (UWORD *)SupportedCommands;
282 iotd->iotd_Req.io_Actual = sizeof(struct NSDeviceQueryResult);
283 iotd->iotd_Req.io_Error = 0;
286 break;
287 #endif
289 case TD_CHANGENUM:
290 /* result: io_Actual = disk change counter */
292 case TD_CHANGESTATE:
293 /* result: io_Actual = disk presence indicator (0 = disk is in drive) */
295 case TD_PROTSTATUS:
296 /* result: io_Actual = disk protection status (0 = not protected) */
298 iotd->iotd_Req.io_Actual = 0;
299 iotd->iotd_Req.io_Error = 0;
300 break;
302 case ETD_UPDATE:
303 case CMD_UPDATE:
304 case ETD_CLEAR:
305 case CMD_CLEAR:
306 case TD_REMOVE:
307 case TD_ADDCHANGEINT:
308 case TD_REMCHANGEINT:
309 /* Ignore but don't fail */
310 iotd->iotd_Req.io_Error = 0;
311 break;
313 case TD_GETDRIVETYPE:
314 iotd->iotd_Req.io_Actual = DRIVE3_5;
315 iotd->iotd_Req.io_Error = 0;
316 break;
318 case TD_GETNUMTRACKS:
319 iotd->iotd_Req.io_Actual = NUM_TRACKS;
320 iotd->iotd_Req.io_Error = 0;
321 break;
323 case CMD_FLUSH:
325 struct IOExtTD *flushed_iotd;
326 struct unit *u =(struct unit *)iotd->iotd_Req.io_Unit;
327 Forbid();
328 while((flushed_iotd = (struct IOExtTD *)GetMsg(&u->port)))
330 flushed_iotd->iotd_Req.io_Error = IOERR_ABORTED;
331 ReplyMsg(&flushed_iotd->iotd_Req.io_Message);
333 Permit();
335 break;
337 case ETD_READ:
338 case CMD_READ:
339 case ETD_WRITE:
340 case CMD_WRITE:
341 case ETD_FORMAT:
342 case TD_FORMAT:
343 case ETD_RAWREAD:
344 case TD_RAWREAD:
345 case ETD_RAWWRITE:
346 case TD_RAWWRITE:
347 case ETD_SEEK:
348 case TD_SEEK:
349 case ETD_MOTOR:
350 case TD_MOTOR:
351 /* Not done quick */
352 iotd->iotd_Req.io_Flags &= ~IOF_QUICK;
354 /* Forward to unit thread */
355 PutMsg(&((struct unit *)iotd->iotd_Req.io_Unit)->port,
356 &iotd->iotd_Req.io_Message);
357 return;
359 default:
360 /* Not supported */
361 iotd->iotd_Req.io_Error = IOERR_NOCMD;
362 break;
364 } /* switch(iotd->iotd_Req.io_Command) */
366 /* WaitIO will look into this */
367 iotd->iotd_Req.io_Message.mn_Node.ln_Type = NT_MESSAGE;
369 /* Finish message */
370 if(!(iotd->iotd_Req.io_Flags&IOF_QUICK))
371 ReplyMsg(&iotd->iotd_Req.io_Message);
373 AROS_LIBFUNC_EXIT
376 /****************************************************************************************/
378 AROS_LH1(LONG, abortio,
379 AROS_LHA(struct IOExtTD *, iotd, A1),
380 struct ramdrivebase *, ramdrivebase, 6, Ramdrive)
382 AROS_LIBFUNC_INIT
384 return IOERR_NOCMD;
386 AROS_LIBFUNC_EXIT
389 /****************************************************************************************/
391 AROS_LH0(STRPTR, killrad0,
392 struct ramdrivebase *, ramdrivebase, 7, Ramdrive)
394 AROS_LIBFUNC_INIT
396 /* FIXME: KillRAD0 not implemented yet */
398 return 0;
400 AROS_LIBFUNC_EXIT
403 /****************************************************************************************/
405 AROS_LH1(STRPTR, killrad,
406 AROS_LHA(ULONG, unit, D0),
407 struct ramdrivebase *, ramdrivebase, 8, Ramdrive)
409 AROS_LIBFUNC_INIT
411 /* FIXME: KillRAD not implemented yet */
413 return 0;
415 AROS_LIBFUNC_EXIT
418 /****************************************************************************************/
420 #define ramdrivebase unit->ramdrivebase
422 /****************************************************************************************/
424 static LONG read(struct unit *unit, struct IOExtTD *iotd)
426 STRPTR buf;
427 ULONG offset, size;
429 D(bug("ramdrive_device/read: offset = %d size = %d\n", iotd->iotd_Req.io_Offset, iotd->iotd_Req.io_Length));
431 if(iotd->iotd_SecLabel)
433 D(bug("ramdrive_device/read: iotd->iotd_SecLabel is != NULL -> returning IOERR_NOCMD\n"));
434 return IOERR_NOCMD;
437 buf = iotd->iotd_Req.io_Data;
438 offset = iotd->iotd_Req.io_Offset;
439 size = iotd->iotd_Req.io_Length;
441 unit->headpos = offset;
443 if (offset + size > DISKSIZE)
445 D(bug("ramdrive_device/read: Seek to offset %d failed. Returning TDERR_SeekError\n", offset));
446 return TDERR_SeekError;
449 CopyMem(&unit->mem[offset], buf, size);
451 iotd->iotd_Req.io_Actual = size;
453 #if DEBUG
454 buf = iotd->iotd_Req.io_Data;
455 D(bug("ramdrive_device/read: returning 0. First 4 buffer bytes = [%c%c%c%c]\n", buf[0], buf[1], buf[2], buf[3]));
456 #endif
458 return 0;
461 /****************************************************************************************/
463 static LONG write(struct unit *unit, struct IOExtTD *iotd)
465 STRPTR buf;
466 ULONG offset, size;
468 if(iotd->iotd_SecLabel)
469 return IOERR_NOCMD;
471 buf = iotd->iotd_Req.io_Data;
472 offset = iotd->iotd_Req.io_Offset;
473 size = iotd->iotd_Req.io_Length;
475 unit->headpos = offset;
477 if (offset + size > DISKSIZE)
479 D(bug("ramdrive_device/write: Seek to offset %d failed. Returning TDERR_SeekError\n", offset));
480 return TDERR_SeekError;
483 iotd->iotd_Req.io_Actual = size;
485 CopyMem(buf, &unit->mem[offset], size);
487 return 0;
490 /****************************************************************************************/
492 AROS_UFH3(LONG, unitentry,
493 AROS_UFHA(STRPTR, argstr, A0),
494 AROS_UFHA(ULONG, arglen, D0),
495 AROS_UFHA(struct ExecBase *, SysBase, A6))
497 AROS_USERFUNC_INIT
499 struct Process *me;
500 LONG err = 0L;
501 struct IOExtTD *iotd;
502 struct unit *unit;
504 D(bug("ramdrive_device/unitentry: just started\n"));
506 me = (struct Process *)FindTask(NULL);
508 WaitPort(&me->pr_MsgPort);
509 unit = (struct unit *)GetMsg(&me->pr_MsgPort);
510 unit->port.mp_SigBit = AllocSignal(-1);
511 unit->port.mp_Flags = PA_SIGNAL;
513 D(bug("ramdrive_device/unitentry: Trying to allocate memory disk\n"));
515 unit->mem = AllocVec(DISKSIZE, MEMF_PUBLIC | MEMF_CLEAR);
516 if(!unit->mem)
518 D(bug("ramdrive_device/unitentry: Memory allocation failed :-( Replying startup msg.\n"));
520 Forbid();
521 ReplyMsg(&unit->msg);
522 return 0;
525 D(bug("ramdrive_device/unitentry: Memory allocation okay :-) Replying startup msg.\n"));
527 FormatOFS(unit->mem, unit->unitnum, unit);
529 ReplyMsg(&unit->msg);
531 D(bug("ramdrive_device/unitentry: Now entering main loop\n"));
533 for(;;)
535 while((iotd = (struct IOExtTD *)GetMsg(&unit->port)) != NULL)
537 if(&iotd->iotd_Req.io_Message == &unit->msg)
539 D(bug("ramdrive_device/unitentry: Recevied EXIT message.\n"));
541 FreeVec(unit->mem);
542 Forbid();
543 ReplyMsg(&unit->msg);
544 return 0;
547 switch(iotd->iotd_Req.io_Command)
549 case ETD_RAWREAD:
550 case TD_RAWREAD:
552 ** same as CMD_READ, but offset does not have to be multiple of
553 ** BLOCKSIZE
555 ** fall through
558 case ETD_READ:
559 case CMD_READ:
560 D(bug("ramdrive_device/unitentry: received CMD_READ.\n"));
561 err = read(unit, iotd);
562 break;
564 case ETD_RAWWRITE:
565 case TD_RAWWRITE:
567 ** same as CMD_WRITE, but offset does not have to be multiple of
568 ** BLOCKSIZE
570 ** fall through
573 case ETD_WRITE:
574 case CMD_WRITE:
575 case ETD_FORMAT:
576 case TD_FORMAT:
577 D(bug("ramdrive_device/unitentry: received %s\n", (iotd->iotd_Req.io_Command == CMD_WRITE) ? "CMD_WRITE" : "TD_FORMAT"));
578 err = write(unit, iotd);
579 break;
581 case ETD_MOTOR:
582 case TD_MOTOR:
584 ** DOS wants the previous state in io_Actual.
585 ** We return "!io_Actual"
588 iotd->iotd_Req.io_Actual = (iotd->iotd_Req.io_Actual == 1) ? 0 : 1;
589 err = 0;
590 break;
592 case ETD_SEEK:
593 case TD_SEEK:
594 unit->headpos = iotd->iotd_Req.io_Actual;
595 err = 0;
596 break;
598 } /* switch(iotd->iotd_Req.io_Command) */
600 iotd->iotd_Req.io_Error = err;
601 ReplyMsg(&iotd->iotd_Req.io_Message);
603 } /* while((iotd = (struct IOExtTD *)GetMsg(&unit->port)) != NULL) */
605 WaitPort(&unit->port);
607 } /* for(;;) */
609 AROS_USERFUNC_EXIT
612 /****************************************************************************************/
614 /* The following routines are based on TurboDevice by Thomas Dreibholz */
616 /****************************************************************************************/
618 static ULONG CalcRootBlock(void)
620 return NUM_CYL * NUM_HEADS * NUM_SECS / 2;
623 /****************************************************************************************/
625 static ULONG CalcBitMap(void)
627 return CalcRootBlock() + 1;
630 /****************************************************************************************/
632 VOID RootBlockCheckSum(UBYTE *buf)
634 LONG checksum, *long_ptr;
635 LONG i;
637 long_ptr = (ULONG *)buf;
638 checksum = 0;
640 for(i = 0; i < TD_SECTOR / 4; i++)
642 checksum += AROS_BE2LONG(long_ptr[i]);
644 long_ptr[5] = AROS_LONG2BE(-checksum);
647 /****************************************************************************************/
649 VOID CalcBitMapCheckSum(UBYTE *buf)
651 LONG checksum, i;
652 LONG *long_ptr = (LONG *)buf;
654 for(i = 1, checksum = 0; i < TD_SECTOR / 4; i++)
656 checksum += AROS_BE2LONG(long_ptr[i]);
658 long_ptr[0] = AROS_LONG2BE(-checksum);
661 /****************************************************************************************/
663 VOID InstallRootBlock(UBYTE *buf, STRPTR diskname, ULONG bitmap,
664 struct unit *unit)
666 struct DateStamp ds;
667 ULONG *long_ptr;
668 LONG i;
670 long_ptr = (ULONG *)buf;
671 long_ptr[0] = AROS_LONG2BE(2);
672 long_ptr[3] = AROS_LONG2BE(72);
673 long_ptr[78] = AROS_LONG2BE(-1);
674 long_ptr[79] = AROS_LONG2BE(bitmap);
675 long_ptr[127] = AROS_LONG2BE(1);
677 DateStamp(&ds);
679 long_ptr[121] = AROS_LONG2BE(ds.ds_Days);
680 long_ptr[122] = AROS_LONG2BE(ds.ds_Minute);
681 long_ptr[123] = AROS_LONG2BE(ds.ds_Tick);
683 long_ptr[105] = AROS_LONG2BE(ds.ds_Days);
684 long_ptr[106] = AROS_LONG2BE(ds.ds_Minute);
685 long_ptr[107] = AROS_LONG2BE(ds.ds_Tick);
687 buf[432] = (UBYTE)strlen(diskname);
689 for(i = 0; i < strlen(diskname); i++)
691 buf[433+i] = diskname[i];
694 RootBlockCheckSum(buf);
697 /****************************************************************************************/
699 static ULONG CalcBlocks(void)
701 return NUM_CYL * NUM_HEADS * NUM_SECS - 1;
704 /****************************************************************************************/
706 static void AllocBitMapBlock(LONG block, UBYTE *buf)
708 ULONG *long_ptr = (ULONG *)buf;
709 LONG longword, bit;
710 LONG old_long, new_long;
712 longword = (block - 2) / 32;
713 bit = block - 2 - longword * 32;
714 old_long = AROS_BE2LONG(long_ptr[longword + 1]);
715 new_long = old_long & (0xFFFFFFFF - (1L << bit));
717 long_ptr[longword + 1] = AROS_LONG2BE(new_long);
720 /****************************************************************************************/
722 static void FreeBitMapBlock(LONG block, UBYTE *buf)
724 ULONG *long_ptr = (ULONG *)buf;
725 LONG longword, bit;
726 LONG old_long, new_long;
728 longword = (block - 2) / 32;
729 bit = block - 2 - longword * 32;
730 old_long = AROS_BE2LONG(long_ptr[longword + 1]);
731 new_long = old_long | (1L << bit);
733 long_ptr[longword + 1] = AROS_LONG2BE(new_long);
736 /****************************************************************************************/
738 static void FormatOFS(UBYTE *mem, ULONG number, struct unit *unit)
740 ULONG a,b,c,d;
741 UBYTE *cmem;
742 UBYTE Name[6];
744 mem[0]='D';
745 mem[1]='O';
746 mem[2]='S';
747 mem[3]=0x00;
749 a = CalcRootBlock();
750 b = CalcBitMap();
752 cmem = mem + (a * TD_SECTOR);
753 strcpy(Name, "RAM_#");
754 Name[4] = '0' + number;
756 InstallRootBlock(cmem, Name, b, unit);
757 cmem = mem + (b * TD_SECTOR);
758 d = CalcBlocks();
759 for(c = 2; c <= d; c++)
761 FreeBitMapBlock(c, cmem);
764 AllocBitMapBlock(a, cmem);
765 AllocBitMapBlock(b, cmem);
767 CalcBitMapCheckSum(cmem);
770 /****************************************************************************************/
772 const char end = 0;
774 /****************************************************************************************/