Dereference symlinks when copying.
[tangerine.git] / workbench / c / Info.c
blob011eb3cd4d359f72e9ee0241d0b868bf5a2eca49
1 /*
2 Copyright © 1995-2007, The AROS Development Team. All rights reserved.
3 $Id$
5 Desc: Info Cli Command
6 Lang: English
7 */
9 /******************************************************************************
12 NAME
14 Info
16 SYNOPSIS
18 DISKS/S, VOLS=VOLUMES/S, ALL/S, BLOCKS/S, DEVICES/M
20 LOCATION
22 Sys:C
24 FUNCTION
26 Show information on file system devices and volumes. When given no
27 arguments, information on all devices and volumes found in the system
28 is displayed. If information is wanted only for some specific devices,
29 these names may be given as arguments.
31 INPUTS
33 DISKS -- show information on file system devices
34 VOLS -- show information on volumes
35 ALL -- show information on bad devices or volumes
36 BLOCKS -- show additional block size and usage information
37 DEVICES -- device names to show information about
39 RESULT
41 NOTES
43 EXAMPLE
45 Info
47 Unit Size Used Free Full Errs State Type Name
48 Harddisk: 964.1M 776.7M 187.4M 81% 0 read/write OFS AROS
49 RAM: 8.0M 7.1M 7.1M 12% 0 read/write OFS Ram Disk
51 BUGS
53 SEE ALSO
55 INTERNALS
57 The original source showed that AROS version of ReadArgs() handles
58 the /M switch with zero arguments differently from AmigaOS. While AROS
59 returns an array where the first pointer is NULL, AmigaOS just returns
60 NULL.
62 HISTORY
64 16.11.2000 SDuvan -- converted to AROS
65 23.12.2000 SDuvan -- changed semantics and updated
66 (now fully functional)
67 17.02.2005 Joe Fenton -- fixed 64bit calculation
69 Based on the original by:
70 © 1997-1998 by Stephan Rupprecht
71 All rights resevered
72 ******************************************************************************/
74 #define DEBUG 0
75 #include <aros/debug.h>
77 #include <dos/dos.h>
78 #include <dos/dosextens.h>
79 #include <dos/filehandler.h>
80 #include <exec/memory.h>
81 #include <libraries/locale.h>
83 #include <proto/dos.h>
84 #include <proto/exec.h>
85 #include <proto/utility.h>
86 #include <proto/locale.h>
87 #include <proto/alib.h>
89 #include <string.h>
91 #define ID_MAC_DISK2 (0x4d414300L) /* MAC\0 - xfs mac disk */
92 #define ID_MNX1_DISK (0x4d4e5801L) /* MNX\1 - xfs minix disk */
93 #define ID_MNX2_DISK (0x4d4e5802L) /* MNX\2 - xfs minix disk */
94 #define ID_QL5A_DISK (0x514c3541L) /* QL5A - xfs ql 720k / ed disk */
95 #define ID_QL5B_DISK (0x514c3542L) /* QL5B - xfs ql 1440k disk */
96 #define ID_ZXS0_DISK (0x5a585300L) /* Spectrum Disciple - xfs */
97 #define ID_ZXS1_DISK (0x5a585301L) /* Spectrum UniDos - xfs */
98 #define ID_ZXS2_DISK (0x5a585302L) /* Spectrum SamDos - xfs */
99 #define ID_ZXS4_DISK (0x5a585304L) /* Spectrum Opus 180k - xfs */
100 #define ID_ARME_DISK (0x41524d44L) /* Archimedes - xfs */
101 #define ID_ARMD_DISK (0x41524d43L) /* Archimedes - xfs */
102 #define ID_CPM_DISK (0x43505c4dL) /* CP/M - xfs */
103 #define ID_ZXS3_DISK (0x5a585303L) /* ZXS\3 - Plus3Dos xfs */
104 #define ID_1541_DISK (0x31353431L) /* 1541 - xfs */
105 #define ID_1581_DISK (0x31353831L) /* 1581 - xfs */
106 #define ID_MAC_DISK (0x4d534800L) /* MSH\0 - CrossDos MACDisk ?! */
107 #define ID_ACD0_DISK (0x41434400L) /* ACD\0 - AmiCDFS disk */
108 #define ID_CDFS_DISK (0x43444653L) /* CDFS - AmiCDFS disk */
109 #define ID_CACHECDFS_DISK (0x43443031L)
110 #define ID_ASIMCDFS_DISK (0x662dabacL)
111 #define ID_PFS2_DISK (0x50465302L)
112 #define ID_PFS2_SCSI_DISK (0x50445300L)
113 #define ID_PFS2_muFS_DISK (0x6d755046L)
114 #define ID_FLOPPY_PFS_DISK (0x50465300L)
115 #define ID_P2A0_DISK (0x50324130L)
116 #define ID_AFS0_DISK (0x41465300L) /* AFS\0 */
117 #define ID_muFS_DISK (0x6d754653L) /* muFS - Mulituserfsys */
118 #define ID_FAT12_DISK (0x46415400L) /* FAT12 */
119 #define ID_FAT16_DISK (0x46415401L) /* FAT16 */
120 #define ID_FAT32_DISK (0x46415402L) /* FAT32 */
123 /* Prototypes */
125 ULONG ComputeKBytes(ULONG a, ULONG b);
126 void FmtProcedure(struct Hook *hook, char a, struct Locale *locale);
127 ULONG ExtUDivMod32(ULONG a, ULONG b, ULONG *mod);
128 void doInfo();
131 const TEXT VersionStr[] = "$VER: Info 41.1 (16.11.2000)";
133 struct Catalog *cat;
134 struct Locale *loc = NULL;
135 ULONG MaxLen;
137 APTR Pool;
140 /* catalog string id:s */
141 enum
143 UNIT,
144 DEVTITLE,
145 DISKSTITLE,
146 DEVFMTSTR,
147 DATEFMTSTR,
148 READONLY,
149 READWRITE,
150 VALIDATING,
151 MOUNTEDSTR,
152 SMALLNUMFMT,
153 BIGNUMFMT,
154 VOLNAMEFMTSTR,
155 BLOCKSSTR
159 struct InfoDosNode
161 struct InfoDosNode *Next;
162 ULONG IsVolume;
163 ULONG DosType;
164 struct MsgPort *Task;
165 struct DateStamp VolumeDate;
166 TEXT Name[108];
169 struct InfoDosNode *head = NULL;
172 struct DiskTypeList
173 { ULONG id;
174 STRPTR str;
177 struct DiskTypeList dtl[] =
179 { ID_DOS_DISK, "OFS" },
180 { ID_FFS_DISK, "FFS" },
181 { ID_INTER_DOS_DISK, "OFS-INT" },
182 { ID_INTER_FFS_DISK, "FFS-INT" },
183 { ID_FASTDIR_DOS_DISK, "OFS-DC" },
184 { ID_FASTDIR_FFS_DISK, "FFS-DC" },
185 { ID_MSDOS_DISK, "MS-DOS" },
186 { ID_ACD0_DISK, "CDFS" },
187 { ID_CACHECDFS_DISK, "CDFS" },
188 { ID_ASIMCDFS_DISK, "CDFS" },
189 { ID_NOT_REALLY_DOS, "NO DOS" },
190 { ID_MAC_DISK2, "MAC" },
191 { ID_MNX1_DISK, "Minix" },
192 { ID_QL5A_DISK, "QL720k" },
193 { ID_QL5B_DISK, "QL1.4M" },
194 { ID_CPM_DISK, "CP/M" },
195 { ID_ZXS3_DISK, "+3Dos" },
196 { ID_ZXS0_DISK, "Disciple " },
197 { ID_ZXS1_DISK, "UniDos" },
198 { ID_ZXS2_DISK, "SamDos" },
199 { ID_ZXS4_DISK, "Opus" },
200 { ID_P2A0_DISK, "NETWORK" },
201 { ID_FAT12_DISK, "FAT12" },
202 { ID_FAT16_DISK, "FAT16" },
203 { ID_FAT32_DISK, "FAT32" },
204 { ID_FAT32_DISK, "FAT32" },
205 { ID_SFS_BE_DISK, "SFS" },
206 { ID_SFS_LE_DISK, "sfs" },
207 { 0L, 0L }
211 /****************************************************************************/
213 int UtilityBase_version = 0;
214 int LocaleBase_version = 0;
216 int __nocommandline;
218 int main(void)
220 static struct TagItem loctags[] = { { OC_Version, 1 },
221 { TAG_END , 0 } };
222 cat = OpenCatalogA(NULL, "info_com.catalog", loctags);
223 loc = OpenLocale(NULL);
225 D(bug("Calling doInfo()\n"));
227 doInfo();
229 CloseLocale(loc);
230 CloseCatalog(cat);
232 return RETURN_OK; /* TODO: Fix this */
236 CONST_STRPTR GetStrFromCat(ULONG id, CONST_STRPTR def)
238 if(cat != NULL)
240 def = GetCatalogStr(cat, id, def);
243 return def;
247 void LPrintf(ULONG id, CONST_STRPTR def, ...) __stackparm;
249 void LPrintf(ULONG id, CONST_STRPTR def, ...)
251 def = GetStrFromCat(id, def);
253 VPrintf(def, ((IPTR *)(&def))+1);
257 BOOL myMatchPatternNoCase(STRPTR *array, STRPTR str)
259 if(*array != NULL)
261 while(*array != NULL)
263 UBYTE matchstr[128];
264 UBYTE name[32];
265 UBYTE *p = *array++;
266 UBYTE len = strlen(p);
268 if(p[len - 1] != ':')
270 CopyMem(p, name, len);
271 name[len] = ':';
272 name[len + 1] = 0;
273 p = name;
276 if(ParsePatternNoCase(p, matchstr, sizeof(matchstr)) != -1)
278 if(MatchPatternNoCase(matchstr, str))
280 return TRUE;
285 return FALSE;
288 return TRUE;
292 BOOL ScanDosList(STRPTR *filter)
294 struct InfoDosNode *idn = 0L;
295 struct DosList *ndl, *dl;
296 STRPTR *strray = NULL, dummy = NULL;
297 BOOL err = FALSE;
299 D(bug("Entered ScanDosList()\n"));
301 if (filter == NULL) filter = &dummy;
303 if(*filter != NULL)
305 strray = AllocPooled(Pool, sizeof(STRPTR)*MAX_MULTIARGS);
307 if(strray != NULL)
309 STRPTR *p = filter;
310 LONG i = 0;
312 while(*p)
313 strray[i++] = *p++;
315 while(i < MAX_MULTIARGS)
316 strray[i++] = NULL;
318 else
319 return FALSE;
322 /* lock list of devices & vols */
323 dl = ndl = LockDosList(LDF_ASSIGNS | LDF_VOLUMES | LDF_DEVICES | LDF_READ);
325 if(strray != NULL)
327 STRPTR *p = strray;
329 while(*p)
330 p++;
332 while((ndl = NextDosEntry(ndl, LDF_ASSIGNS | LDF_VOLUMES | LDF_READ)) != NULL)
334 TEXT name[108];
335 STRPTR taskName = NULL; /* Initialized to avoid a warning */
337 __sprintf(name, "%s:", ndl->dol_Ext.dol_AROS.dol_DevName);
339 if ((ndl->dol_Type > DLT_VOLUME) || !(myMatchPatternNoCase(strray, name)))
341 continue;
344 switch (ndl->dol_Type)
346 case DLT_VOLUME:
347 taskName = ndl->dol_Ext.dol_AROS.dol_DevName; // ((struct Task *)ndl->dol_Task->mp_SigTask)->tc_Node.ln_Name;
349 D(bug("Found volume %s\n", taskName));
350 break;
352 case DLT_DIRECTORY:
354 struct AssignList *al = ndl->dol_misc.dol_assign.dol_List;
357 taskName = ndl->dol_Ext.dol_AROS.dol_DevName; // ((struct Task *)((struct FileLock *)BADDR(ndl->dol_Lock))->fl_Task->mp_SigTask)->tc_Node.ln_Name;
359 D(bug("Found directory %s\n", taskName));
361 while(al != NULL)
363 *p++ = ""; // TODO!!! ((struct Task *)((struct FileLock *)BADDR(al->al_Lock))->fl_Task->mp_SigTask)->tc_Node.ln_Name;
364 al = al->al_Next;
367 break;
370 *p++ = taskName;
373 else
374 strray = filter;
376 ndl = dl;
378 while((ndl = NextDosEntry(ndl, LDF_VOLUMES | LDF_DEVICES | LDF_READ)) != NULL)
380 UBYTE len = 0;
381 UBYTE type = ndl->dol_Type;
382 UBYTE name[108];
384 // if(((type == DLT_DEVICE))) // && (!ndl->dol_Task) TODO Check this!
385 // continue;
387 __sprintf(name, "%s:", ndl->dol_Ext.dol_AROS.dol_DevName);
389 D(bug("Found name %s\n", ndl->dol_Ext.dol_AROS.dol_DevName));
391 if((type == DLT_DEVICE) && (myMatchPatternNoCase(strray, name) == FALSE))
393 int i;
395 D(bug("Failure! -- name = %s, strray = %p\n", name, (void *)strray));
397 for (i = 0; strray[i] != NULL; i++)
399 D(bug("Strray %i = %s\n", i, strray[i]));
402 continue;
405 idn = (struct InfoDosNode *)AllocPooled(Pool, sizeof(struct InfoDosNode));
407 if(idn == NULL)
409 err = TRUE;
410 break;
413 // idn->Task = (struct MsgPort *)ndl->dol_Task;
414 idn->IsVolume = type == DLT_VOLUME;
416 while((idn->Name[len] = name[len]))
417 len++;
419 if(type == DLT_VOLUME)
421 idn->VolumeDate = ((struct DeviceList *)ndl)->dl_VolumeDate;
422 idn->Name[len - 1] = '\0'; /* remove ':' */
424 else
426 BPTR ptr = BADDR(ndl->dol_misc.dol_handler.dol_Startup);
427 struct FileSysStartupMsg *fssm = (struct FileSysStartupMsg *)ptr;
429 idn->DosType = ID_DOS_DISK;
431 // DLT_DEVICE
432 if (len > MaxLen)
433 MaxLen = len;
435 if (fssm)
437 struct DosEnvec *de;
438 de = (struct DosEnvec *)BADDR(fssm->fssm_Environ);
440 if (de && (de->de_TableSize & 0xffffff00) == 0)
441 if (de->de_DosType)
442 idn->DosType = de->de_DosType;
446 /* kinda insert sort */
448 struct InfoDosNode *work = head;
449 struct InfoDosNode *prev = NULL;
451 while((work != NULL) && (Stricmp(idn->Name, work->Name) > 0))
453 prev = work;
454 work = work->Next;
457 if(prev != NULL)
458 prev->Next = idn;
459 else
460 head = idn;
462 idn->Next = work;
466 /* unlock list of devices and volumes */
467 UnLockDosList(LDF_ASSIGNS | LDF_VOLUMES | LDF_DEVICES | LDF_READ);
469 // strray freed at DeletePool
471 return !err;
475 void PrintNum(ULONG num)
477 /* MBytes ? */
478 if(num > 1023)
480 ULONG x, xx;
481 char fmt = 'M';
483 /* GBytes ? */
484 if(num > 0xfffff)
486 num >>= 10;
487 fmt = 'G';
490 num = ExtUDivMod32(UMult32(num, 100) >> 10, 100, &x);
492 /* round */
493 x = ExtUDivMod32(x, 10, &xx);
495 if(xx > 4)
497 if(++x > 9)
499 x = 0;
500 num++;
504 LPrintf(BIGNUMFMT, "%5ld.%ld%lc", num, x, fmt);
506 else
508 LPrintf(SMALLNUMFMT, "%7ldK", num);
513 STRPTR GetFSysStr(ULONG DiskType)
515 struct DiskTypeList *dtlptr = dtl;
517 STRPTR ptr = NULL;
519 do {
520 if(dtlptr->id == DiskType)
522 ptr = dtlptr->str;
523 break;
525 } while(*((ULONG *)dtlptr++));
527 if(ptr == NULL)
529 static TEXT buffer[5];
531 ptr = (STRPTR)buffer;
532 *((ULONG *)ptr) = DiskType;
534 if(ptr[3] < ' ')
535 ptr[3] += '0';
537 ptr[4] = '\0';
540 return ptr;
544 enum
546 ARG_DISKS,
547 ARG_VOLS,
548 ARG_ALL,
549 ARG_BLOCKS,
550 ARG_DEVS,
551 NOOFARGS
555 void doInfo()
557 struct RDArgs *rdargs;
558 struct Process *proc;
559 struct Window *win;
560 struct InfoDosNode *idn;
562 static struct InfoData id;
564 IPTR args[] = { (IPTR)FALSE,
565 (IPTR)FALSE,
566 (IPTR)FALSE,
567 (IPTR)FALSE,
568 (IPTR)NULL };
570 CONST_STRPTR unit = GetStrFromCat(UNIT, "Unit");
572 Pool = CreatePool(MEMF_ANY, 1024, 1024);
574 if(Pool == NULL)
576 PrintFault(ERROR_NO_FREE_STORE, NULL);
577 return; /* ??? */
580 D(bug("Calling ReadArgs()\n"));
582 /* read arguments */
583 rdargs = ReadArgs("DISKS/S,VOLS=VOLUMES/S,ALL/S,BLOCKS/S,DEVICES/M",
584 args, NULL);
586 if(rdargs != NULL)
588 BOOL disks = (BOOL)args[ARG_DISKS];
589 BOOL vols = (BOOL)args[ARG_VOLS];
590 BOOL showall = (BOOL)args[ARG_ALL];
591 BOOL blocks = (BOOL)args[ARG_BLOCKS];
592 STRPTR *devs = (STRPTR *)args[ARG_DEVS];
594 if (devs && (*devs == NULL)) devs = NULL;
596 /* If nothing is specified, show everything we got */
597 if(devs == NULL && !disks && !vols)
599 vols = TRUE;
600 disks = TRUE;
603 /* check pattern strings */
605 if(devs != NULL)
607 STRPTR *p = devs;
609 while(*p != NULL)
611 TEXT matchstr[128];
613 if(ParsePatternNoCase(*p, matchstr, sizeof(matchstr)) == -1)
615 PrintFault(IoErr(), *p);
616 goto end;
619 p++;
623 /* avoid requesters */
624 proc = (struct Process *)FindTask(NULL);
625 win = (struct Window *)proc->pr_WindowPtr;
626 proc->pr_WindowPtr = (struct Window *)~0;
628 MaxLen = strlen(unit);
630 D(bug("Calling ScanDosList()\n"));
632 /* scan doslist */
633 if(ScanDosList(devs))
635 CONST_STRPTR dstate[3] = { GetStrFromCat(READONLY, "read only"),
636 GetStrFromCat(VALIDATING, "validating"),
637 GetStrFromCat(READWRITE, "read/write") };
638 STRPTR datetimeFmt = NULL;
639 BOOL first = TRUE;
640 TEXT nfmtstr[16];
641 TEXT buf[64];
643 D(bug("Printing stuff\n"));
645 /* get datetimefmt string */
646 if(loc && (GetVar("info_datetime", buf, sizeof(buf), 0L) > 0L))
648 datetimeFmt = buf;
651 /* calc format string for 'Unit' */
652 __sprintf(nfmtstr, "%%-%lds", MaxLen);
654 /* show device infomation */
655 if(devs != NULL || disks || !vols)
657 for(idn = head; idn; idn = idn->Next)
659 BPTR lock;
660 STRPTR name = idn->Name;
662 D(bug("Got name = %s\n", name));
664 if(!idn->IsVolume && IsFileSystem(name))
666 /* if first device to print, print title */
667 if(first || blocks)
669 if(!first)
670 Printf("\n");
672 D(bug("Printing device\n"));
674 LPrintf(~0, nfmtstr, unit);
675 LPrintf(DEVTITLE, " Size Used Free Full Errs State Type Name\n");
677 first = FALSE;
680 D(bug("Locking \"%s\"\n", name));
681 lock = Lock(name, SHARED_LOCK);
683 D(bug("Lock = %p\n", lock));
685 if(lock != NULL)
687 D(bug("Got lock on %s\n", name));
689 if(Info(lock, &id) == DOSTRUE)
691 ULONG x, y;
693 D(bug("Got info on %s\n", name));
695 LPrintf(~0, nfmtstr, name);
697 x = ComputeKBytes(id.id_NumBlocks, id.id_BytesPerBlock);
698 y = ComputeKBytes(id.id_NumBlocksUsed, id.id_BytesPerBlock);
700 PrintNum(x);
701 PrintNum(y);
702 PrintNum(x - y);
704 D(bug("Calling NameFromLock()\n"));
706 if(NameFromLock(lock, name, 108L))
708 LONG len = strlen(name) - 1;
710 if(name[len] == ':')
712 name[len] = '\0';
716 if(x > 0xfffff)
718 x >>= 10;
719 y >>= 10;
722 if(x)
724 x = ExtUDivMod32(UDivMod32(UMult32(y, 1000), x), 10, &y);
726 if(y > 4)
727 x++;
729 else
730 x = 0;
732 // y = ((struct DeviceList *)BADDR(id.id_VolumeNode))->dl_DiskType;
734 // if(!y)
735 y = id.id_DiskType;
737 if((idn->DosType & ID_DOS_DISK) != ID_DOS_DISK)
738 y = idn->DosType;
740 LPrintf(DEVFMTSTR, "%4ld%% %4ld %-11s%-8s%s\n",
741 x, id.id_NumSoftErrors,
742 ((id.id_DiskState >= ID_WRITE_PROTECTED) && (id.id_DiskState <= ID_VALIDATED)) ?
743 dstate[id.id_DiskState - ID_WRITE_PROTECTED] : (STRPTR)"", GetFSysStr(y), name);
745 if(blocks)
747 LPrintf(BLOCKSSTR,
748 "\nTotal blocks: %-10ld Blocks used: %ld\n"
749 " Blocks free: %-10ld Blocksize: %ld\n",
750 id.id_NumBlocks, id.id_NumBlocksUsed,
751 id.id_NumBlocks-id.id_NumBlocksUsed, id.id_BytesPerBlock );
754 else
755 D(bug("Info failure\n"));
757 UnLock(lock);
761 LONG err = IoErr();
763 if((err != 0) && showall)
765 LPrintf(~0, nfmtstr, name);
766 PrintFault(err, NULL);
773 /* show volumes */
774 if(vols || (!devs && !disks))
776 if(!first)
777 PutStr("\n");
779 LPrintf(DISKSTITLE, "Volumes\n");
781 for(MaxLen = 15, idn = head; idn; idn = idn->Next)
783 if(idn->IsVolume)
785 LONG len = strlen(idn->Name);
787 if(len > MaxLen)
788 MaxLen = len;
792 __sprintf(nfmtstr, "%%-%lds%%-10s", MaxLen+1);
794 for(idn = head; idn; idn = idn->Next)
796 if(idn->IsVolume)
798 LPrintf(VOLNAMEFMTSTR, nfmtstr, idn->Name,
799 GetStrFromCat(MOUNTEDSTR, "[Mounted]"));
800 // idn->Task ? GetStrFromCat(MOUNTEDSTR, "[Mounted]") : ""); TODO
802 if(datetimeFmt)
804 UBYTE datestr[128];
805 static struct Hook hook;
807 memset(&hook, 0, sizeof(struct Hook));
809 hook.h_SubEntry = (HOOKFUNC)FmtProcedure;
810 hook.h_Data = datestr;
812 FormatDate(loc, datetimeFmt, &idn->VolumeDate, &hook);
814 PutStr(datestr);
816 else
818 TEXT StrDay[LEN_DATSTRING];
819 TEXT StrDate[LEN_DATSTRING];
820 TEXT StrTime[LEN_DATSTRING];
822 struct DateTime dt;
824 dt.dat_Flags = DTF_SUBST;
825 dt.dat_Format = FORMAT_DOS;
826 dt.dat_StrDay = StrDay;
827 dt.dat_StrDate = StrDate;
828 dt.dat_StrTime = StrTime;
829 dt.dat_Stamp = idn->VolumeDate;
831 if(DateToStr(&dt))
833 if(Strnicmp(StrDate, StrDay, strlen(StrDay)) == 0)
835 dt.dat_Flags = 0L;
836 DateToStr(&dt);
839 LPrintf(DATEFMTSTR, "created %.3s, %-10s %s",
840 StrDay, StrDate, StrTime);
844 PutStr("\n");
849 else
851 PrintFault( ERROR_NO_FREE_STORE, NULL);
854 /* reset window pointer of our process */
855 proc->pr_WindowPtr = win;
857 /* free args */
858 FreeArgs(rdargs);
862 end: /* free allocated memory */
864 DeletePool(Pool);
868 ULONG ComputeKBytes(ULONG a, ULONG b)
870 // UQUAD result = UMult64(a, b);
872 UQUAD result = (UQUAD)a * b;
874 return (ULONG)(result >> 10);
878 void FmtProcedure(struct Hook *hook, char a, struct Locale *locale)
880 *((STRPTR)hook->h_Data) = a;
881 hook->h_Data = (STRPTR) hook->h_Data + 1;
885 ULONG ExtUDivMod32(ULONG a, ULONG b, ULONG *mod)
887 *mod = a % b;
889 return a/b;