SIBO 'C' Software Development Kit PLIB REFERENCE Version 2.30 March 1, 1999 (C) Copyright Psion PLC 1990-98 All rights reserved. This manual and the programs referred to herein are copyrighted works of Psion PLC, London, England. Reproduction in whole or in part, including utilization in machines capable of reproduction or retrieval, without express written permission of Psion PLC, is prohibited. Reverse engineering is also prohibited. The information in this document is subject to change without notice. Psion and the Psion logo are registered trademarks, and Psion, Psion MC, Psion HC, Psion Series 3, Psion Series 3a, Psion Series 3c, Psion Siena and Psion Workabout are trademarks of Psion PLC. TopSpeed is a registered trademark of Clarion Software Corporation. Intel 8086 and 80286 are registered trademarks of Intel Corporation. IBM, IBM XT and IBM AT are registered trademarks of International Business Machines Corp. Microsoft and MS-DOS are registered trademarks of Microsoft Corporation. Apple and Macintosh are registered trademarks of Apple Computer Inc. VAX and VMS are registered trademarks of Digital Equipment Corporation. Brief is a registered trademark of Underware Inc. Psion PLC acknowledges that some other names referred to are registered trademarks. Contents 1 Introduction...............scccscscssscrsscrsceseserseesessessseesssesseeeseesseesseesseesseesseessesscsesceessesscesscessessaeeseees 1-1 PLIB, SIBO and: EPO eihcne sca .ansaunedehsanuedons aoaddthecaaltineehe 1-1 The SIBO architecture::.:2.: sts2iess a Aleit: eters ede keene kt 1-1 The: BPOG ‘operating: systerin.s..cescr)iccstasstisastesicctapatasscanddsisteanassicuss gauss agagenstiauaens 1-2 The EPOC programming CnvirOnMent .......... ce eeeesceceseeceseeeeseeesseecsaeecseecseecesaeeesaeeesaeers 1-2 Small: programming Model s,s. i255: 3eshesscstiessdacieas asides, dapbestoussbers esdisi aspen Aeedios Ad 1-2 Hardware protect oni.ssii.sis: cessing ies Pexvs Poss tavshea avis est Seevased stung sas ideosdea deehe reel devsceupabesee dey 1-3 More about memory moving and the 8086 segment registers...........eeeeeeeeeeneeeeeees 1-3 The Clarion TopSpeed C compiler ...........cecceesccesseeceseeesseecesaeeesseecsaeecseeesneesssaeeesaes 1-4 SYSLEM:SELVICES ssshe18 ccd Soap esidephie Asphials todos etiesieioindarhadietiadaehatddeisdaehis: 1-4 PLIB Header! files s2i sss es covstaess oa ius foistness Oasis cote teossids Ban peetaess Ts hia Aateestos Rana ave 1-4 PSU Biz: scasss cic vetoes csuczeiovstas vet aueahvovasiassc ssaginneeanaavee cane sisaeanaanas shagsuseeanaceageasasuigestases’ 1-5 Callins conventions scs.s:ic15: Sstiothascii eet iotiows Sibi sthctosss oa ieiaciossesehilaedieseniieres 1-6 Small proprawms sss vs.ssss lapivossesdbeed seabed sesh dacs sesesees ovsa sees sesv sues sustoese deavoassouatbase nesweeee AS 1-7 The PLIB C startup modules 20.0.0... ceececesecsseeceseeeesaeecseecseeceseeessseessaeersneeesseeeesaes 1-7 Related -teference: marital §:1s..s:::sccsndasstesssccassatlavetesbeceassnndauteestea ansendaicasteieatendaneestea isin: 1-8 TopSpeed C library reference ........ eee eeeeeeceesseecsseecsseecsseecesceeesaeecsacecsseessseeeesaeeesaeers 1-8 Window server reference :.:/:s.0 hahaha siderite ii 1-9 W/O devices teferen Ce: 25.5 cis: eotstevss st: Find eotstavss batyek ets thors baths beiateese ds hibsaiteesiatsiredetes 1-9 EPOC O/S System Services reference Manual...........eeeeeseeesseeceseeeesteeeseessneeeeteeeesaes 1-9 Object dynamiclibraries y..:2.:) Asch elcid es aed ee eg eed a Love 1-10 2 Characters, Strings and Buffers ................ccsssccssssssccsscsscssscscesssscesssscssesssscssessssessesssssessssees 2-1 General string and buffer fUNCtIONS 00.0.0... ee eee eeseeceneeeeteecesaeeesaeecsaeecsseeseeecesaeeesaeeesaeers 2-1 Copy memory to Memory (P_DCPY)..........eeeceeeesseecesseeeceeseeesessseeeseseeeeseseeesesseeeees 2-1 Return string length (p_slen)............ccscccecsssscceeeecceeeesneeceeseaeeeceseaeeeeeeeaeeeeesneeeeeeneeeees 2-1 Copy a: Stlin' © (Pe2SCpy,) cu. sess scagsset tea pbeouetey oes seca detboeg sdauevey bet hte ocaneyh pact ide poteaenbepeteeyy 2-1 Copy multiple strings (P_SCPyM) ...........:ceseccseseecsseecsseeesseecesseeesaeecsaeecseeesseeeesaeessaeees 2-2 Concatenate two Strings (P_SCat) .......eeeeeeeeesneessneeceseeesseeceseeeesseecseecseeceseeeesaeessaeers 2-2 Concatenate many strings (P_SCAtM) ......... sees eeeseecsseeesseeeeseeeesneecsaeecsseeesseeeesaeersaeers 2-2 Replicate a buffer (p_brep) .0..........ccceeeeecceeesneceeeseneeeessneeeeeeaeeeeesnaeeeceeneeeeessneeeeeeeeeeess 2-2 Replicate a string (p2srep) se.vsieccaece.ciesyseeecaigtesecovsees Digi desovdevsgapdeb begaeoeebidepeel beanies 2-3 Swap two buffers (p_Ds wap) ..........s:ccsssccssecsscecsseeceseeeesaeecsseecseeesseecssaeeesaeesseesseeeses 2-3 Fill a buffer with a value (p_Dfil) 0.0.00... eeeccccceeencceeesneeeeeeeeeeeceseaeeeeesneeeeeesneeeessnaeeeess 2-3 Ali gtisbutter: (psjtob) ssn: seccscocscevssce cou pbeesetey ote cue suet btes adthaveg beep sdhy oannest poekt ede podeaeeopanthees 2-3 Generate the CRC number (p_crc)..........ccsssccccessnceecesnceeeeeeeeeeessneeeeseneeeeessneeeeseeeeeess 2-4 Character classification and COMVELSION...........::cescccesceecesseecssceceseeeesaeecsaeecsaeecsseeeesaeeeeaeers 2-4 Test for upper case character (P_iSUPPeT)...........ceeeeseeesseceseeeesseecseecsneeceseeeesaeessaeers 2-5 Test for lower case character (p_1slOWeL) ............::ccceseeeceeeeeneeeeeeeeeeceeneeeecesteeeeesneeeess 2-5 Test for alphabetic character (p_isalpha) ...........ceseeseeeeseeceseceseeceeeceseeceseeeesaeeesaeers 2-6 Test for numeric digit (p_isdigit)....... eels eesecsseecsseeesseeceseeeesseecsseecseeseseeeesaeessaeers 2-6 Test for alphanumeric character (p_isalnUmM).............c:eeeeeeeseeeeseecsneecsseeeeseeeesaeessneers 2-6 Test for hexadecimal digit (p_isxdiQit) 0.0.0... eee eeseeeseeceseeceseeeseecseessseessseeeesaeessneers 2-6 Test for whitespace character (Pp_1SSPace) .........eeseeeseeeeseecesseeeseecsaceceeeseseeeesaeessaeers 2-6 Test for control character (p_iscntr]) ..........c:ccceeesceceeseceeeeesneeeeeesaceeeeeneeeeeneneeeeeeneeeees 2-6 Test for punctuation character (p_iSpUNCt)..........eseeeseeesseeceseeeseecseecseeeeseeeesaeersaeers 2-6 Test for printable graphic character (p_isgraph) .............s:cessseesseecsseeceseeceseeeesaeereaeers 2-6 Test for printable character (P_iSPrint) .0....... ee eeseeeseceseeceseeeesaeecseecseeesseeeesaeessaeers 2-6 Skip whitespace characters (p_Skipwh) ......cccccccccsscecsssecsscecesseeesseecsneessseeceseeeesaeeesneess 2-7 Skip non-whitespace characters (p_Skipch)............sscsessecsseecsseeeeseeceseeeesseesseessneeeees 2-7 Fold a character (p_tofld) ..0.......:.:cceesscceesescceeeeeneeeceeneeeeeseaeeeceseneeeesenneeeeseneeeesseneeeess 2-7 Copy string with fold (p_Scpyf) ........eeeeeseessneeesseecsseecsseecesaeeesseecsaeecseeceseeeesaeeesaeers 2-7 Fold ‘string (p USCOnf): 2. ssc. tt en oan eiet dees bithak eid cecal acckaeeeidide cucaeshoweeitibede tact sae 2-7 Convert character to upper Case (P_tOUPPeP)......... eee ee eeeeeeceeseeeeeeeseeeeeeseeeeeenaeeeees 2-7 Convert character to lower case (p_tolOWe?) ...........ceeeeesseeeeceeeeeesenneeeeceeeeeessneeeeeeeeeees 2-8 PLIB REFERENCE Capitalise string (P_SCap) .......eeseescecssceceseeeesseecsseecescecsseecesaecesseecsseecseessseeeesaeeesaeers 2-8 SPINS COMMParISON Ger scse2.cesen gst ook, hot Shea etek Uoeist one sig oa Yaeek shes ited Taest oae Oe tant canes ak 2-8 Compare two buffers (p_DCMp)..........cscceseecssseessseecsneeceseecesaeeesseecsaeecseessseeeesaeeesaeers 2-8 Compare two Strings (P_SCMP) ........:eeseceeseecesseessseeceseecsseecesaeeesaeecsaeecseeceseeeesaeeeeaeers 2-9 Case independent buffer compare (p_DCMP Ii) ...........eeeeeeeeceesecesseeceneeeseeseseeeesaeersaeers 2-9 Case independent string compare (P_SCMP1)............esecesseccesseeeseecsneecsteeeeseeeesaeeesaeers 2-9 String Searchin .sisi.sai sek dise tetera nl soared ates wei nel anavainbi autagineta. 2-10 Locate byte in buffer (pP_DIOC) ...... eee eeeeeeesneeceneecsseeceseeceseecesaeecsaeecseecesaeeesaeessaeers 2-10 Locate character in string (P_SIOC).........eeecceesseeeseecsseeeeseecesseeesaeecsaeecseeesseeeesaeeesaeers 2-10 Case independent locate character in buffer (p_bIOCi) ....... eee eee eeeeeeeseeeeseeeeeeeeneers 2-10 Case independent locate character in string (P_S]OC1).........eeeeeeseeeseeeseeeeseeeeeeeeeeneers 2-10 Locate last matching character in a string (P_SIOCT) ............::cceeeeseeeeeeeeeeeeeneeeeeseeeeees 2-11 Locate last matching folded character in a string (p_SIOCT1) ............cceeesceeeeeeeeeeeteeeees 2-11 Locate sub-buffer in buffer (p_bsub) ...............cceeecsceeeesceeeeeeeeeeeesneeeeeeeaeeecseneeeesseeeeees 2-11 Locate substring in string (P_SSUD) ...........ccesccecceesenseceeeneceeeeeneeeceseneeecseeeeeeseaseeeesaees 2-11 Case independent locate sub-buffer in buffer (p_bsubi) ........... eee eeeeeeseeeeneeeeeeeeeeeee 2-11 Case independent locate substring in string (P_SSUD1) 00.0... eeeeeeseeeeeseeeeeeeeeeeteneers 2-12 Pattern match a buffer (p_bmatch).............cccccceeesseceeeenceeceeeneeecesneeeeeeeaeeecseneeeeeseeeeess 2-12 Pattern match a buffer, case independent (p_bmatch1) ..............c:::ceesesseeeeeseeeeeeeneeeees 2-12 Pattern match a string (p_smmatch) ............ceeccceeeeeeceeeeeeceeeenseeceeeceeeeseaeeeceenneeeeseeeeess 2-12 Pattern match a string, case independent (p_smatchi)..............:cccccesesseeeeeseeeeeeeteeeees 2-12 3 ATTAYS ANd QUEUES ............sccscesscssccseccsscscecssccseessscseesssscceessscesesssseseesesssccsesssscesesssscseesssccseeeees 3-1 ATTAYSssiscisabsssorehhsiaidaaspsshasshessaciassostasibespigahaeakdsids sandals coatgsiar aids Mooedg secede Gecatdsieuuls hens 3-1 Binary search an array of records (p_Dsrch) .........eesceeseeeeseessseeceseeeeseeeesaeerseeesseeeesaee 3-1 Sort anarray of records: (PAqsOrt) si Jccbsss-esesests apie Aaadess Lap dedi Aesvdeadseedeh Aesvteas eeeaieh ees 3-2 Doubly linked. ques’ #0. :s2s205cossvinsboedyosi ots keueubs these Sacks sodsthess Mh bseetstievsus Raeebeiess ts Beas 3-3 Add entry to queue (p_CNqQue)........ eee eesecesecsseeesseeeesseecsseecsscecsseeeesaeecsaeecsaeeeseeesaes 3-4 Remove entry from queue (p_Geque).......... eee eeeeesssecesseeceseessseeceseeeesseecsaeecseeesseeeesaes 3-5 Delta: QUeCUGS: 53 is52css sor asians Aistdiee pcascass cus vaess Ase cass osst beds sua stissoustbans det duss sent dussseasduascunaiteaays 3-5 Add entry to delta queule (p_emqued) 0.0... eeeeeesseceeseeceneesseeceseeeesseessaeesseeesseeeesaes 3-5 Remove entry from delta queue (p_dequed) ........ eee eeseeeseecesceceseeeeseeecseecseeeeneeensaes 3-6 4 Integer Conversion and Rectangle Functions ................ssccccssscssssssccsssssecssscseecssscseessscesessssees 4-1 CORVETTE ANTE SETS LOTER Eases co, sede ecg shee Mnsedseseesseh sWastes sees ansheateeteesdupbuehest vant sees eebeats east ex 4-1 Convert an INT to decimal buffer (p_i1tob) ..............ceeeesceceescceecesneeeeeeeeeecesneeeeeeeneeeees 4-1 Convert a LONG to decimal buffer (p_Itob) 00.0.0... eeeecceeeescceeeeneeeeeeeeeeeeeeneeeeeseeeeees 4-2 Convert a UINT to buffer any radix (p_gtOb) 0... eee eesecsseeceseeesseeessseecseeeeseeeesaes 4-2 Convert a ULONG to buffer any radix (p_gltob) ...... eee eeeeeceseeeeeeeeseeseeeseseeeesaes 4-2 Convert multiple arguments to buffer (P_atob)......... eee eeeeeeseeceseeeeeeeeeseeceeeeeteeeesaes 4-2 Convert multiple arguments to string (P_AtOS) ........eeeeeeseeesseeceseeeeeeeeesseessaeeseteeeesaes 4-4 Converting text to INtEQeLS ..... eee eee eeeeeceseeeesseecseecsseecsseecesaeeesaeecsaeecseeseneeeesaeessaeessneeeses 4-4 Convert a signed decimal string to a WORD (p_StOi).........eeeeeeeeeeeneeeeneeseneeeeneeeeseee 4-4 Convert a signed decimal string to a LONG (p_stol)....... cee eeeeeseeeeeseeeeneeesneeeeneeeesaes 4-4 Convert an unsigned number in any radix toa UWORD (p_st0g)..........eceeeeeeeeeeeeeee 4-5 Convert an unsigned number in any radix to a ULONG (p_stogl)...... eee eeeeeeeeeeeee 4-5 Convert a string to arguments (P_StOd) ........eceeseeseeeesseeceneecseeceseeessaeecsaeersaeessneeeesaes 4-5 Rectangle function ses esses tah ccccetaekcsceacieheseva gee seeid tebe cues deea uneesda deus cusadynetavadyen Gevsbyedeeda duke delts 4-7 Offset:a rectangle: QP sOfirec) + a.25sss2ssseztdesetas ess canes iovadiass Sasagstaaeanasts cabigieszeaneaes Sausegansess 4-7 Inset a rectangle (po Insrec): oc. ied lakh eG itiedegiciel nee Pandas 4-8 Union of two rectangles (P_UMILCC)........ ee eeeeeeseeeeseeesseecsseeeseeceseeeesaeessaeersaeessseeeesaes 4-8 Intersection of two rectangles (P_iMtreC) ....... eee eeeeeeesseeesneeesseeceseeeesaeecsaeessaeessseeeesaes 4-8 Test if a point is inside a rectangle (P_PINTEC)........ eee eeseeeeseeeeseeeeseeeesaeerseeeeteeeesaee 4-9 Test if a rectangle is empty (P_CMPTe€C) ....... eee eeeeeeeseeeeseeceseeceeeeeesaeetsaeerseeesteeeesaes 4-9 Convert to an absolute rectangle (p_aDsrec) ........eeseesceceseessseeceseeeeseeeesseerseeesseeeesaes 4-9 CONTENTS 5 FlOating: PONE .cc.ccsssacecessssvecensoovessassasennasoeceneasssdenendeosunansessusacdsodesasdssiesesdeosanassossaiosdensesoasensenee 5-1 PlOatinie! POM tC oso, seses ceysditseantaced sensed stutievbcepseea sunpeecpoaete eee staeaate beste ceevatetpaeeth epee tease 5-1 The: 8087 emulators: ais nice cesta hese ee eA SAG a A AE ay 5-1 Avoiding the 8087 emulator .......... cee eeeceesseccssneecseecseecsseecesaeeesaeecsaeecseecsseesesaeessaeers 5-2 MAaCTOS s:Aieitsevtesis ties ioti nse Re ad ee ed ee 5-3 Converting doubles to and from text... eee eee esseeseeceeeeeeceeeseesseesseesseeseesseeseeeags 5-3 Double. to.string: (pu dtob)sz.e.ss:.c.c cscs sesteieeeieeeeeib capt tedeydevbede poe besidebbezeneesbeeaeaes 5-3 String to double (p_StOd) .........eeccceeesscceessceeeesneeeeeseeeeeceseeeeeeeeeeeceseaeeeeeenaeeecseneeeeeeees 5-4 Get number representation preferences (p_getctd) ........eeeeeseesseeceseeceneeeeseeeesneessneers 5-5 LON INtE Ber MIN CHUONS 2. sede leseleecsecusees secesbensctesesesesdeard oars sausvens sadesarvsadebars seudbanveatabereyegs 5-6 Long random number (p_rand]) .00......ececeeesccceeeseceecesnceeeeeenceeeeseaeecesenneeeensneeeeesneeeees 5-6 SCIEN AG TUNCHONS 55. 122. fs ses ook saet ee eaaes cet bea tese chit castes bce daunt ots duerees Sauet one beeeecaesineote 5-6 Sime (PSI) x. scvvdsscesveisdaceee shaves pacdaceeedeceevcedesensced aves vesdaveancauaessscedacvat cesdevacccdesvarceaeates 5-6 COSING: (PD =COS) os csce ced ce. g tis reneececetsteceenscecededs bedivesetadegetasedesndace ceded tedevsdnsadeisteseteatcatates 5-7 ‘Lancent, (potan)nivsicetst ste nvtisrisr tert rata eahiaeen sens pesiahesyen ened he bheveddu ayo ouss 5-7 ATCSINE! (ASIN) 3d seh eskstue ooh aiden aye deh GUM eek Aiea aoe Au deh GoM otek de au obedient Hee 5-7 AT CEOS. (Pp: 2aCOS) cs tscaecesseestceaa ces eenvavat cana cevvensvaeetcaancusudsanecst casereavdvaeas censsenvdtantabecetes 5-7 ALCLANPSNE:(PAltAN):vsss0s dapsew cede gs lou ee siacetees sah pesos svdyedes aN pecedeedfeveuhety sveteetpedaseedpeincoregess 5-7 Natural logarithm (p_In) .0..... eee eeeeeescecsseeceseeeesseecsseecsseecesaeeesaeecsaeecseeceseeeesaeessaeers 5-7 Exponential (p2exp) cece bt ee eA RR Re A ee a 5-7 Logarithm: (p::108) 2.02) savei heehee av hil arene hi ei ih ell eed ees 5-8 SQUAT: TOOL (PASE). 25. discsatseedeces edusacessvevecesevepaceteenyetatsdusouetscsbedegstapedebsnnvedesosupaeebeaty 5-8 Raise to the power (P_POW) .........cesccessceceseceeseeeesseecseecsseecesaeeesaeecsaeecseecsseeeesaeessaeers 5-8 Double random number (long seed) (p_rand)...........eeeeceeeeessceeeeeeeeeeeeeeeeeesneeeeeenneeeees 5-8 Double random number (p_frand) ...........cceccceeeesceeeeseceeeeeseeeeeseaeeeeeeneeeeessneeeeeseeeeess 5-8 Floating point arithmetic without the 8087 emulator ........ eee ee ee eee ees eeseeeeeeeseeteeeeees 5-8 Assignment: (pfld) ss: cs:c..ccaseestespiesdeoeekieegie des bebediyieel hye dvlal haben ane 5-8 AG (ot fad) a cco sev ieetese ceed eas ebtee eh Gave enable ak Veh vada vite WVedcl ces esetecundersstieedaee eh 5-9 Subtract: (p= fsub) si cc. ses. ccseseanceeeccaaceesceaaceescodaceescedescavecda seaucdueecsucedacvat ceudevanceseevancesendea 5-9 Multiply (p7tmull) s.0 cece iecescedestecitbcstecavese tect acotechesda tue bestecugecetestbaeetecevetetesiprentedy 5-9 Divide (Pp: f01V). i sccccc. cede ciescaciissceaeideesardes sess viesvesanieseeserdcseisardeveiderd covasardessitandeneacandens 5-9 Compare-(p-ACMp)) 5.62 ccksttee cet eaiet eas teel aha eos thatch ait teeta aie ae a aie eaten 5-9 Neate: (p: Mee) .3.ssciasiiatisivevaiiaten carga esisavaisel astvainelamginnieines 5-10 Modiilus (pin) wiecccscaiee focaceteveducavedecatadedelasich Goiatededelace dagninteds dedsededncnsededadaeredaaneeey 5-10 Integer part (pant): 5 2eescp.cenyazsdeghs testes ees etig tes eee apa eee aad eee est 5-10 Convert double to integer (P_inti)...... eee eeeeceseecsneeceseecesaeeesaeecsaeecseecsseeeesaeessaeers 5-10 Convert double to long (P_int])...... eee eeeeesseeesneecsneeceseecesaeeesaeecseecsseeesseeeesaeessaeers 5-10 Convert integer to double (p_itof) 0... eee eeseeeeseecneecsseeeesseeesseecsacecseecsseeesseeessaeeesaes 5-10 Convert long to double (p_longtof) ........ eee eeeeeeseceseecsseeceseeeesseecseesseeesseeeesaeessaeers 5-10 OG Error Handling .icis.ciscccsiesscessecsvussccsvadeoctavssccvetedeccveassecevesedeocseasiseutadsdeerdacsseessededeactooedenssstoaes 6-1 Process teriminatiOn:.c:istsisoutislarss dation tialineaids ica tia docauisties Mathoauethon Maer des Mate iad 6-1 ‘TLermifiatin e this: process: f.5.56$:sciesesccuectehsdetevsa cg vbevsiedehestnoyntevsbcschqueadyes enubedeleces yeaewuncd 6-1 ‘Terminating another process 24. :.i:0.d..0i.isstedistdaciestticeiish daciestdsedib ischial ons halen 6-1 Finding out when other processes terminate ............ceeeeesseecsseeesneeeceseeeesaeersaeessneeeees 6-2 The process termination WOId...........cccccesecssecssceeseceeesseecsaeecseeceseeeesaeeesaeesseeesneeeesaes 6-2 PAIS his oh otafh ish scebsc chads ostocseuitevaoes saghe Sete eu boc edna tus Saestocnsiane Sui geigonsibabeoesseiedbiabesueags 6-2 System: PANIC NUMDELS ssc. sssisessvesiees des dees ves does aves seessees does sves dues svapbues svasdabaovapbibesvasdues 6-3 Terminate this process (P_€Xit).........ceesseesecssseecsseecsneecsseecssaeeesaeecsaeecseecsseeeesaeeesaeers 6-5 Terminate after an unrecoverable error (P_PAaniC) ..........eseeeeeeesseeseneecsseeeeseeeesaeeesaeers 6-5 Unilaterally terminate a process (p_pKill) 0.0... eee eeseceeseeceseeeeeeceeecsneeceseeeesaeeesaeers 6-5 Terminate a process (p_ptermimate).........eeeeseesseceseceeseeeeseeeesaeecsacecseeeeseeeesaeessaeers 6-6 Elect to receive termination message (p_onterminate) 0.0.0.0... ceseeeseeeeseeeseeeeeeeeeneers 6-6 Panic a process by id (P_PPanic).........eeceeeeccssseeesseecsseecsseeeeseeeesaeecsaeecseeceseeeesaeeesaeers 6-6 Request notification of process termination (p_logona)...........eseeeseeesseeeeeeeeseeesneees 6-6 Cancel notification of process termination (p_logoffa) ...........eeeeeeseeeeseeeeseeeeeeeeneees 6-7 Request message on process termination (P_lOQON)............essceesseeesseeceneeeeeeeeeeeeeeaeers 6-7 Cancel message on process termination (p_logoff)...........eeseeeseeeesecsseeeeseeeesaeeeeaeees 6-8 Cancel message of specific type on process termination (p_logoffx)..........eeeeeeeees 6-8 Watching:all-exaits: (p“watchall) - 2.5. cis.ctcsastesidsepbssdspscdnsscespdticonssddasceapdescoverdedevapoeye 6-8 ill PLIB REFERENCE Error Tetum: a.iestawest eis t at iste inte tia GS a A Ena eet 6-8 Convert error number to string (P_errs)........cesccesssecesseessseeesseeceseeeesseecsaeecseeesteeeesaes 6-9 Notifierservices isi iiiseiesictsea sated aia avei nied dohavhs edvuh ai asinr abate eae 6-9 Present the user with a message and get response (p_Mnotify) .......... ee seeeseeeeseeeeseeeeeeee 6-9 Notify user of error and get response (p_notifyerr)..........eeeesccceseeeesseeesneeteteeeeseeeesaes 6-10 Setnotify. state:(pSemotity) :e: be seviee ons .e el ied eat itn abe eS ale eM 6-10 Get notify state (p_getnotifY) 0.0... eee eeeeeesceceseeceseeeesseecseesseecesaeeesseessaeesseeesteeeesaes 6-10 Hook the notifier interface (p_notifyhOOk) ......... eee eeseeeseeeeseeceseeeeseeessaeecseeesseeeesaes 6-11 Unhook the notifier interface (p_notifyunhook) ............ceseeseeceseeeesneeesneeseneeeeseeeesaee 6-11 Bnterand leaves: i.cicitssaceucatsccesis its. ossiasineectas seaste saves leaseanblaissetdaseen ladveetiaseeunlaecetast 6-11 Enter a function (p_enter)............cccccceeeesccceesnececeeneeeeeeeaceecesnaeeeceseeeeeenaeeeeesaeeeeeseneeeees 6-12 Unwind stack and return from last p_enter (p_leave) 0.0... eeeeeeeeeeseeeeeneeteneeeeseeeesaee 6-13 Unwind stack and return from last p_enter if error (f_leave)............ceesseeeessteeeeeeeees 6-13 7 Memory Allocation. .............cccscccsscssscssscssecssssseesscsscsscsssscscesssssesssscseesssscsesssscssesssscssesssccsessees 7-1 Overview of systeM MEMOTY USAGE ......... ce eeeeseseecsseessseecsseeeesaeeesaeecsaeesseessseecesaeeesaeessaeers 7-1 Memory Seaments..2.ccciiishs otictovesdbehdeacned cova deeladuneais ceesguube doteweh eqveqate dea couh Suesduvacsedees 7-2 OEPMENE NAMES isc sssiisis esos dissdsetseandesk Aissdekd aseesh Aapoestousecisbovsecasteenpass daxsiasddexdeseets 7-2 Segment handle, address and SiZe...........eeescceeseceesecesseeeseeesseeceseecesaeecsaeessneeesneeeesaes 7-3 Process: data Segments’, .:siccsissctes.scisagaliass iat sassgatioes ise tavteanaeteapensasicasigaasgeasaaycaaiass 7-3 Phe Heap: allocatOrs. sce cis.k oeloeeses sida dhecbeseeaanhdeet bietoess cei teed aehoasbensd obeedevsodyemebouva gueyodenecebed 74 Heap Structure: ii.c.dctesec avbisefcnadesscvsvassdvandessovevioss doandvasdvacbest sostdassapbacesvataesioastaseoes 7-4 Growing and shrinking the heap... eeeeseseecsseeseseeceseeeesaeeesseecsaeecsaeessseeessaeensaes 7-4 AllOC- DEAVEn fetiehvis eA Ree wale ila ie inhi in ial eats. 7-5 Internal fragmentation .......... eee eeeeeeseecsseecsseeceseeceseeeesseecsscecscecsseesesaeeesaeecsaeeseneeeses 7-5 Allocate a memory Cell (p_allOc) ...... ieee ee eeeseceeeseeccesaeeecessaeeecesaeeecesaaeesesseeeess 7-5 Free an allocated cell (p_firee)..........ccececccceesncceeeeseeceeseeeeeeenneeecesneeeeeseaeeeeesneeeeeseeeeees 7-6 Change cell size (p_realloc) sic. scvccesicvsscus ces veguetesi cesta nceuneesieceesvanegseevevocas dan evsicesbeds 7-6 Insert or delete data in cell (pP_adjust) ...... eee ee eeeeeeecesseecsneeesseeceseeeesseeesaeesseessseeeesaes 7-6 Getcell length: (pialen) ssi eat tpint itsdeiyied Gist testes ates benvieh iii bearietene 7-7 Set heap granularity (pP_hgran) ..0...... ees seeesseeesseecsneeceseeceseeeesaeecseessseeseseeeesaeeesaeers 7-7 Visit all:cells’ (pvallwalk)s:. .c..ccacseccdiecseccavevancedscvas ccaendercedescetccavscne consacencesvedaecesecte ees 7-7 Check heap integrity (p_allchk)...... eee eesecceseceseeeeseecsaeecseecsseesesaeessaeessaeesseesees 7-8 Get heap address and potential free space (p_allspc)..........ceseeeseceesseeesneersneeeeseeeeeaes 7-8 SYSLEM: MEMONY USAGE so. cee. s cass Miveas auacees unt Seen atn, css alba subalta casateeses sabdesdstaebsds sabebonebee 7-9 Get addressable system RAM size (p_getram) .........eeseeeseeesseeceseeeesseeeseesseeesteeeesaee 7-9 Get total system RAM size (p_totalK) 0.0... eeeeeseecesseeesseeesseeceseeeesseecsaeessaeessseeeesaes 7-9 Get size of available segmented memory (p_Sgfree) ......... ee eeeeeeeeeeesreeeeteeeeneeeeneeeesaes 7-9 Get memory used by internal RAM disk (p_sgramdisk)...........escceeeseeesseeseneeeeneeeeeees 7-9 Memory segments: 33:2 sesinciasi sas thous eat hinds ee atin ene ee Rv oe 7-9 Create memory segment (P_SQcreate) 0... ee ee eeeeeceeeeeeeesseeeceesseeeceseeeseesaeeeens 7-10 Delete memory segment (p_Sgdelete)..............cceeeccecesesceeeeeeeeeesneeeeeeeaeeeceeneeeeeseeeeees 7-11 Open memory segment (P_SQOPeN).......... eee eeseeeeeesseeeceeseeeeeeseeecessseeesesseeeceeseeeees 7-11 Copy to memory segment (P_SQCOPYtO)........e ee eeeeeceesseeeceesceceeseeectesseeeseseeeeeeseeeees 7-11 Copy from a memory segment (P_SQCOPYAT)........eeeeeseceseessteeceseeeeseeecsseecseeesseeeesaes 7-11 Get size of memory Segment (P_SQSIZC) ....... see seeeeeeeesseeesneeteseeseseeeesaeeesaeesseessteeeesaes 7-12 Adjust the size of a memory segment (p_Sgadjust) ...........ccscceceesseceeeeeneeeceeteeeeeseneeeees 7-12 Find segments by name (p_sgfind) ...........eecccceeesceceeeenceeceeeeeeeceseeeeeesaeeeceeneeeeesneeeess 7-12 Close memory segment (P_SQClOSE) 0.0... eee ee eeseeeceesseeeceeseeeeeeseeeceesaeeeceseeeceeseeeess 7-12 Increment segment usage count (Pp_S]OCK)........ ec eeeeeesecsseessneeceseeeseeeessaeesseeesseeeesaes 7-13 Decrement segment usage count (p_SgUNIOCK).........eeeeeeseeesseeceseeeeeeeecsseersaeeesteeeesaee 7-13 Environment variables: cecctsesey a eetesteceine reeset teed sta nebsdeesteaissiabeger vier aeeenebeenseleee 7-13 Get environment variable value (p_QeteNv).........eeeesecsseeesseeceseeeesseeesseecseeenseeeesaes 7-14 Get environment variable value (p_getenViTON) ........... se eeseeeeseeceseeeeseeeeseeteeeeeseeeesae 7-14 Set environment variable value (p_setenv)..........:::cceeseeceeeeenceeceeeeeeeeeneeeeeseneeeeeseeeeees 7-14 Set environment variable value (p_setenVirOn)............::cceeeesceeeeeenceeeeeneeeeeeeneeeeesnaeeeees 7-14 Delete environment variable (p_delenv)..............ccccccesesscceeeeeeeceeneeeeeeeeeeeseneeeeeseneeeees 7-15 Delete environment variable (p_delemviron) .............cc:cccceeeseeeeeseeeeeeeeeeeeeneeeeeeeeeeees 7-15 Find environment variables (p_findenv) ............ccecccceeesccceeeeeeeeeeneeeceeeaeeeeeeneeeeeseeeeees 7-15 Find environment variables (p_fimdenvirom).............cc:cccceseeceeeeeeeceeeeeeeecesnneeeeseaeeeees 7-15 CONTENTS 8 Asynchronous Requests and Semaphore ..............cssscccssscssscsscsseccsscseecssssesssscsscsssscseesssesees 8-1 DEMAPNOLES 2 aie ssassutss causes saepe dea suuseveyssuteveyssucedey saute dey stuteddysuute des stucedssndutedvestute Merdetavevsteaates 8-1 Process; scheduling 3:ia.tincaniienriseniiiagis aniiia cis ii avin ain pin ie 8-1 SHared:access uti asain Sod we Ae ee Rad ae ot et ae ot a Bl a hk, al 8-2 Supphier-consumer vii) cveieecii aie all i navel a iene 8-2 ASYNCHTONOUS TEQUESIS :..s205 dee scapsessedepsdpesebsubtedes onus scebsunsedebontpageroutaedetouspraubenagedebonepsantensy 8-2 Ihe l/O: sémaphote.sicea tics arti nied ara esha egret getneeeey 8-2 StAtUs: WOLdS: ei ie Seed ete ctet otitis Stati eal etet tal aie Aa Ai tt on 8-3 Cancelling an asynchronous request ............scceseccesseeeseecseecssceceseeeesseecsaeesseeesseeeesaes 8-4 Waiting for a particular Completion ........... ee eeeeeeseeceneeceseeeesceessaeecsseecseeesseeeesaeeesneees 8-4 Constructing synchronous fUNCTIONS 2.0... eee eeeeeeeseeeesseecsseecseeceseeeesaeeesaeesseeesseeensaes 8-5 Wearthhandlerss.. sti: sicch ccecstat oR slide cues ois sant ato Bact stna been ater ed ted ot cevadietah oat eet, 8-5 Polling rather than Waiting... eee eeeecesseecsneecsseesscecsseeeesaeeesaeecsaeecseessneesenaeeesaes 8-6 Attached W/O devices sox... ieeccees. (ban edie sessedey bashedvesvetecagudas step aveesdepsdehontpesebededadehontpeanbadeg 8-6 Primitive semaphore fUNctions ............seeeeeeeseeesseeeeseeceeecscecesaeeesseecsaeecseessteesesseeeeeeeeses 8-6 Create a semaphore (Pp_SCMCIt).........ece ce eeeseeceesneeecesseeeceesseeecessseeeseseeeeeeseeesesseeeees 8-6 Delete a semaphore (p_semdel) .0....... cee eeseceesseessseeceseecsseeceseeeesaeecsaeecseecesaeeesaeessaeers 8-6 Wait on a semaphore (p_Wait) ..........eceeseceseceseeeeseeeesseecsaeecseeceseeeesaeessaeessneeesseeensaes 8-7 Signal a semaphore (p_sigmal)......... ces eeesecsscecsseeceseeeeseecsaeeceeeesseeeesaeeeseesseessneeenes 8-7 Signal a semaphore n times (p_signaln) ......... eee eeeeeeeseeesseeceneeeeseeeeseeeesaeesseeseeeeee 8-7 Signal a semaphore with no re-schedule (p_signalnr) ...........esceeseeeeseeeeneeeeneeteneeeees 8-7 DHE TAO Seta ph Ores sc:5, si /ocatservevetsss cecebsvs, odes sunp eden savy sgesschyass bouts deetocey edetoaspraubonagstebouepsasbensy 8-7 Signal the IO semaphore (p_iosignal)..........eecesecssseeesseecsseecsneeceseecesaeeesaeessaeesseeeens 8-7 Signal the IO semaphore of another process (p_iosignalbypid) ...........:eeseeeeseeeeseeeeee 8-8 Wait on the IO semaphore (p_10Walt) ........ cee eesceesseeesseeesneesseeceseeeesaeecsaeessaeessneeeesaes 8-8 Allow any wait handlers to run (p_loyield) ..........eseeseeeesecceseeeesneecseeceseeesseeeesaeessneers 8-8 Wait for a particular request to complete (p_waitstat) 0.0... ee eeeeeeseeesneeteneeeeneeeeeaes 8-8 Woatt anidlers x. toe. a, Sug sogstede opes sia conte ones ales ente fs ove ts Uaoans Dace veaud otnabhbess atest scerbunteanteestotensts 8-9 Add a wait handler function (p_svecadd)...........ccccccecssscceeeeeeeeeeeeeeeceseeeeeessneeeeessaeeeess 8-9 Activate/deactivate a wait handler (p_sveccall).............:ccessccceesenceeeeeeeeeeeeeteeeeeeseeeees 8-10 Remove a wait handler (p_Svecrem) ...........:::cccssesceeeeseceeeeeeceeeeesaeeeceeeeeeesseneeeeesneeeees 8-10 QD. TO SYStEM sscsciscscscessoessnviscevevedssedevsssecdecedsocdonssssevosescocbasdesvenedsogeansdoscvensdeonbaosseentadedceeseatsenseso’ 9-1 VO; Device Drivers .sitiscesslatie tise tatctieoktateentis entation dateactialleataticasttaiss 9-1 LDDs :atid. PODS es. seienet Sous cosh Sioa grads eub gosh Sais thee dutewes Sesecoens dunes ceuscnes sdutgout covsaven sgt oes 9-1 Extermal device drivers'::s.4.csst2,.nisnisdiedinsacnsihode Mindi stonsth Miia A 9-2 Opening a channel to a deVICC oes eeeeceseecsnecesseeeesseecsseecsaceceeeeesseecsaeesseessneeeesaes 9-2 Operations on an open I/O channel..........eeseesecesssecesseesseesseeceseeeesaeecsaeesseeesseeeesaes 9-2 The file Server sisc.fo a sieties ihe dine shite ioievaeteesersiohntoes bavi Miedo aot haba 9-3 Attached: drivers: jiis.:hstosss | iiiosscsetis hs sohassaetiest Asioesstentiess casidevarbiseAssetisscouedone soeepaeed 9-4 Channel-based I/O functions .0....... eee eeececeseeceseeceseeesseecseecssceceseaeecsaeecsaeesseesesaeeesatessaeers 9-4 Open a channel to a device (P_OPeM) .........seeesceeeseeesseeesseeeeseeeesaeecseecsneeseseeeesaeeesaeers 9-4 Start an I/O operation (Pp_10)........sceeseeesecsseecssceceseeeesseecsaeecsneecsseecesaeeesaeesseeeeneeenes 9-5 Start an I/O operation with guaranteed completion (P_i10C)........eeeceeseesseeeeneeeeneeeee 9-6 Start an I/O operation and wait for completion (P_iOW) .........seceseeesreeeeneeeeneeteneeeeee 9-8 Close:an:I/O:channel (p- ClOSe)i.c.35;scesncsiisassassecectesuaneetlaneteatadaabeysacatosniaeseeasenaseunansss 9-8 Read from an I/O channel (p_read) ...........cescceeeescceeesecceeeeeeeeceseaeecceeneeeeenseeeeeeeaeeeess 9-9 Write to an I/O channel (p_Write)............ccececccccessceceeeeneeeeeenneeeseeeeceeeeaeeeesenneeesseeeeess 9-9 Cancel requests on an I/O channel (p_iow(P_FCANCEL))............ccssesceeeeseeeeeeeneeeees 9-9 Device dfiver futict Ons: ssc..26sss2c.sscchoteilshtesteniaes ches Woaekdaeaceeddates sate oteendaieatesiateandiisoes Bees 9-10 Load a logical device driver (p_load]dd) ........ eee eeseeeseeesseecesseceseecseecsseeceseeeesaeessaeers 9-10 Load a physical device driver (p_loadpdd).......... eee eeeeeseeeesceessneeceneeceseeeeseeeesaeeesaeers 9-10 Delete a device driver (p_devdel)............cceessccessesceeeesneeeeeeeeeeeesnaeeccsseeeeeeneneeeeeseeeeess 9-10 Query the number of units supported by a device (p_devqu).........eseeseeeseeeeeneeeeneees 9-11 Find all devices (p_devfind) ............cceeeecccessscceeeeseeeeeesneeeeeseaceecesnaeeeeesnaeeeeneneeeesenneeeess 9-11 Simple; console O's. sss jets esicaseoeasdestoust csssass nethses Asseeisssestieeg ous tbass cent dose suesvaae Sorostedobi ons 9-11 Redirecting COnSOle WIites ...........cceseceseccesnceeseecsseecsseeeceseecssceceeecesseeesaeecsaeecsseeeesas 9-12 Changing the size of the console WiINdOW ............secseseceseeesteeeeseeeesaeessaeerseeesseeeesae 9-12 Changing the console Window MOde.............essseseseecsseeesseecesseeesaeecsaeecseecsseeeesaeeesaeers 9-12 Write a character to the console (p_pUtch).........eeseeeseecsseesssceceseeeesaeecsaeecsaeessteeensaes 9-13 Write a string to the console (P_puts) 0.0... .e se eeeeessecesseecsneeesneeesseeeesseeesseerseeesseeeesaee 9-13 PLIB REFERENCE Convert arguments and write line to console (p_printf).......... ce eeesecesseeeeeeeeeeneeeeneers 9-13 Convert arguments and write to console (P_Print).........e ee eeeeesseeceseecsseeeeseeeesaeeseaeers 9-13 Get a character from the console (p_getch)...........seesscesseccessecesneecsseecseecsseeeesaeeesaeers 9-13 Get a string from the console (p_gets) ..........eeseeeseecesseecsneecssceceseeeesaeecsaeesseessteeeesaes 9-13 Get a string with prompt from the console (p_getl) ..........eeceeeeeesecceeseeeeneeeeneeeeneeeeeaee 9-13 10 Time, Timers and Dates ..............ccccccccccsssssssssssssssssssssssssssssssssssssssssssssscssssscccccssssccccscsccsssssssess 10-1 SYStEMM tM, 22.453 ciss is Movethsiicds iatcessas hoeatatiesatielsaaeits Mrestieieduls teas detioss sta ielelincestetioatia 10-1 Return the system time (p_date) oe. cece seceseeceseeeeseecseesseeceseeeesaeecsaeecseeseseeeesaes 10-1 Set:the- system: time: (Psdate) a .ssp.set. Ass testesaphesssatiapeeedads Ash bekse sodas: Aapdeadesedshecs 10-1 Absolute: and relative timers: s:.2..600i55c2.us seus cava teag eens coveeesciva seyaeuva cows dev see dune davesduchus eva sevedued 10-1 Suspend process for n tenths of a second (p_sleep) ..........:eeseeeseecsseecsseeeeseeeeseeeesaeers 10-2 Suspend process for n system ticks (p_Sleept) .........eseeseceesseeeseeecsneecseeeseseeeesaeeesaeers 10-2 Suspend process until absolute time (p_sleepa) ........... eee eee eeeeseeeeeeeeeetseeeseetseeeaes 10-3 ASYNChronOus tUMers's..53<.8sceseecsstevstes schvocsadteysceh acevseehatevscedscevecgestevscevscevacebaceyscebsdevsegestevsdd 10-3 Start an absolute timer (p_ioc(P_FABSOLUTE)).............cc::cccsssseceeeeeeeeeeeneeeeeseneeeees 10-4 Cancel a timer (p_iow(P_FCANCEL)) ...........ccccsscsceeeesseeeeeeneeecesneeeeeesaeeecnsneeeeeseneeeess 10-4 Close a timer channel (p_clOSe) ............cccceeeessceeesseeeeeeeeeeceenseeecsseeeceesaeeesseneeeeeseeeeess 10-4 Converting between binary representations Of tiMe......... cee eeseeeseeceseeeeseeeesecesaeeesaeereaeers 10-4 Convert system time to P-DAYSEC time (p_sttods) .00........eccceeessceceeeeeeeeeeteeeeeeeeeeees 10-5 Convert P_DAYSEC time to system time (p_Cstost) ...........c:cceesesecceeeseeeeceeeeeeeeeeeeeees 10-5 Convert P_DAYSEC time to P_DATE time (p_dstodt)........0.eceecceeesseeeeeseeeeeeeneeeees 10-5 Convert P_DATE time to PLDAYSEC time (p_dttods)..........ceecceeeeeseeeeeeeeeeeeneeeees 10-6 Find the number of days in the specified month (p_dayinm)............seeseeeeseeeeseeeeeeee 10-6 Convert day since 1900 to day in week (p_Wkday)..........eseeseceseecesseeeeneessneeesneeeesae 10-6 Calculate week number in year (pP_Weekn0)...........c:ceesceseseessseeceseeeesseeeseesseessseeessaes 10-6 Time and date components in text fOr ......... eles eeeeeceseeeseeesseeeneeceaeesseeceseeeesaeenaeeesaeers 10-7 Get the day name (p_nmday)........ eee eeseeeseecsseceesceeesseecsseecseeceseeeesseecsaeesseeesneeeesaes 10-8 Get the day name abbreviation (p_nmdaya) ............:eesscssseecsseeceseeeesseeesseesseeesseeeesaes 10-8 Get the month name (P_NMMON)......... ccc eeeeessssneeeeceeeeeeeenneeeceeeeeseeenaeeeeeeeeeeeeenaeeeeees 10-8 Get the month name abbreviation (p_NMMoma) ..............cccssccceeeseceeeeeneeeceeseeeeeseeeeees 10-8 Get the day-in-month suffixes (p_getsuffixes) 0.0... eeceeseeesseceseeeeeeeeeseecseeseneeeesaes 10-8 Get the am and pm suffixes (p_getampmtext)......... eee eeseeesseeceseeeesseecsseecsaeeesteeeesaee 10-8 Get time representation preferences (p_getctd) ........eeeeeeeseeesseeceseeeeseeeeseeceeeeeseeeesaes 10-9 Generating time and/or date StringS............ceeeeeesecsseeceseeseeeceseeeesseecsaeecseecneecssaeeesaeersaeers 10-10 Date and time format Strings 0.0.0.0... eeeeessseeesseeceecseecscecscecsaeeesecesaeeesaeesseesseeese 10-10 Convert a P_DATE time to a string (p_dt2str) oo... eee eeseeeseeceseeeeseeesseesseeeeseeeesaes 10-13 Convert a PDAYSEC time to a string (p_ds2str) oo... ee eeeeeeeseeceeneeeeeeseneeeeseeeesaes 10-13 Convert a system time to a string (P_St2Str) 0... eee eeeeceseeesseeceseeeseneeesaeerseessteeeesaes 10-13 Convert the current time to a string (P_NOW2SUL)........ eee eeeeesseeceseeeeeeeeesseeceaeeseneeeesaee 10-13 11 Files 11-1 vi VEST POC ao aerate cedseen sirecattldstet sahlpeestl ts deies sles iat Sidi cokes Satis shat els tat he Sa lilo set iat 11-1 The file server scs3.cicee teeing oaey bei aagiesd iv aid eng ne ea ae 11-1 File: SYStems's... 32.8 2s te A Rea oe ek Qa Bek A ie a ak 11-1 SSD drives. sibs ienyiethd aah ee a ee vd i ee 11-2 Unattended applications 2.0.00... eeeeesecceseeceseecseecsscecsseecseecesaeessaeecsaeecsaeesseeesenaeensaes 11-3 RAM SS Ds:z.t.cstiaistisd Slee eeste eden ahaa ape ae eee Gani eee eae ets 11-3 Blash SSDS sss01 seis on GN lat Ao Maat sh Ge eit ah AGA eects gO AGN ute ae BON Ss 11-3 File-specificati Ons .vessvccsescvcess sextcces sens cons ceaveeavacds ceanccavecancevvecaeseavesadccseeuaacevnuassccveenanes 11-4 Detault:path: 2:03 /5:0..cissed test ctsss Maetest eniehs Aosdin A aeiehieetedciehs hardin Antes Anes 11-5 Charinél-based Services's. is: szcsseees dies sch tuys sets bes tsteuscevsaubessh stexs cus sebesvelvevesvbateaetnivevest 11-5 Non-channel-based: Services 1: ..235isccsaiedosetis ec seadanaoeetaa seca wdativeahaguacaesbiaaveandaaousdaiaes. 11-6 Asynchronous file Operations...........:ccesscecesseeesseecsseecsceceseeessaeeesaeecsaeessaeesseeessaeeesaes 11-6 Manipulating file specifications............cccseccceseseceeesenceeceeeeeeecesceceeseaeeeeeeeneeeceeseeeeeeneaeeeess 11-7 Parse a file specification (p_fparse)..........ccccssccccssececeeeeseceeeseneeecesneeecessaeeeceenneeeeseeeeees 11-7 Using p_fparse across filing SYSteMS..........ceceeseesseeesseeessceeeseeeesaeecsaeerseeesneeesseeensaes 11-8 Change the directory in a file specification (P_Chdir) 0.0.0... cee eeeeeeseeeeeseeteneeeeseeeesees 11-9 The default node, device and directOry ..........eeeeeseseecesseeceseeeseecseecseecsseeeesaeessaeesseeeses 11-10 Set the system-wide default path (p_setdefaultpath) ......... ee eeeeeeseecsseeeeeeeeeeeeneers 11-10 CONTENTS Set the default path of this process (p_setpth) ..........eeeeeeecsseeessneessseeesneessseeeeteeeesaes 11-10 Get the default path of this process (p_getpth) .........eeceeeeeeseeesseeesseeceneeeeseeeesaeersneers 11-11 Get the default path by process id (p_getpthbyid)........ ee eee eesecsseecsneeeeseeeeseeeesneers 11-11 Operations on, nodes and devicesieissc..cs.55 Sas destneceeissesobissseap des naubiasnipdest Sasssespdensneitaaedes 11-11 Get node information (p_minfO) .............cceeesccceeseeceeeesnceeeeeeseeeeeseaeeeeeeneeeeeeseeeeeseseeees 11-12 Check if LOC:: has changed (p_locchg) .........eceeseeeseeeeseceseeeseecseecsneeesseeeesaeessaeers 11-12 Get a list of devices (p_open(P_FDEVICE))............:cccccccesesseceeeeneeeeeeneeeeeesneeeeeseneeeees 11-13 Get device information (p_dinfO).............cccsccccesssceecesnceceeeeneeeceseneeceeeneeeeseeneeeeeeneeeess 11-13 Read media information of a local device (p_locdevice).............e:ccceeeeeceeeeeseeeeeeneeeees 11-17 Direct read of local SSD (p_locreadpdd)...... eee eee eeeseceeseeceseeeeseeceaeecsseeesseeeesaeeesaeers 11-17 Operations on directories and files 1.0.0... eeeeeseeceseeesneeesseecececeseeeesaeecsaeecsaeecsteeeeseeeesaes 11-18 Get a directory list (p_open(P_FDIR)) .............cceccccesseccceesenceeeeeseeeceeneeeeseeneeeeeseaeeeees 11-18 Return file information (p_fimf0) ............cccccccceesesceeeeseceeeeeeeeeeeseaeeeeeeeeeeeseeeeeeeseeeeees 11-20 Test for the existence of a directory (p_testpth) 0.0.0... ceeeeeescesseeseseeceseeeeseeeeseeeesaeers 11-20 Rename a file or directory (P_renaMe)......... eee eeesecsseeeeseecesceeesseecsaeecseeceseeeesaeeesaeers 11-20 Delete a file or directory (p_delete).............ccccceeeesseceeseeceeeeeneeeceseneeeceeneeeeeeeneeeeseeneeess 11-21 Make a new directory (pP_MKdIL).............eeeeccccesseceeeesnceeeeeseeeeeseaeeecesneeeeeesneeeeeseeeeees 11-22 Set file attributes or label medium (p_sfstat) .............::ccesesceceeseeeeeeeneeeeeeeeeeesesneeeeeeees 11-22 Set file creation date (p_fdate) .............ceeeccccesssscceeseneeeeeseneeceesneeeeeseaeeeeeesaeeeeeeneeeeeeeas 11-23 Binary. file:access\..2: ai acich devieichaccin dieses bantu dante Bani daar 11-24 SAE ACCESS 25 5253 aoe use Us Heandhedi wack Seaaeeb abe eS e Saau vs cueady wb cada den Sues By eke Ta ayaeeTe Tyee eee 11-24 Exammple‘of binary file-access szt25..cc.:ciussetialcesddcissandaistesiesisaeataslscesdesiapeatea.cesbaaianess 11-25 Close a binary file channel (p_ClOS€) ........e ec eeseeeseceseeesseeeeseeeesseecseecseecesaeeesaeessaeers 11-27 Read from a binary file channel (p_read)......... ees eeseeesseeeeseeeseeecsaeecseeeeseeeesaeessaeers 11-28 Write to a binary file channel (Pp_WIrite) 0.0.0... eeeeesseeceseecsneeeeseeeesaeecsaeesseessseeessaes 11-28 Position a binary file channel (p_seek) 00.0.0... eee eeeseeeseeesseeeeseeeesseecseecseeseseeeesaeeesaeers 11-29 Cancel an asynchronous file channel request (p_iow(P_FCANCEL)) .............:::::08 11-30 Stream: text Mle:ACceSsi a. -5.5..visscesebicteaseisbscassdevecaaws das daswisng cavsoeea Sosa edegcventics suvavaassvancigesvaanics 11-30 Open a stream text file (p_open(P_FSTREAM_TEXT))............c::cceeeesseeeeeseeeeesetseeees 11-31 THOXt TIE ACCESS: tacsiacstadsacancs sezesties exGeahesaece teats onde ist «anand saeateeeasagteauacaueaataseeetacnegaeesaas 11-31 Open a text file (p_open(P_FTEXT))...........ccccecesceceessceeeeseneeecesnaeeecesneeeeeseneeeeesneeeess 11-32 Close a text file channel (p_ClOse) ............eeesscceessneeeeesnceeeeeeaeeecessneeeceeneeeesseneeeessnseeees 11-32 Read from a text file channel (p_read)............ceeeeecccesescceeeeneeeeeeeneeeeeeneeeeessneeeeesneeeees 11-33 Write to a text file channel (P_WTite) ............cccceeeeecceseneeeeeeeeeeeceeeeeeeeeaeeeessneeeeeseeeeess 11-33 Position a text file channel (p_seek) .00.........cccceeesceceesecceeeesceeeeeeneeeeeeneeeesssneeeeeeneeeees 11-34 Flush internal file buffers (p_iow(P_FFLUSH)).............cccsscceeesssseeceeseeeeeeseeeeeseeeeees 11-34 Set end of text file (p_iow(P_FSETEOP))..............ccccccceessseeceesneeeeeeeeeeeesseeeeessneeeeenees 11-34 Cancel an asynchronous file channel request (p_iow(P_FCANCEL)) ..............::::008 11-34 12 Processes and Inter-Process Messaging ..............scccsssssscssssssccsscssccssscseecssscsesssscssesssscseesssocsors 12-1 PLOCESSES - snc oe isbadtpoast tee deveganspadeeededetehnuses cots vebetvennton cot deletuborie coho tela Tntoutel coaeteaaueerees oontedy 12-1 SYSLEtM PLOCESSES sci sesstis laste asdebresindess tiptesbesaedesdapheansydncepha iasbhies Mish Geile 12-2 Process ID and process control DIOCK ...........::cccesecceceeeeceeeeeeeeeeeeeaeeeeeenneeesseneeeesenneeeess 12-2 PROCESS: SLALES we. y sas tstavccitens Velie teeteeeetstesis cevvdepei ar aaaceveeni nei eae ba vauiecaanecuiseavdsaseateceteey 12-4 PHOGCESS: QUEUES frdac ised evsn sie oadatecs ¢ edsns ve poang eles auenach godecotesoDtgarn (bceteten eVatbengteeteds eadttecegbec envy 12-4 PLOCESS: PTIOTItIOS 2 si ccieieseedandesbeseniesecdardeveedendcaeidandcvuidenta devise tecopicas cdesdeadedevadaecdeveceoees 12-4 Preemptive Scheduling: : 05, .:.c¢...s0ies ek vavsd saeeshgscun cee va tedestiaast ots satniesh caesbecesduce sshavestonesats 12-5 PFOCESS NAMES we ses se ceveedvevaeeci acess ebeavauadsaceseesusees deste verhnceet diay evens ouat ea ae uh Taree aS 12-5 Reserved statics (Magic StatiCs).........:cesccesssscceeeeseeeeeeeneeeceseceeseaeeeceeneeeeeseeeeeeseeeseaees 12-6 Shared Code:sepmients::, s1.5g.c2...egeysgidesspbestegeyhaibesrdesbgiyiesden reece duyoedesenebdieyoebeanaees 12-8 Tra esl eS: se, Ses sod sane ted oer tcict seh Sioe coun test oak og caartaiec eh eal satan cee hides Western dlceaatt eet eee ts 12-8 ProGess: terMinatiOM .cseevccsescsieeae ceveesanccaievcas cevedcatccesicna covvccancessccaa dus vecdaseaucdaaceauceseevseceseevanees 12-10 Cr@atitiS asprocess ia... vess ta fedat eves tat hfs sdetcengyotetedadet tts ciutetesotes terppdase elas oveendauythdevecidpeiteth ets 12-10 Load san: image:(p exec) s.2..5checyeet ey aekbedi gigs ceeedes dada piaed deeebeabegs pas edesbesbeepae eae 12-10 Load an image asynchronously (p_eXeCCASyMC)..........eeccceeeeseceeeeeceeceeeeeeeeeeseeeeeeneeeees 12-12 Create:a procéss (p= peréale).cctccii i atankt ined a iis 12-12 Operations on the CULTeNt PLOCESS........ eee eeseceseecesceeeseeceseeessaeecsseecesaeeesaeecsaeecseeseteeeesaes 12-14 Get this process ID (p_getpid) 0.0... eee eeseeeeseecsseecsneecsseecesaeeesseecsaeecseeesneesesaeeesaeers 12-14 Mark this process as non-active (p_Ummarka)...........c:cccceeesscceeesneeeceeeeeeeseeneeeeeseneeeess 12-14 Register activity (p_tickle)........ceeceeessccceesnceeeeseeeeeeseeeeceenneeecseeeeeeseaeeeeneneeeeeseeeeees 12-14 Mark this process as active (pP_marka)..........cseeseccesseeseeceseeeesseeceaeecsseeceseesseesenaeeesaes 12-15 vii PLIB REFERENCE Operations ON ANY PLOCESS 0.0... eeeeeeeessneeesseecsseecssceceseeeesseeesacecsaeecseecssaeeesaeeessneeeeseeeesaes 12-15 Get a process priority (P_Qetpri) ....... eee eee seeeseecsseeeeseecseecseeceseeeesseecsaeesseessneeeesaes 12-15 Set a process priority (P_SCtpTi) ........eeeeeeeecesseecsneecsseeceseeceseeeesseecsaeecseeessseeeesaeeseaeers 12-15 Resume a process (P_PreSUME) ....... eee eeeeeeceeseeeeeeseeeceeseeeesesseeeceesseeeseseeeceesaeeeees 12-15 Suspend a process (p_PSUSPeNd)...........seeeeeesseeesseecsseecsseeceseeeesseecsaeecseeseseeeesaeeesaeers 12-15 Get a process name by ID (p_pname)............eesceessecesseecsseeceseeceseeeesaeecsaeesseeseneeeesaes 12-16 Rename a process (P_PreMaMe) 200.0... eee eee eeeeseeeceesceecessececceseeecessseeecesseeeceeseeeess 12-16 Get a process ID by name (p_pidfind)...... eee eeeeceneeceneessseeceseeeeseeecsaeesseeesteeeesaes 12-16 Find all processes (p_pfind).......... cc eeceeeseceseecsseeceseeeesseecseecseeceseeeesseeesaeesseeesneeensaes 12-16 Determine the owner of a process (P_gQetOWNET)...........::cceeeesceeeesseeeeeeeeeeeeeeneeeeseeeeees 12-17 Accessing a process data SCgMent.............::cccesessceeeeeneeeceeeneeeeeeeeceeecesseeeeesnaeeeeesneeeeeseeeeess 12-17 Copy data froma process (p=pCpyft) uc. sseccissd-ssiesedeas dseteestbedacsspdsicvesplaasdoapdossoeseeaabers 12-17 Indirected string copy from a process (P_PISCPYfT) ..........:ccssccceeeseceeeeeeeeeeeeeeeeeeseeeeees 12-17 Copy data to a process (P_PCPYtO).........eseeesccssseeceseeceseecseecsseeceseeeesseeesseecseeesseeeesas 12-18 Titer process:messa gin gyi iesioc5ise nasa desi oteh Sek Seok donk Seehcbekbies Sock cdondeed Like on feud Waadasheaabee 12-18 Message: slots:ssi ss s:cssissssustiess datbsie ostions Gapees avbins Aaadies assess Asians austen Asides nantes os 12-18 What the:Server does. .2.s5 sci seus eszsths fet steyestasvis sel scveseusssube ful cevuscayssbes sd cavuscuvestbeavisveyest 12-19 What: the CHent dOeS ¥:s.s.i:.4setesieccetlanissotgaletestdasbeentasiecestSawsvosteaiacs tlavivestastarent ladoeenace 12-19 Atv example Of a SEL VEL if oct Seosccc) bach Schl coueess Sock bouie ceveoehcccaaed caveneus ohevedsaubbeguscven serene 12-20 Corresponding client code example ...........cescceeseeesseecsseeceseeeeseeeesaeecsaeecsaeessneeesseeeesaes 12-21 ASYNCHTONOUS: MESSAGING c s2.. 6225 cewek cea syea cadet eee ovs dank Sobtah we cov aaekg FuvTa estas Sea ndeVta seek eae 12-21 Message processing OFeL ...........sseccccesscceeessceeceencecesseeeceesnneeeseeeeeeeeeaeeeeseneeeeeseseeess 12-22 SERVED TUNCHONS 23 vose se oes26i Saf oak sslat es eeaaeuabaessckibe caged saubceeis lon Sedensoersyoseneodeduneeesentodougs fy 12-23 Initialise for message reception (P_MIMIt) ...... eee eee ceseeesneeceneeeeeeeeesaeerseeesteeeesaes 12-23 Wait for message reception (P_MIeCC1VEW).........-eeseceeseceeseeeseeessaeecseesseecsneeeeseeeesaes 12-23 Asynchronous message reception (P_MIreCelVe) ..........::ccceeeesceeeeseeeeeeeeneeeeeeeeeeeeseeeeees 12-24 Cancel a message receive request (p_Mcancel) ........ eee eeseeeseeceseeeeeseeesaeesseeesteeeesaes 12-24 Freeda messagé-(p: mire): -i i ccssssbesed cedeaccvisdusecedesieacvtedusteoteasch dons vencsesege da Svansastoeseca tees 12-24 Chent fun CH OSs syc0i. seus sdsiiecsets hesfiovtuesvevs Geis Goth Sd eedviiersts dase tees ea eases hase 12-25 Senda. message’ (p mSend)ssc2.ics.sccsueissscennsseatesteasaceabsdesganhe sant sanoeaoebieaeoeetieenseahaase Ss 12-25 Send a message and wait for a reply (p_msendreceivew)............:ceseceeseeceseeeesneeeeneers 12-25 Asynchronous send message and get reply (p_msendreceivea)............eseeeseeeeseeeeeeee 12-25 13 General System Services ..............ssccsscscscssscssecssscecsscsccsssccessssescescssesssscssessssessesssscsssscsseessseees 13-1 SyStemn- in fOrMalOn ss si. ssestewes hos Abst ss cach shes pbsDeves cet dies Avs TG sous thug baDUS. cous hess TE cast heeds 13-1 Get the operating system Version (Pp_VeTSiON)..........::cescceesessseeceseeeesseeesseersneeesneeeesaee 13-1 Get the ROM version (P_romVversiOn)...........::ccccesccceeseeceeeeeeeeeeeesneeeeeseaeeeceeneeeeeseaeeeees 13-1 Get the cause of the last system shut-down (p_getres) .........eseceeseeseseeesneeesneeeeneeeesaes 13-1 Get operating system data (p_getosd)..........eseeseesseecesneessseesseeceseeeesaeecsaeesseeesseeeesaes 13-2 Get power supply type (p_QetPSu) ........ ee eee ec eeseeeceesseeeeeeseeeeceseeesessaeeeseeseeeceesaeeeees 13-2 Wan Sua Se AN" COUNTY 12 555 Sica Seok sheesh ok bees ek het Shek eebge sh Sues eoabe solwead yaebvil coleneh een eoid ewiete 13-3 Get the language code (p_getlanguage)...........eseeeseesseecsseessseeceneeeesaeessaeesseeesneeeesaes 13-3 Get operating system text (P_QetteXt).... eee eeeeesseecesneecsneecsseeceseeessaeeesaeecseeseteeeesaes 13-3 Get country-dependent data (p_getctd) 0.0... eee eeeeeessseecsseessseeceseeeesseeeseecsaeessseeeesaes 13-4 Set country-dependent data (p_setctd)...... 0. ee eeeseeesseecsseeceseeeesseecseecsseeceseeeesaeeesaeers 13-4 Switching on and off.s.e55s:3 iets teres Hides ede Penance diet aa meie alban demyinbe eaned ae 13-5 Switch-off: (Pp: Off): sees sec Gotia viet dette enti ee kaa ek Ae AN ae es 13-5 Get the auto-switch-off period (p_getauto) 00.0... eeeeseesseessseeceseeesseeessaeecseeeeseeeesaes 13-5 Set the auto-switch-off period (p_Setauto) 00... eee eeeceeeseecesneeeseecseecseecsseeeesseessaeers 13-5 Get switch-off state when mains is present (p_getautomaiNs)............:eeseeeseeeeseeeeeee 13-5 Disable/enable switch-off if mains is present (p_setautoMaiNS) ...........eeeeeeeeeeeeeeeeee 13-6 Allow auto switch off (p_allowoff)............cccecsceceesncceeeeeeeeseeeeeeeseeeeeeeneeensaeeeseeneeeeeeees 13-6 Enable/disable the ON key event (p_SetoneVent)...........eeeeeseeceseeeeeeesseesseeeeteeeesaes 13-6 Power SUPPLY ss. itccei ets oite Moiese eee Se tei nea ee eae 13-6 Get power supply status (p_SUPPLY)........0. eee eeeeeeceesseeeceeseeeeeesaeeeceesaeeesesseeeceesaeeeees 13-7 Get additional power supply data (p_supplyinfo) ..0...... eee eeeeeceseeeeeneeeeneeseneeesseeeesaee 13-7 Get battery warning and maximum levels (p_WSUPpLY)...........-.esecseseeeseeeeneeeeneeeeseee 13-9 Get the battery type (p_getbat) 0.0... eee eesecsseceseeeesseecseecsseeceseeeesaeessaeerseeesteeeesaes 13-9 Set the battery type (pi setbat)iaicc heli at ail etteelon ail iti hai Alaris 13-9 Keyboard yi.2325) aiteeiseseneidiveibaeyssyideoes esta iis ser egieiest Randy eines ieeeneai asi cetcenese eae don 13-9 Get the state of all keys (p_getscancodes) .0.........esseeeesecsseeesseeceseeeesseeesaeecseeeeneeeesaes 13-9 viii CONTENTS Display iesesissaeusrestecei bess 2eveptazebs feistebesia loves paubteestna stvke eb tveslov dies Fevadhestoasieks seadvslovsenaeeedy 13-10 Get the system display type (p_getled) oe. ee eeeeesseeceseeceseeeeseecsaeecseeeeseeeesaeersaeers 13-10 Change the LCD contrast (p_Icdcontrastdelta)...........seeeeeeesecesseecsneeeeseeceseeeesaeersaeers 13-10 Get the current LCD contrast (p_gethcdcontrast)..........ceecceeseesseeeeseeceneeeeseeeesaeeesaeers 13-11 Switch the backlight on or off (p_backlight) ........ ee eeeeeeseeceseeeeseeeeeeeeesseessaeeseeeenes 13-11 Set the backlight control value (p_setbacklight) 0.0.0... eecceeseeeseeeeseeceseeeeeeeeaeessneeenee 13-11 Get the backlight enablement (p_getbacklight) ........ ee eeeeeeceesseeesseeeeseeceseeeesaeersneers 13-11 DOUMG aces satiets ibs. ee lrevsess deaesvisugedues Aavesesadsdouakach stbece Ausudeed evsbens sesmries susasdesheaatiesouaeeersaaaeaes 13-12 Make a sound with the piezo (p_SOUNG)........... se eeseeeseeesseeceseeeesseecsacecseeessaeeesaeessaeers 13-12 Get the:sounid flags (p~Setsnid )isvicic ascetic asesndausesenas aosendassceebaccaosondauseeeigacassendaseasieaiss 13-12 Set the sound flags (p_setsnd)..........ceeseesceeesecsseeeeseeeesseecsaeecseeseseeceeaeeesaeesseeseeeese 13-12 SOuMGON the Series: Bais. ee sas sess baehiatesodses Aas dda ceagedads Sass deudcavedeua csnvseacouse cues cvsasiasoesecuessvaarces 13-13 SOUMG: PCG 5 08 cece sek cteveznks Heekendedeesesha deesessadeecsseadies cubadu ys tadaduwasevgduncteds dyveceds tunesessdunssebane’ 13-13 The A-Law-encodine Schemes. ic..i:ic-arisuiseuntaistanssoanaiianpadaiaiaieaniaiciet 13-13 Series 3a sound SySteM SELVICES..............::esececsoresesscrensetensnesnonersssevenseneneeesnenestosenenseteneeees 13-17 Record a sound asynchronously (p_recordsounda) ...........::esecesesecsseecsseeeeseeeeseeesaeers 13-17 Cancel sound recording (p_recordsoundcancel) ...........ceseceesseesseecsseecsseeeeseeeesaeersaeers 13-17 Record a sound synchronously (p_recordsOundw)...........::ccsssccesseeesseeceseeeeseeeeseeesaeers 13-18 Play back a sound asynchronously (p_playsounda)............ssseesseseseeeeseeeeeeeeeseeeesaeers 13-18 Cancel sound playback (p_playsoundcancel)..........eeseeescceeseeeeeseeceseeesneeseseeeesaeessaeers 13-18 Play back a sound synchronously (p_playsoundW) ...........:eesceeesecsseessseeeeseeeesaeeseaeers 13-19 Miscellaneous s..:is..ecstdsssecadeascest sssaacataateeatesssacateaustes teat beateaieceasodeipeubacntdonseuaeextanteaaneantss 13-19 Exit t6:DOS:i(p Dwexit isi ocecissek deka oak cack aa hodee eae Pek elt HOLL Coheed Perel ote 13-19 Null: action) (pdummy).sscc.2hse¢ssties, ccssdeas sosnbess diastase hethess Aanlanenerdass Aactasomarissnattass 13-19 14 Database Files............sccsssssscssscssscsssessscsssesssssssessscsssssssessssessssesssesssessscssscssscssscssscessesssessoeees 14-1 Overview of database files ............cccccceeesscceesseceecssnececeesnececeesneeeessaeeecsecaeeeessneeceesaeeessenees 14-1 The file header ic.23 cc. cssccissctaecevvcuac deve stan devedansceseduvandeaesaceseiuarceneivan cons denecesecae ce seenvens 14-2 ROCOLAS 3, fecsecth code ete doduaies Pout eteastuei ee deat edepetat eva vantedegeletecsertatedseslesedenransedevslehervaadteccey 14-2 Strin Hels: .3:25 seysessegipassd abe dephaes aeseedaad ei gages ewes aivlshe hal aayoen iene: 14-3 Number Of TECOrd S's. soitess ciecd oaks ice ah aed ca ae at ck Goenka ieee Uaehe ee eset cada een atid eae tient bec’ 14-3 End. of file T6COrd sce. ceiscisvcestadeceuas seve evans cveiasaceae saute eeu ceaaceesceauceeycdda vescddaecsecddacvanceseeees 14-3 Database files and OPL ............cceeccccessscceeeeseeeceeeceeeeeeeeeceenaeeecsseeeesenaeeecneneeeessneeeess 14-4 DBF fUnCtiOns y: s.ciscticcsisatcciviuek jevndsatecspasstecsvduetcdevdcedecus dene cevvacee cdevdeancdevecsoeds vacee ceeuecsbeseslade 14-4 Open a database file (DbfOpen).......... eee eeeeeeseecsseeeeseecsscecssceceseeeesaeecsaeersaeessteeeesaes 14-4 Open a database file (DbfQuickOpen).............ecceeeessseceeeeesreeesseecsaeesseeceeesneeesseeeesaes 14-6 Close a database file (DDfCIOSE) ..........eccccccccccesessseeceececeeseesnseeeeeceessesseeeeeeceessesssaeeeees 14-6 Flush a database file (DDfFIUSH) .............c cc ccceesssccceeccesssssseeeeeceeessesneeeeeeeeessesseeeeees 14-6 Notify that the DBF buffer has been overwritten (DbfTrash)..............::cceessceeeeetteeees 14-6 Copy down a DBF record (DbfCopyDown)........ eee eeeeeseecseceeeseessceceeeeeseeeesaeessaeers 14-6 Compress a database file (DbfCompress) .............s:ceeseeesseeeeseeeeesececsseeceseeeenaeeeseeesaes 14-7 Copy a database file (DbfCopyFile) 0.00.0... eee eeeeeceseeceeeeseeesseeceaeessseeceseeseeeesaeeesaes 14-7 Find the size of a database file (DbfFileSize) ............cccccecssseceessessneeeeeeeeeseesseeeeeees 14-8 Read a DBF extended header (DbfExtHeaderRead) ..............cccccccssscecceeeesessteeeeeeeeees 14-8 Write a DBF extended header (DbfExtHeaderWrite) ..............ccccccccesssccceceeeesteceeeeeeees 14-9 Read a DBF descriptive record (DbfDescRecordRead) ............::scccceseeceeeeteeeeeeneeeeeeee 14-9 Write a DBF descriptive record (DbfDescRecord Write) ............ccsecceeeeeseeeeeeeeeesteeeees 14-9 Get the DBF version number (DbfVersion)............ccccccccssccccccsssesseeeeeceeesssseeeeeeseseeaaee 14-10 Read a specific DBF record (DbfAbsRead) ............ceeeecceeeeeeeeeeeeeeceeeneeeeesneeeeeeneeeess 14-10 Read and sense a specific DBF record (DbfAbsReadSense)............:ccccccesssceeeeeseeeeeeeee 14-10 Read the next DBF record (DbfNextRead) ..........ccccccsssscccceeesessneeeeeeeeeesessneeeeeeeeenes 14-10 Read the previous DBF record (DbfBackRead) .............ccescccceeeseceeeeeeeeeeeneeeeeeeneeeeeees 14-11 Read the first DBF record (DbfFirstRead)............000cccccccccccccscececceeeeeeeeessssssssessseseessees 14-11 Read the last DBF record (DbfLastRead) 0.0.0.0... ccc ccscccccccccccccceeceeeceeeeeeeeceeeeeeeeeeeees 14-11 Append a DBF record (DbfAppend) ..............ccceeeececeeseeceeeeeceeeeeeaeeeeeeneeeesseeeeeesnneeeess 14-12 Erase a DBF record (DbfEraseRead)................ccccccccccsscscccccceeesesseeesestseeesessssstssseeeseees 14-12 Update a DBF record (DbfUpdate)........ eee eeeeeeseeceseeceseesssececeseeeesseessaeessaeeesseeeesaes 14-13 Find a DBF record (DbfFindReadField)..............:cccccccssssscccceeeeessesseeeeeesessessseeeeeeeeeees 14-13 Find a DBF record (DbfFindRead)..............cccccccccccecccecceeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeees 14-14 Sense the current DBF record number (DbfSense) ............:ccccccccsssscececeesesssssteeeeeeeeees 14-16 Count the number of DBF records (DbfCOUNE)............ceeeceeccccccessessneeeeeeeeessesessstseeeeees 14-16 PLIB REFERENCE 15 Object Oriented Programming ...............cccsscccsscssscssscssccssscsesssscsessssccessssesesssscsessssesesssssesoes 15-1 CHASSES at the beginning of your C source file. 3The code, written in 8086 assembler, that provides a C function interface to a ROM-based service is sometimes called a C shell. 1-4 1 INTRODUCTION The plib.h header file collects a default set of header files that are sufficient for the functions described in this manual. For most source files, plib.h will include more than you actually need. Once you are familiar with PLIB and if you can be bothered, you may wish to browse around the header files in the \sibosdk\include directory to work out which files you need to include in a particular source file. In all the structs in the PLIB headers, we have been careful to organise the members so that 16-bit (or wider) variables are on even address boundaries - since the 8086 processor can fetch a 16-bit word in a single cycle rather than two if the word is at an even address. p_std.h Because Psion has had ten years of working with C using tens of different C compilers to develop for tens of different target computers, we have of necessity developed a fairly defensive approach to our C sources. Rather than using the C variable types and declarations directly, we define our own to give us the opportunity of re-defining their meaning, depending on the compiler. All these definitions are in p_std.h that is included first by plib.h. The following extract from the Clarion version of p_std.h is for declaring functions and data: #define GLREF_D extern #define GLDEF_D #define LOCAL_D static #define GLREF_C extern #define LOCAL_C static #define GLDEF_C where the _c and _p refer to code and data respectively and where: LOCAL_C are used to declare local functions and local static variables LOCAL_D GLDEF_C are used to declare global functions and global variables GLDEF_D GLREF_C are used to declare function prototypes and external global variables GLREF_D The following extract from p_std.h declares operand types: #define VOID void typedef int INT,HANDLE;_ typedef unsigned int UINT;_ typedef char BYTE;_ typedef unsigned char UBYTE;_ typedef short int WORD;_ typedef unsigned short int UWORD;_ typedef long int LONG;_ typedef unsigned long int ULONG;_ typedef double DOUBLE; _ typedef char TEXT; where the meanings of BYTE, UBYTE, WORD, UWORD, INT, UINT, LONG, ULONG and DouBLE are as suggested by their names (and where a leading u means unsigned). By convention, we favour the signed variant in cases where it does not matter whether the signed or the unsigned variant is used. The Text type is used to indicate character data as in, for example: TEXT *str; where str points to a character string. The HanbLe typedef is used to refer to an instance of something - such as a process or a memory segment. In many cases, a HANDLE is actually the offset into the operating system's data space. 1-5 PLIB REFERENCE This manual uses the above declarations in function descriptions and examples so you do need to know about them to be able to understand this manual. However, this does not mean that you have to use our declarations. For example, using our declarations, you can write: #include GLDEF_C INT main(VOID) { p_printf ("Hello world"); p_getch (); return (0); } or, not using our declarations, you can write: #include int main (void) { p_printf ("Hello world"); p_getch (); return (0); } It is entirely up to you. Calling conventions The content of this section is quite technical. Provided you: e include plib.h e declare local functions before calling them ¢ use prototypes when calling your own global functions you don't need to be particularly aware of the calling convention used and you don't have to understand this section (although you may feel more comfortable if you do). The only exception is for functions which take as a parameter the address of (and subsequently call) a second function. In such a case you must declare an explicit calling convention for the second function. In PLIB this occurs when using p_enter, used to handle errors, or when using an object-oriented programming message-sending function such as p_send. In these two cases the descriptions of the relevant functions include full guidance. For a greater understanding of calling conventions and the #pragma cali declaration, refer to the TopSpeed documentation. With the TopSpeed C compiler you can change the calling convention using: #pragma save to save the current calling convention #pragma call to set a new current calling convention as defined by parameters that follow call #pragma restore to restore a previously saved calling convention You can also declare functions to use a stack-based calling convention using CDECL. The C header files containing the prototypes for the PLIB functions (and automatically included when you include plib.h) also contain #pragma statements that declare calling conventions on a function by function basis. For example, the prototype for p_bcpy is effectively: #pragma save #pragma call(reg_param =>(di,si,cx),reg_saved =>(bx,cx,dx,si,di,ds,st1,st2) ) GLREF_C UBYTE *p_bcpy(VOID *,VOID *,UINT); #pragma restore where the three parameters to p_bcpy are passed in the registers DI SI and CX as required by the BufferCopy interrupt (described in the EPOC O/S System Services reference manual). 1-6 1 INTRODUCTION When defining a sequence of prototypes, you only need to bracket the sequence with #pragma save and #pragma restore - not each individual prototype. Prototypes for PLIB functions that take a variable number of parameters must use a stack-based calling convention and are declared using cpEct as in, for example: GLREF_C INT CDECL p_iow(VOID *,INT,...); In many cases (where there is a limit to the number of parameters), the same function is also offered in fixed parameter versions as in, for example: INT p_iow2(VOID *pcb, UINT func); INT p_iow3(VOID *pcb, UINT func, VOID *al); INT p_iow4(VOID *pcb, UINT func, VOID *al, VOID *a2); where you can use the appropriate fixed parameter variant to take advantage of a more efficient register calling convention. When calling your own functions, you don't need to worry about setting a calling convention since the default calling convention will apply. The default (register) calling convention is: #pragma call(reg_saved =>(ax,bx,cx,dx,di,si,ds,stl,st2),reg_param => (ax, bx, cx, dx) ,c_conv=>off) When the compiler comes across a function that is declared as taking a variable number of arguments, such as: LOCAL_C VOID PrintToLog(TEXT *str, ...) it ignores the current calling convention and uses a stack-based calling convention. Small programs Because the body of nearly all PLIB functions is provided by code in the ROM, C programs built for EPOC are typically significantly smaller than, say, the standard C library on a PC. The difference is at its most extreme when there is such a small amount of program-specific code that the size of the program is dominated by the code that is brought in from the library. For example, the following program: #include GLDEF_C INT main(VOID) { p_printf ("Hello world"); p_getch (); return (0); } when compiled and linked for EPOC produces a program that contains fewer than 500 bytes of code. Depending on the compiler, upwards of 10K is typical on a PC. The amount of memory required to run an EPOC program (sometimes called the "working set") typically ranges from 10K to 100K bytes. For example, when using the Spreadsheet on the MC400 (a large program by any standard), you can load about 70K of code and open and manipulate a 10K spreadsheet with less than 100K of free system memory. In practice, it is quite possible to take advantage of the multi-tasking and run several programs at the same time and especially (given that the code is only loaded once) to run more than one process of the same program. The PLIB C startup modules When producing an executable using the linker, a C startup module is automatically linked in before the program-specific modules and the libraries. A number of C startup modules is supplied with PLIB where each module sets set up a different stack size (typically ranging from 2K to 8K). Except for the different stack size, the different PLIB startup modules are identical - they all declare the same basic structure for the process data segment - as described in the chapter Memory Allocation. The code in the PLIB startup modules is minimal - it just connects to the file server (using a Filconnect interrupt) before jumping to main (most applications need the services of the file server and the overhead to connecting to the file server is modest). PLIB REFERENCE Note that the PLIB startup modules do not set up the standard argv, argc parameters to main because PLIB is not particularly designed for command line user interfaces (although there is a mechanism for passing parameters when starting a process - see the chapter Processes and Inter-Process Messaging). Not all the stack that is declared in the PLIB startup module may be used by the program and you should subtract: 0x100 for any program 0x300 for programs that use the floating point emulator (as described in the Floating Point chapter) Related reference manuals The ROM contains more system code than is directly accessed by the functions described in this manual. In addition, a standard C library (CLIB) is provided. TopSpeed C library reference CLIB is a version of the TopSpeed C library for the EPOC operating system. The functions in CLIB are described in the TopSpeed C Library Reference manual. Additional notes, including a list of the TopSpeed C library functions that are not implemented, may be found in \sibosdk\doc\clib.doc. The EPOC version of the TopSpeed C library supports the ANSI functions and most of the portable functions that are commonly supported by MSDOS C libraries such as Microsoft C and Borland's Turbo C. The less portable functions such as those that access the BIOS and graphics functions are not included. The benefits of using CLIB are: e portability (existing C programs may easily be converted) e less to learn for programmers already familiar with standard C libraries Although the EPOC system services (and hence PLIB) has comprehensive support for floating point operations, it does not provide this support in a way that supports the floating point C as generated by the TopSpeed C compiler. This is described more fully in the chapter Floating Point in this manual. As you might expect, using CLIB in place of PLIB makes less efficient use of the SIBO architecture. In particular: e many of the EPOC system services are not available from CLIB (eg asynchronous I/O, inter- process messaging, the window server graphics functions) e executables are larger and the process takes a larger data segment The executables are larger because, although CLIB uses the ROM-based system services wherever possible and fares better than the PC library, it is still a much "thicker" library than PLIB. The data segments also tend to be larger because the various CLIB subsystems typically require large static buffers and tables. For example, the following CLIB program: #include int main (void) { printf ("Hello world"); getchar(); return (0); } when compiled and linked for EPOC produces a program that contains 6K bytes of code (as compared with 0.5K for the equivalent PLIB program). Unless you are using the in-built user interface object dynamic libraries (accessed using object-oriented programming) described below, you can freely mix PLIB calls with CLIB. We expect most experienced C programmers to use CLIB and regard PLIB and WLIB (the window server library, described below) as they would regard non-portable components of any C library. 1-8 1 INTRODUCTION It is worth converting completely to PLIB and WLIB when the desirability of making efficient use of memory outweighs the benefits of portability and familiarity. Window server reference The window server is a system process that provides shared access to the screen and keyboard (and also a pointing device, if present). The PLIB library contains only primitive console functions to input typed lines (with simple backspace editing) and to output lines of mono-spaced characters. The con: device driver provides row and column positioning and printing of mono-spaced characters. Although the PLIB console functions and the con: device driver ultimately call on the window server for both user input and screen drawing, the window server is capable of far more than can be accessed via these interfaces. In particular, the window server can be used to implement graphical user interfaces and to display bitmap images such as maps and diagrams. The WLIB library contains a set of C functions that can access all the services of the window server. These functions are described in the Window Server Reference manual. The window server supports the following features: e =ahierarchical system of overlapping windows where all drawing is clipped to visible areas e redraw events informing the client of areas of windows that need to be redrawn, with redrawing clipped to the invalid areas e =multi-font (proportional and mono-spaced) pixel addressable text drawing in a variety of text modes and styles e fast bitmap operations e drawing of lines, boxes and pattern-filled areas in a variety of modes ¢ optional double drawing to a background bitmap as well as the window such that the window server automatically redraws windows as necessary Like PLIB, the WLIB library is a library of thin C shell functions that contain software interrupts to ROM-based code. I/O devices reference This /O System chapter in this manual describes the EPOC I/O system in general and the PLIB C functions that are used to access I/O devices. A device driver may be built into the ROM or it may be loaded from an external source (such as an SSD). To use a particular device you need to read a description of the device driver. The files device driver and the asynchronous timer device driver are described in this manual - in the chapters Files and Time, Timers and Dates respectively. All other device drivers are described in the /O Devices Reference manual. The I/O Devices Reference manual describes device drivers that have been written by Psion - many of which are commonly supplied in the ROM. The device drivers described include the following: e Parallel port (PAR:) e = Serial port (TTY:) e Console device (CON:) e Sound driver (SND:) Descriptions of additional device drivers will be added to the I/O Devices Reference manual from time to time. EPOC O/S System Services reference manual The EPOC O/S System Services reference manual describes the software interrupt interface to the ROM- based system services. Nearly all of the services are available from C using the functions in PLIB. The CLIB library also uses the system services where possible (the source for CLIB may be found in \sibosdk\src). PLIB REFERENCE Because the EPOC O/S System Services reference manual is intended to be used in conjunction with the PLIB Reference manual, the descriptions in the EPOC O/S System Services reference manual are comparatively brief. You would refer to the EPOC O/S System Services reference manual if you were writing in 8086 assembler or accessing a system service from OPL. In this case, you should refer to the corresponding function in the PLIB manual for a fuller description of the service (there is a list of the corresponding PLIB functions in an appendix of the EPOC O/S System Services reference manual). Being able to refer to the description of a software interrupt can be useful when debugging C programs. The EPOC O/S System Services reference manual also contains information on: e writing device drivers e writing an installable file system e hardware interfacing Object dynamic libraries The Object-Oriented Programming chapter of this manual describes a set of functions that provide run- time support for object-oriented programming where object classes are constructed by: e using a proprietary tool to define class property structures and to declare methods e using regular TopSpeed C to implement the declared method functions for each class Object-oriented programming techniques are well-suited to implementation of graphic user interfaces and multi-threaded application control. The object dynamic libraries (DYLs) contain classes that may be used and/or subclassed to construct applications with a consistent graphical user interface. The classes supplied in the libraries include the following: e dynamic variable length arrays and large character buffers for building complex in-memory data structures e active objects that represent a variety of event sources for controlling multi-threaded programs e an extensive window class tree supporting such graphics user interface components as menus, dialog boxes and edit boxes The classes that are used to build user interfaces may vary for different SIBO machines. At the time of writing, a user interface object library had not been constructed for the HC range (since there is no requirement for a consistent user interface on a machine of this type). The object classes are organised into dynamic libraries (DYLs) in the ROM. For example, the MC400 has: OLIB.DYL containing classes that are independent of the user interface WIMP .DYL containing classes that implement the graphical user interface The object-oriented message passing mechanism also serves as a means of calling far code (such as the code in ROM-based DYLs). Large applications may be split into multiple DYLs to reduce their working set and to overcome the 64K code segment limit. The object classes are described in a manual per DYL. For example, the OLIB Reference Manual describes variable arrays and active objects. 1-10 CHAPTER 2 CHARACTERS, STRINGS AND BUFFERS General string and buffer functions PLIB contains the following general string and buffer functions: p_bcpy to copy a buffer p_slen returns the length of a string p_scpy, p_scpym to copy a string or multiple strings p_scat, p_scatm to concatenate a string or multiple strings p_brep, p_srep to fill a buffer or string with a repeated sequence p_bswap to swap the contents of two buffers p_bfil to fill a buffer with a repeated character p_jtob to left, right or centre align a buffer in a (normally wider) buffer with a fill character p_ere to generate the CRC number of a buffer p_bcpy Copy memory to memory UBYTE *p_bcpy(VOID *target, VOID *source, UINT len); Copy len bytes of data from source to target and return the address following the last byte written (ie targettlen). The data is copied correctly when source and target overlap. For example: p_bcpy (str+1,str,p_slen(str)+1); *str='A'; inserts 'A’ at the beginning of str. p_slen Return string length UINT p_slen(TEXT *str); Return the length of the zero terminated string str, not including the terminating zero. p_scpy Copy a string TEXT *p_scpy(TEXT *target, TEXT *source); Copy the zero terminated string source, producing a zero terminated string at target and return the address of the terminating zero of target. The strings should not overlap. For example: p_scpy (buf, "hello"); writes "hello" to buf. PLIB REFERENCE p_scpym Copy multiple strings TEXT *p_scpym(TEXT *target, ...); Copy and concatenate a list of zero terminated strings to target creating a zero terminated string at target. Returns the address of the zero that terminates the string at target. The first string is copied to target and the following strings are concatenated to it. The list of strings should be terminated by a NULL argument. For example: p_scpym(buf,"The cat"," jumped", NULL) ; writes "The cat jumped" to buf. p_scat Concatenate two strings TEXT *p_scat (TEXT *lstr, TEXT *rstr); Concatenate the zero terminated string rst r to the zero terminated string 1str and return the address of the zero that terminates the new string at 1str. For example: p_scpy (buf, "hello"); p_scat (buf," fred"); writes "hello fred" to buf. p_scatm Concatenate many strings TEXT *p_scatm(TEXT *lstr, ...); Concatenate a list of zero terminated strings to the zero terminated string 1str. Returns the address of the zero that terminates the new string at 1str. The list of strings should be terminated by a NULL argument. For example: p_scpy (buf, "The"); p_scatm(buf," cat"," jumped", NULL) ; writes "The cat jumped" to buf. p_brep Replicate a buffer UBYTE *p_brep(VOID *buf, INT buf_len, VOID *pattern, INT pat_len); Replicate pattern as many times as necessary to exactly fill the buffer but of length buf_1en and return the address of the byte following the last byte written (ie buf+buf_len). If buf_1lenrbuf then return is greater than 0 If lbuf==rbuf then return equals 0 2-8 2 CHARACTERS, STRINGS AND BUFFERS The result of the comparison is based on the difference of the first two unsigned bytes to disagree. The strings are equal if they have the same length and content. Where two strings have different lengths and the shorter string matches the first part of the longer string, the shorter string is considered to be less than the longer string. For example: p_bemp ("abc", 3, "abcd", 4) returns less than 0 p_bemp ("abcd", 4, "abc", 3) returns greater than 0 p_bemp ("abc", 3, "abc", 3) returns 0. p_scmp Compare two strings INT p_scmp(TEXT *lstr, TEXT *rstr); Compare two zero terminated strings by comparing corresponding characters, returning 1str-rstr, that is: If 1strrstr then return is greater than 0 If 1str==rstr then return equals 0 The result of the comparison is based on the difference between the first two characters to disagree. The strings are equal if they have the same length and content. For example: p_scmp ("abc", "abcd") returns less than 0 p_scmp ("abcd", "abc") returns greater than 0 p_scmp ("abc", "abc") returns 0 p_bcmpi Case independent buffer compare INT p_bcempi(TEXT *lbuf, INT lbuf_len, TEXT *rbuf, INT rbuf_len); Performs a case independent comparison of the two buffers by effectively folding the characters in both buffers before comparing them using p_bcmp. Returns as for p_bcmp, described above. For example: p_bempi ("abc", 3, "abcd", 4) returns less than 0 p_bempi ("abcd", 4, "abc", 3) returns greater than 0 p_bempi ("ABC", 3, "abc", 3) returns 0 p_scmpi Case independent string compare INT p_scmpi(TEXT *lstr, TEXT *rstr); Performs a case independent comparison of the two zero terminated strings 1str and rstr by effectively folding the characters in each string before comparing them using p_scmp. Returns as for p_scmp, described above. For example: p_scmpi("abc", "abcd") returns less than 0 p_scmpi ("abcd", "abc") returns greater than 0 p_scmpi("ABC", "abc") returns 0 2-9 PLIB REFERENCE String searching The PLIB string searching functions are: p_bloc, p_sloc, to search for a character p_bloci, p_sloci, p_slocr, p_slocri p_bsub, p_ssub, to search for a sequence of characters p_bsubi, p_ssubi p_bmatch, p_smatch, to search for a sequence of characters that matches a wildcard specification p_bmatchi, p_smatchi p_bloc Locate byte in buffer INT p_bloc(VOID *buf, INT buf_len, INT ch); Locate the byte ch in the buffer at buf of length buf_1en returning the index of the first matching byte or -1 if ch is not in the buffer. For example: p_bloc("abcde",5,'£') returns -1 p_bloc("abcde",5,'a') returns 0 p_bloc("abcde",5,'c') returns 2 p_sloc Locate character in string INT p_sloc(TEXT *str, INT ch); Locate the first occurrence of the character ch in the zero terminated string str, returning the index of the first matching character or -1 if ch is not in str. For example: p_sloc("abcde", '£') returns -1 p_sloc("abcde", 'a') returns 0 p_sloc("abcde",'c') returns 2 p_bloci Case independent locate character in buffer INT p_bloci(TEXT *buf, INT buf_len, INT ch); Perform a case independent locate of ch in the buffer buf by effectively folding ch and the characters in buf before using p_bloc to locate the folded character. Returns as for p_bloc, described above. For example: p_bloci ("abcde",5,'f£') returns -1 p_bloci ("abcde",5,'A') returns 0 p_bloci ("abcde",5,'c') returns 2 p_sloci Case independent locate character in string INT p_sloci(TEXT *str, INT ch); Perform a case independent locate of ch in the zero terminated string str by effectively folding ch and the characters in str before using p_sloc to locate the folded character. Returns as for p_sloc, described above. For example: p_sloci ("abcde", '£') returns -1 p_sloci ("abcde", 'a') returns 0 p_sloci ("abcde", 'c') returns 2 2-10 2 CHARACTERS, STRINGS AND BUFFERS p_slocr Locate last matching character in a string INT p_slocr(TEXT *str, INT ch); Locate the last occurrence of the character ch in the zero terminated string str, returning the index of the matching character or -1 if ch is not in str. For example: p_slocr("abcabc", 'A') returns -1 p_slocr ("abcabc", 'a') returns 3 p_slocri Locate last matching folded character in a string INT p_slocri(TEXT *str, INT ch); Locate the last occurrence of ch in the zero terminated string str by effectively folding ch and the characters in str before using p_slocr to locate the folded character. Returns as for p_siocr, described above. For example: p_slocri("abcde", 'f£') returns -1 p_slocri("abcabc", 'A') returns 3 p_bsub Locate sub-buffer in buffer INT p_bsub(VOID *buf, INT buf_len, VOID *sbuf, INT sbuf_len); Locate sub-buffer sbuf, sbuf_len In buf, buf_len returning the index of the start of sbuf in buf or -1 if sbuf does not exist in but (which must be the case if buf_len0 *pmatch belongs after record number *pmid If two or more array elements exactly match the record at pmatch, the return value will be zero and *pmia will contain the index of any one of the matching elements. Each time p_bsrch needs to compare a record with the record at pmatch it calls: compf (n, pmatch) ; where n is the index of the record to be compared. This user-supplied comparison routine should return: 0 *pmatch 1s equal to record number n <0 *pmatch is before record number n >0 *pmatch is after record number n As with any binary search, the array must be ordered. For p_bsrch, it should be ordered with respect to compf, the user-supplied comparison routine. In most cases the set of records will be a fixed array, but any arrangement which allows the routine to identify a record by index (e.g. hashing) will be suitable. Likewise, pmatch may be any address suitable for interpretation by compf, since pmatch is not used inside p_bsrch, other than being passed to compf. This allows code using p_bsrch to be re-entrant. If the whole table is created before being searched it is, in general, quicker to build the table unordered and then sort it (see p_qsort) than to build the table in sequence by insertion. 3-1 PLIB REFERENCE Example LOCAL_D INT array[]={5,8,13,19,25,30,41,48,51, 62,70, 76, 80, 90, 98}; LOCAL_C intcompare (INT n,INT *pmatch) { INT f£,m; f=array[n]; m=*pmatch; if (f==m) return (0); return (m>f£?1:-1); } VOID find(INT match) { INT nrec; /* number of INTs in the array */ INT result; INT mid; TEXT *pstr; nrec=sizeof (array) /sizeof (INT) , result=p_bsrch(nrec, (INT (*) ()) intcompare, &mid, &match) ; if (!result) p_printf("sd matches record number %d",match,mid) ; else { pstr=result<0?"before": "after"; p_printf("%d belongs %s %d",match,pstr,array[mid]); } p_qsort Sort an array of records INT p_qsort (INT nrec, INT (*ordf)(), VOID (*excf) (), UBYTE *base) Sort a set of records into ascending order, using the quicksort algorithm. There are nrec records to sort. Each time the routine needs to compare two of these records it calls: (*ordf) (n,m,base) ; where n and mare the indexes of the two records to compare (starting at zero), and base is a parameter that may be used or ignored by the ordering function. The ordering function, ordf, should return: 0 record number n is equal to record number m <0 record number n is less than record number m >0 record number n is greater than record number m Each time the routine needs to exchange two records it calls: (*excf) (n,m, base) ; where n and mare the indexes (starting at zero) of the two records to exchange. The user-supplied exchange routine should exchange the records indexed by n and m. Again, the user-supplied exchange function is free to use or ignore base, but its use should be consistent between the ordering and exchange functions. Normally, base will represent the address of a fixed length array, but any method of storing records may be used, as long as the records can be accessed using the indices. The value of base is not used inside p_gqsort, but is passed down to both the ordering and exchange functions to enable the writing of re- entrant code that uses p_qsort. The routine uses its own stack, declared locally, to avoid the need to be called recursively. This stack is 150*sizeof (INT) in length and, as such, could cause the main stack to overflow if the routine is called from too deep inside a program. The function p_gsort returns zero if successful, else a negative error. 3-2 3 ARRAYS AND QUEUES An error return value of £_GEN_FaIL is returned if the internal stack overflows. This is, however, unlikely as an experiment to sort 50000 elements used only 80 stack elements. If this error is returned, the routine can be called again, as it is likely that the array has been rearranged enough to permit the successful completion of a second attempt. Example LOCAL _D INT array[]={98,90,80,76,70,62,51,48,41,30,25,19,13,8,5}; LOCAL_C intcompare(INT first,INI second, INT *array) { INT £,s; f=* (array+first); s=* (array+second) ; if (s==f) return (0); return (f>s?1:-1); } LOCAL_C VOID intexchange(INT first,INT second, INT *array) { INT r; r=* (arrayt+first) ; * (array+first) =* (array+second) ; * (array+second) =r; } LOCAL_C VOID sort (VOID) { INT n; /* number of INTs in the array */ n=sizeof (array) /sizeof (INT); if (p_qsort (n,intcompare, intexchange, &array[0]) ) p_panic("Too many partitions"); Doubly linked queues This section describes functions for inserting and deleting entries from doubly linked queues. Each entry in the queue contains a P_guz structure, defined in p_que.h as: typedef struct p_que { struct p_que *next; /* pointer to next item */ struct p_que *prev; /* pointer to previous item */ } P_QUE; A special header entry consisting only of a p_ouz data structure provides a single address by which the queue may be accessed. The empty queue consists only of the p_quz header with both next and prev pointing to itself. A queue with n entries contains n+1 P_que structures - one for the header and one for each entry. The queue is built such that the next pointer of the last entry points to the header and the prev pointer of the header points to the last entry such that the n+1 p_oue structures form a doubly linked circular queue. The following extract from p_que.h: #define P_INITQ(q) (q)-—>next=(q) ->prev=(q) #define P_DECLAREQ(q) P_QUE q = {&q,&q} #define P_ISEMPTYQ(q) ((q)==(q) ->next) defines 3 macros where: P_INITO may be used to initialise a header that represents an empty queue P_DECLAREQ may be used to declare a header that represents an empty queue P_ISEMPTYQ evaluates to TRUE if the queue header represents an empty queue 3-3 PLIB REFERENCE Entries are inserted into a queue using p_enque and removed using p_deque. Neither of these functions set aside memory for entries or free memory - they merely make and break the links between entries. The following example illustrates the use of p_enque and p_deque to set up a queue of zero terminated strings that are allocated and freed from the heap (see the chapter Memory Allocation for a description of f_alloc and p_free). #include #include typedef struct P_QUE pid; TEXT name[1]; } QUEUE_ENTRY; LOCAL_D P_DECLAREQ (headq) ; GLDEF_C VOID AddNameToEnd (TEXT *name) QUEUE_ENTRY *p; p=f_alloc(p_slen (name) +sizeof (QUEUE_ENTRY) ) ; p_scpy (&p->name[0],name) ; p_enque (&p->piq, &headq) ; } GLDEF_C TEXT *FirstName (VOID) { TEXT *name; name=é& (((QUEUE_ENTRY *)headq.next)—->name[0]); if (P_ISEMPTYQ (&headq) ) name=NULL; return (name) ; } GLDEF_C VOID DeleteFirstName (VOID) { QUEUE_ENTRY *p; p=(QUEUE_ENTRY *) headq.next; p_deque (&p->pig) ; p_free(p); } Names are allocated and added to the end of the queue using AddNameToEnd. The code that processes the items in the queue uses FirstName to get the first item in the queue (which returns NuLt if the queue is empty). After processing the first name, calling DeleterirstName removes it from the queue and frees the memory used by it. p_enque Add entry to queue VOID p_enque(P_QUE *pNew, P_QUE *pEntry) ; Insert entry pNew before pEnt ry (ie between pEnt ry->prev and pEnt ry) into the doubly linked queue that contains pEntry. If P_QUE hdq is the queue header: p_enque (pNew, &hdq) adds pNew to the end of the queue (since queues are circular the previous entry to the header is the entry at the end of the queue) p_enque (pNew,hdq.next) adds pNew to the start of the queue 3 ARRAYS AND QUEUES p_deque Remove entry from queue VOID p_deque(P_QUE *pEntry); Remove queue entry pEnt ry by linking the entries on either side of pEntry to each other, excluding pEntry from the queue. If P_QUE hdg is the queue header: p_deque (hdq.next) removes the first entry from the queue p_deque (hdq. prev) removes the last entry from the queue (since queues are circular the previous entry to the header is the entry at the end of the queue) If hdg is empty then p_deque (&hdq) will have no effect. Calling p_deque (ahdq) of a non-empty queue is not a good idea as there will then be no way to get into the queue. EEE Delta queues A delta queue builds on doubly linked queues, described in the previous section, to store entries ordered on a tone key. A delta queue consists of a p_oquz header and doubly linked entries, each containing a p_pELTA structure, defined in p_que.h as: typedef struct { P_QUE q; LONG key; /* Delta key */ } P_DELTA; where (except for the first entry in the queue) key contains the offset relative to the previous entry (the delta). The value of an entry's key is determined by accumulating the deltas of its predecessors. For the first entry, the key is just the delta. The EPOC operating system uses a delta queue to implement timers. Each entry represents a timer where the key is the relative time in system ticks to the expiry of that timer and the delta is then the number of system ticks after its predecessor. This design minimises the system effort to maintain the timers - on each tick the system only has to decrement the head of the queue. See the chapters Asynchronous Requests and Semaphores and Time, Timers and Dates for more about the time delta queue and timers. A delta list is set up much as a regular queue. A header is declared and initialised using (p_1INTTQ or P_DECLAREQ) giving an empty queue. Entries containing a p_pELTAa structure are added to the delta queue using p_enqued and are removed using p_dequed. Neither function allocates or frees memory for entries - all they do is maintain the links and calculate the deltas. p_enqued Add entry to delta queue P_DELTA *p_enqued(P_QUE *pHead, P_DELTA *pEntry, LONG key); Inserts entry pEnt ry into the delta queue headed by pHeaa according to the key key and returns the address of the first entry in the queue. The queue is scanned, accumulating the key from the deltas, until an entry is found with a key that is greater than key. The new entry pEntry is inserted (with an appropriate delta) before that entry and the delta of that entry is recalculated. 3-5 PLIB REFERENCE p_dequed Remove entry from delta queue P_DELTA *p_dequed(P_QUE *pHead, P_DELTA *pEntry); Remove pEnt ry from the delta queue headed by pHead and return the address of the first entry or NULL if p_dequed leaves the queue empty. The delta of any following entry is updated to keep the accumulated key of each remaining entry constant. If P_QUE hdgq is the queue header: p_dequed(&hdq, (P_DELTA *)hdq.next); removes the first entry from the queue. CHAPTER 4 INTEGER CONVERSION AND RECTANGLE FUNCTIONS This chapter describes functions for converting integer numbers to a textual representation (eg to print a number) and vice versa (eg to input a number). The chapter ends with a section that describes a set of functions that operate on rectangle data structures. These are useful, for example, when organizing a screen display. Converting integers to text PLIB contains the following functions to convert various types of integers to a textual representation: p_itob to convert an INT to a signed decimal number p_ltob to convert a Lonc to a signed decimal number p_gtob to convert a UINT to an unsigned number in any radix p_gltob to convert a ULONG to an unsigned number in any radix p_atob, p_atos for general purpose conversion and formatting of multiple arguments The functions that handle "any radix" are typically used to handle a radix of 2 (binary), 8 (octal), 10 (decimal) or 16 (hexadecimal). Note that it is up to the caller to ensure that there is sufficient space in the target buffers for the output of the conversion. p_itob Convert an INT to decimal buffer UINT p_itob(TEXT *buf, INT value); Write a signed decimal representation of value to buf and return the number of characters written. If value is negative, a leading '-' is written. For example: buf [p_itob (&buf[0],-24) ]="\0"'; writes "-24" to buf. PLIB REFERENCE p_ltob Convert a LONG to decimal buffer INT p_ltob(TEXT *buf, LONG value); Write a signed decimal representation of the LoNG value to buf and return the number of characters written. If value is negative, a leading '-' is written. For example: buf [p_ltob (&buf[0],-240000L) ]="\0'; writes "-240000" to buf. p_gtob Convert a UINT to buffer any radix INT p_gtob(TEXT *buf, UINT value, INT radix); Write an unsigned base radix representation of value to buf and return the number of characters written. For example: buf [p_gtob (buf, 0xaa55,16) ]='"\0'; writes "AA55" to buf. p_gltob Convert a ULONG to buffer any radix INT p_gltob(TEXT *buf, ULONG value, INT radix); Write an unsigned base radix representation of the ULONG value to buf and return the number of characters written. For example: buf [p_gltob (buf, 0xaa5577,16)]='\0'; writes "AA5577" to buf. p_atob Convert multiple arguments to buffer INT p_atob(TEXT *buf, TEXT *fstr, VOID *parg); Write formatted text to buf as controlled by the format zero terminated string fstr and the argument list parg and return the number of characters written. The format string fstr contains literal text, embedded with commands for converting the arguments at parg. The embedded commands are prefixed with the '%' character (two successive '%' characters count as one literal '%'). The literal text is simply copied to buf and the % commands convert successive arguments (which may be integers, longs or strings) at parg. An embedded command takes one of the following forms: 3 for output (with no padding) of the converted data type that is one of b, c, d, f, m, 0, s, u, w or x (as described below). Where appropriate, the type may be widened to a long by preceding the type letter with an | or an L or by providing the type letter in upper case. % for right-aligned space-filled output in width where is either a positive decimal number or a * to take the width as a u1nt from the argument list. If more than characters is generated by the conversion, the output is truncated. %0 for right-aligned zero-filled output in width . % for left, right or centre aligned output in width with fill character <£> where is either -, + or =. If <£> is a *, the code of the fill character is taken as a UINT from the argument list. (If you want to fill with «'s, you have to supply it through the argument list). 4-2 4 INTEGER CONVERSION AND RECTANGLE FUNCTIONS A common requirement is for space-filled output. It is therefore worth enumerating special cases, using the last of the above four forms of embedded command. Note that, in all cases, there is a space (the fill character) immediately preceding . %- for left-aligned space-filled output in width %+ for right-aligned space-filled output in width %= for centre-aligned space-filled output in width The specifies the type of argument conversion to be performed, as follows: b convert the urnt to a binary text representation c convert the urnt to a single character corresponding to its code d convert the mnt to a signed decimal text representation f just output fill characters (does not use up an argument) m_ convert the urnt to a two byte binary numeric representation, with the most significant byte first (only available in EPOC version 2.17 or later) o convert the urnT to an octal text representation s copy the TExT * zero terminated string to the output, excluding the terminating zero. u convert the urnT to an unsigned decimal text representation w_ convert the urnt to a two byte binary numeric representation, with the least significant byte first (only available in EPOC version 2.17 or later) xX convert the urnT to a hexadecimal text representation The type may be widened to a long by preceding the type letter with an | or an L or by providing the type letter in upper case (ignored if is s or f). Output for types m and w will then occupy four bytes. This function is normally used indirectly by the more immediately useful p_atos, described below. However, p_atob is useful for constructing p_print£-like text output functions (p_print¢é itself is described in the chapter I/O System). For example, if file is static variable that contains the channel of an opened text file, the following function behaves like p_printéf. GLDEF_C CDECL VOID PrintToFile (TEXT *fmt,UINT arg,...) { UINT len; UBYTE buf[256]; len=p_atob (&buf[0],fmt,&arg); p_write (file, &buf[0],len); } If the int variable a contains 65: PrintToFile("[%b %c $d %o %u %x]",a,a,a,a,a,a) writes [1000001 A 65 101 65 41] PrintToFile("[%04x]",a) Writes [0041] PrintToFile("[%*x]",3,a) Writes [ 41] PrintToFile("[%+$4d.00 %s]",a,"over") writes [$$65.00 over] PrintToFile("[%0*s]",10,"fred") writes [0O00000fred] PrintToFile("[%=*4x]",'*',a) writes [*41*] PrintToFile("[%—-**d]",'.',10,a) writes [S564 s es ee ] PrintToFile("[%-A4f]",a) writes [AaaAA] and makes no use of the value of a. 4-3 PLIB REFERENCE p_atos Convert multiple arguments to string VOID p_atos(TEXT *str, TEXT *fstr, ...); Convert multiple arguments to a zero terminated string at str under control of the format string fstr. The content of fstr is described under p_atob, above. For example, if the variable a is a urnT that contains 65: p_atos(str,"%b %c %d %o %u %x",a,a,a,a,a,a) Writes "1000001 A 65 101 65 41"tostr p_atos(str,"%04x",a) writes "0041" to str p_atos(str,"%*x",2,a) writes "41" to str Converting text to integers PLIB contains the following functions to convert text to various types of numbers: p_stoi to convert a signed decimal number string to a worD p_stol to convert a signed decimal number string to a LoNG p_stog to convert an unsigned number in any radix to a UwoRD p_stogl to convert an unsigned number in any radix to a ULONG p_stoa to convert multiple fields in a string to a series of arguments p_stoi Convert a signed decimal string to a WORD INT p_stoi(TEXT **pstr, WORD *pval); .Attempt to convert the signed decimal string at *pstr to a 16 bit number and, if a valid number is recognised, write the number to pval, update *pstr to point to the terminating character and return zero. Otherwise, neither *pstr nor *pval is written to and the function returns one of the following negative error numbers (defined in p_gen.h): E_GEN_OVER the number is too large (greater than 32767 or less than -32768) E_GEN_FAIL the text could not be recognised as a number Conversion continues until a non-decimal digit is found in the string, or the number overflows. For a number to be recognised, the string must contain at least one decimal digit. The string may be preceded by a '-' or a '+' (whitespace is significant and will terminate the conversion but leading zeros are ignored). To convert unsigned data up to 65535, use p_stog. For example, after: INT ret; WORD val; TEXT *ptr="-123abc"; ret=p_stoi(&ptr, &val) ; val contains -123, ptr points to the 'a' and ret is zero. p_stol Convert a signed decimal string to a LONG INT p_stol(TEXT **pstr, LONG *pval); Converts the signed decimal string at *pstr to write a 32 bit number to pval. Behaves and returns as for p_stoi above except that it can handle decimal numbers in the range -2147483648 to 2147483647 inclusive. To convert unsigned data up to 4294967295, use p_stogl For example, after: INT ret; LONG val; TEXT *ptr="-123456abc"; ret=p_stol (&ptr, &val) ; val contains -123456, ptr points to the 'a' and ret is zero. 4-4 4 INTEGER CONVERSION AND RECTANGLE FUNCTIONS p_stog Convert an unsigned number in any radix to a UWORD INT p_stog(TEXT **pstr, UWORD *pval, INT radix); Attempt to convert the unsigned number base radix (typically 2, 8, 10 or 16) at *pstr to a 16 bit number and, if a valid number is recognised, write the number to pval, update *pstr to point to the terminating character and return zero. Otherwise, neither *pstr nor *pval is written to and the function returns one of the following negative error numbers (defined in p_gen.h): E_GEN_OVER the number is too large (greater than 65535) E_GEN_FAIL the text could not be recognised as a number Conversion continues until a character that is invalid for the radix is found in the string or the number overflows. For a number to be recognised, the string must contain at least one digit in the radix. Whitespace is significant and will terminate the conversion but leading zeros are ignored. For example, given: UWORD val TEXT *ptr="f£123"; p_stog(&ptr,&val,10) returns E_GEN_FAIL p_stog(&ptr, &val,16) returns O and writes Oxf123 to va1 p_stogl Convert an unsigned number in any radix to a ULONG INT p_stogl (TEXT **pstr, ULONG *pval, INT radix); Converts the unsigned decimal number base radix at *pstr to write a 32 bit number to pvai. Behaves and returns as for p_stog above except that it can handle numbers up to 4294967295. For example, after: INT ret; ULONG val; TEXT *ptr="f£123abz"; ret=p_stogl (&ptr, &val,16); val contains Oxf123ab, ptr points to the 'z' and ret is zero. p_stoa Convert a string to arguments INT p_stoa(TEXT **pstr, TEXT *fstr, ...) Convert multiple fields within the zero terminated string *pstr to a series of arguments ... as controlled by the format zero terminated string fstr. Returns zero if successful and the text pointer *pstr is updated to point to the terminating character of the last field converted. If an error occurred, *pstr points to the start of the field that caused the error and one of the following negative error numbers is returned: E_GEN_OVER the result is too large E_GEN_FAIL fails to recognise a number E_GEN_ARG the supplied buffer does not contain enough items The format string fstr contains literal text, embedded with commands for converting the fields in *pstr to the passed arguments. Any excess leading whitespace (as defined by p_iswhite) before a field in *pstr is automatically skipped. The embedded commands in fstr are prefixed with the 's' character (two successive 's' characters count as one literal ''). Any non whitespace literal text causes characters in the *pstr to be scanned until a match is made or until the end of the string is encountered (any whitespace characters in fstr are discarded). If a match is not found in *pstr, the function returns. 4-5 PLIB REFERENCE The general form of an embedded command is: %&[*] [] [] :=a positive decimal number :1|L :=(B|b|c|c|D|d|N|n]|o]olalq|s|s|u]ulx|x) where the square brackets indicate optional fields and '|' separates choices. The mandatory parameter (optionally qualified by <1ong>) indicates the data type to be converted. If the asterisk is present, conversion is performed but the result is not be stored. There should be no corresponding value pointer in the argument list for a suppressed conversion. The parameter is a positive decimal number that specifies the maximum input field width when is s or q (in either upper or lower case) - if is other than s or q, it is ignored. Note that the corresponding storage buffer must be large enough to hold characters plus | more for the terminating zero (eg %19s requires a buffer of 20 bytes). The specifies the type of argument conversion to be performed, as follows: b convert an unsigned binary number to the uworp c convert a character to its code, written to the uworD d convert a signed decimal number (optionally preceded by a '-' or a '+') to the worp n write the number of characters consumed so far to the uworD oO convert an unsigned octal number to the uworp q convert quote delimited text, copying the data in between the quotes (the delimiting quotes are discarded) to produce a zero terminated string at the TExt * buffer. Although often used to convert text delimited by quote (") characters, the first non whitespace character encountered is taken to be the delimiter. Ss convert contiguous non whitespace text to a zero terminated string at the Text * buffer. The string is determined from the first non whitespace character to the first whitespace character (or a zero terminator). u convert an unsigned decimal number to the uworp X convert an unsigned hexadecimal number to the uworp When performing a numeric conversion, for a number to be recognised the string must contain at least one digit in the radix. If <1ong> is present, it should be L or | to specify that the corresponding argument address points to either a LONG or a ULONG (ignored if is s, f or q). A long parameter can also be indicated by specifying the conversion type in upper case. For example, after: INT ret; WORD dl1,d2; TEXT *ptr="111,-33"; ret=p_stoa(&ptr,"%b, 3d", &d1, &d2) ; di is 7 and d2 is -33, ptr points to the terminating zero, ret is zero. After: INT ret; WORD dl,d2; TEXT buf[16] ptr="xxx @a “def ghi®* #44a"; ret=p_stoa(ptr,"@ Sc %15q # %d",&d1,buf, &d2) ; di contains 'a', "def ghi" is written to buf and d2 is 44, ptr points to 'a' and ret is zero. 4-6 4 INTEGER CONVERSION AND RECTANGLE FUNCTIONS ——————————————————————————————————————————————————————————————————————————————————————— Rectangle functions This section describes a set of functions that operate on p_reEct structs. A p_recT struct describes a rectangle in terms of its: e top left coordinates (internal) e bottom right coordinates (external) where the units of the coordinates depend upon the application. Typically, the coordinates count pixels (for graphics displays) or monospaced character columns and rows (for character-oriented displays). For example, a character display would normally be mapped to an (x,y) coordinate system as follows: e corresponds to the character in the top left corner e x increases to the right and counts the character columns e yincreases downwards and counts character rows Using the above coordinates, the rectangle of as in the following character display: +++++4+4+ ++AAA+++ ++AAA+++ +4+4+4+4+Z4+ is described by the coordinates of the top left a (internal) and the bottom right z (external) - that is, (2,1) and (5,3). Subtracting corresponding coordinates gives the correct dimensions of the rectangle - (3,2). The p_rect struct is defined in terms of two p_potnt structs. The definitions, in p_graf-h, are: typedef struct { WORD x; /* Horizontal coordinate */ WORD y; /* Vertical coordinate */ } P_POINT;. typedef struct { P_POINT tl; /* Top left point (internal) */ P_POINT br; /* Bottom right point (external) */ } P_RECT;. An empty rectangle is a rectangle that has one or both of its sides zero or negative. The rectangle functions are as follows: p_offrec moves a rectangle by an offset p_insrec shrinks or expands a rectangle about its centre p_unirec calculates the union of two rectangles (the smallest rectangle that encloses both of them) p_intrec calculates the intersection of two rectangles p_pinrec determines whether a point is inside a rectangle p_emprec determines whether a rectangle is empty p_absrec converts any negative sides of a rectangle to their positive equivalents p_offrec Offset a rectangle VOID p_offrec(P_RECT *rect, INT xoffset, INT yoffset); Displace rect by (xoffset, yoffset), without changing its size. PLIB REFERENCE p_insrec Inset a rectangle VOID p_insrec(P_RECT *rect, INT xinset, INT yinset) ; Adjust the width and height of rect by xinset and yinset respectively, in such a way as to produce a rectangle concentric with the original. A negative inset makes the rectangle bigger. For example: p_insrec(&rect,2,-1); decrease the width of rect by 4 and increases the height by 2. p_unirec Union of two rectangles VOID p_unirec(P_RECT *rectl, P_RECT *rect2, P_RECT *result); Write the union of the two rectangles rect1 and rect 2 (the smallest rectangle that encloses both of them) to result - as illustrated by the following diagram: Result The parameter result may point to the same address as either rect 1 or rect 2. For example: LOCAL_D P_RECT rect1={{10,20},{30,40}}; LOCAL_D P_RECT rect2={ {50,50}, {100,120}}; LOCAL_D P_RECT result; p_unirec(&rectl, &rect2, &result) ; writes {{10,20},{100,120}} to result. p_intrec Intersection of two rectangles INT p_intrec(P_RECT *rectl, P_RECT *rect2, P_RECT *result); Write the intersection of the two rectangles rect 1 and rect2 (the largest rectangle that is contained in both of them) to result. Rect 1 Rect 2 Intersection If the rectangles do not intersect or if either rectangle is empty, the result is an empty rectangle. The parameter result may point to the same address as either rect 1 or rect 2. Returns TRUE if the rectangles intersect, raLsE if the rectangles do not intersect (or if either rectangle is empty). 4-8 4 INTEGER CONVERSION AND RECTANGLE FUNCTIONS For example, after: LOCAL_D P_RECT rect1={{0,0},{7,20}}; LOCAL_D P_RECT rect2={{4,4},{100,120}}; LOCAL_D P_RECT result; ret=p_unirec (&rectl, &rect2,é&result) ; ret 1S TRUE and result contains {{4,4},{7,20}}. p_pinrec Test if a point is inside a rectangle INT p_pinrec(P_POINT *point, P_RECT *rect); Return true if the point point is within the rectangle rect or FALSE if point is outside rect (or if rect is empty). p_emprec Test if a rectangle is empty INT p_emprec(P_RECT *rect); Return True if the rectangle rect is empty (that is, if it has zero or negative width or height). p_absrec Convert to an absolute rectangle VOID p_absrec(P_RECT *rect, P_RECT *result); Write the absolute of the rectangle rect (where any negative sides are converted to their positive equivalents) to result. The parameters rect and result may point to the same address. For example: LOCAL_D P_RECT rect={{7,20},{4,4}}; LOCAL_D P_RECT result; p_absrec(&rect, &result) ; writes {{4,4},{7,20}} tO result. 4-9 CHAPTER 5 FLOATING POINT Floating point C The 8087 emulator The code that is generated from floating point C requires that the 8087 floating point coprocessor emulator be present and loaded. The 8087 emulator is implemented as an external logical device driver (LDD), loaded from sys$8087.ldd. The PLIB (or CLIB) startup module (that is, the code which calls main) automatically loads sys$8087./dd if the application program contains any floating point code. The startup module searches for sys$8087.ldd in the following directories (in order of precedence): e as specified by the zero terminated string in the environment variable with name "Ems" (if such an environment variable exists) e in the same directory that contained the program being executed The startup module will fail with panic 80 the search for sys$8087.1dd fails. See the chapter on Error Handling for an explanation of the panic mechanism. The Ems environment variable may be set using p_setenv - as described in the chapter Memory Allocation. For example, the following installation program causes the startup module to look for sys$8087.ldd in the a:\sys\ directory: #include GLDEF_C main(VOID) { p_setenv ("EMS", "A:\\SYS\\") ; } Note that environment variables names are case-sensitive - setting up (say) "ems" will not have the desired effect. Only one copy of sys$8087.ldd is loaded however many floating point processes are started. The LDD is deleted (freeing the memory) when the last floating point process exits gracefully. If the last process panics or is stopped by another process, the LDD will remain loaded - but it will be deleted by a subsequent normal exit of a floating point process. The LDD has the device name Ems, so you can find out if the device is loaded using: LOCAL_C INT Is8087Loaded (VOID) { TEXT bb [E_MAX_NAME+2]; return (p_devfnd(0, "EM$",E_LDD, &bb[0])>0); } which returns TRUE if the LDD is loaded. If required, a clean-up program can delete the loaded LDD with: p_devdel ("EM$",E_LDD) ; which will only successfully delete the LDD if there are no floating point processes using it. The functions p_devfnd and p_devdel are described along with LDDs in the //O System chapter. PLIB REFERENCE If sys$8087.ldd is not present in the ROM, it must be loaded into RAM at a cost of approximately 8K bytes (but this does not detract from the code segment limit of the application). If the LDD is present in the ROM, it still has to be loaded but at a reduced cost of RAM. You can find out if sys$8087.1dd is present in the ROM using: LOCAL_C INT Is8087InROM (VOID) { P_INFO info; return (p_finfo("ROM: :SYS$8087.LDD", &info) >=0) ; } which returns TRUE if sys$8087./dd is present in the ROM. Avoiding the 8087 emulator It is possible to perform floating point operations without using floating point C and without loading the emulator. For example, a program that contains the following function (to evaluate sin(x)/x): LOCAL_C INT Sinc(DOUBLE *pret,DOUBLE *parg) { if (*parg==0.0) { *pret=1.0; return (0); } if ((ret=p_sin(pret,parg) ) <0) return (ret); *pret=*pret/*parg; return (0); } will load the emulator because the expressions *parg==0.0, *pret=1.0 and *pret=*pret/*parg all generate calls to the emulator. However, if the function is implemented without using floating point C as in: LOCAL_C INT Sinc(DOUBLE *pret,DOUBLE *parg) { WORD x; DOUBLE zero; x=0; p_itof (&zero, &x) ; if (!p_fcmp(&zero,parg) ) { x=1; p_itof (pret, &x); return(0); } if ((ret=p_sin(pret,parg) ) <0) return (ret); p_fdiv(pret,parg) ; return (0); } then sys$8087./dd is not loaded. Furthermore, the code that is generated is smaller and runs faster. The benefits of avoiding the emulator are: ¢ you do not need to worry about the presence of sys$8087.ldd e the program is smaller and runs faster The benefits of using the emulator are: e you can use floating point C and use 32-bit float variables (as well as 64-bit doubles) e the emulator works with 80-bit numbers internally and therefore gives more precise results 5-2 5 FLOATING POINT Macros The following macros, defined in p_math.h #define ABS(x) ((x)<0O ? -(x) : (x)) #define MAX(a,b) ((a)>(b) ? (a) : (b)) #define MIN(a,b) ((a)<(b) ? (a) : (b)) can be used with integer or floating point expressions (although the code that is generated might be quite lengthy). Using any of these macros with a floating point expression will cause the 8087 emulator to be loaded. The remaining functions in this chapter, with the exception of p_rand and p_randl, are implemented independently of the emulator. Using them will not cause the emulator to be loaded. If the emulator is loaded, they may still be used. Converting doubles to and from text p_dtob Double to string INT p_dtob(TEXT *pbuf, DOUBLE *pval, P_DTOB *pformat) ; Convert the double floating point number *pvai to text at pbuf using the pformat format specification. This text is not zero terminated. The p_pros structure is defined in p_math.h as: typedef structure { UBYTE type; /* conversion type */ UBYTE width; /* width of representation in characters */ UBYTE ndec; /* number of decimal places */ TEXT point; /* decimal point character */ TEXT triad; /* triad separator character */ UBYTE trilen; /* threshold for triad character use */ } P_DTOB;. where: type specifies the numeric format as being fixed, scientific or general. (Integer format is obtained as a special case of fixed point format where the number of decimal places ndec is zero.) width specifies the maximum number of characters allowed to represent the number (it must be in the range 1 to 255 inclusive). You must reserve width bytes at pbuf. If the formatted string would be wider than width then =_GEN_FAIL Is returned. (If you want the output aligned and filled, post-process pbuf with p_jtob). ndec specifies the number of digits following the decimal point when type is P_DTOB_FIXED Of P_DTOB_EXPONENT where ndec must be in the range zero to P_FLT_PREC (15) inclusive. point specifies the character used for the decimal point. It would normally be either ""(dot) or ','(comma). triad specifies the triad separator character used to delimit groups of 3 digits in the integer part of a fixed point number. It would normally be one of '.'(dot) or (comma) or ' '(space). trilen is either zero to disable triad insertion, or a threshold number of digits above which triad insertion takes place. In practice, trilen is set to 1 for normal conventions and 4 to conform to the French convention. PLIB REFERENCE In all cases, negative numbers are represented by the insertion of a leading '-' sign (positive numbers do not have a leading '+' sign). To obtain a bracketed representation of negative numbers you must post- process the output string. There can never be more than p_FLT_pRECc(15) significant digits. Where there are less than P_FLT_PREC significant digits, the number is rounded to the number of significant digits displayed. The detailed formatting details as a function of type is as follows: P_DTOB_FIXED the number is represented with ndec decimal places, where ndec may be zero to represent an integer (in which case no decimal point character is displayed). If the ASCII form exceeds width (usually due to the number being large and having too many digits before the decimal point), =_GEN_FAIL is returned. A zero is displayed in the form "0.000" where there are ndec zeros following the decimal point or as just "0" if ndec is zero. P_DTOB_EXPONENT the number is represented in scientific notation with one non-zero digit before the decimal point and ndec digits beyond the decimal point followed by 'E', a sign ('+' or '-') and the exponent as two digits (with leading zero if necessary). If ndec is zero, the number is rounded to one digit of precision and no decimal point is displayed. A zero is displayed in the form "0.000E+00" where there are ndec zeros following the decimal point or as "0E+00" if ndec is zero. Triad separation is not available and triad separation parameters are ignored. P_DTOB_GENERAL converts either as fixed format (with no triad separator) or scientific format, making best use of width. Here, "making best use" is defined as showing the greater number of significant digits and preferring fixed format when the number of significant digits shown is the same. The number of decimal places displayed depends on width (ndec is ignored). A zero is displayed as just "0". Triad separation is not available and triad separation parameters are ignored. P_DTOB_GEN_LIM as for P_DTOB_GENERAL, except that output is limited to not exceed 12 significant digits. Returns the number of characters written to pbuf if successful, or one of the following negative error numbers: E_GEN_UNDER the number is too small to represent (less than approximately 1E-99). If you would prefer not to fail in this case, you can always write your own zero or re- call p_dtob with a zero double. E_GEN_OVER the number is too large to represent (greater than approximately 1 E99). E_GEN_FAIL the representation exceeds width characters. E_GEN_ARG either the double is illegal, or type is not one of P_DTOB_FIXED, P_DTOB_EXPONENT Of P_DTOB_GENERAL. p_stod String to double INT p_stod(TEXT **pstr, DOUBLE *pval, INT point); Scan the zero terminated string *pstr for a floating point number where point is the code of the decimal point character (normally either '.' or ',') and write the value as a double float to *pval. For a number to be recognised, the string must contain at least one decimal digit. If p_stod is successful, it updates *pstr to point to the terminating character and returns zero. If it fails, *pstr is not changed and it returns one of the following negative error numbers: E_GEN_UNDER the number is too small (less than approximately 1E-99). The value zero is written to *pval. E_GEN_OVER the number is too large (greater than approximately 1E99). E_GEN_FAIL fails to recognise a number. 5-4 The supplied string at *pstr should take the form: [+|-].[E|e] [+|-] where: the leading '+' sign may be omitted for positive numbers and are optional but at least one should be present leading zeros in are legal but have no effect trailing zeros in are legal but have no effect 5 FLOATING POINT there is no reasonable limit to the number of significant digits, but digits that are beyond the precision of the floating point representation will not be reflected in the mantissa of the number which is produced the exponent field (which starts with 'E' or 'e') is optional the leading '+' sign in the exponent field may be omitted for positive exponents the resulting number should be in the range approximately 1E-99 to approximately 1E+99 Example LOCAL_D TEXT buf []="-134.43735abc"; FAST INT ret; DOUBLE val; TEXT *ptr; ptr=é&buf [0]; After ret=p_stod(&ptr,é&val,'.'); val will be -134.43735, ptr will be pointing to 'a' and ret will be zero. p_getctd VOID p_getctd(E_CONFIG *pcfg); Write a copy of the system &_conFTI¢ struct to pcfg. Get number representation preferences The =_conric struct is defined in p_config.h as: typedef struct { UBYT UBYT UBYT UBYT UBYT UBYT UBYT UBYT UBYT UBYT } EL UWORD countryCode; WORD gmtOffset; UBYTE UBYTE UBYTE UBYTE UBYTE UBYTE UBYTE UBYTE UBYTE UBYTE TE E E E E E E E E E dateType; timeType; currencySymbolPosition; currencySpaceRequired; currencyDecimalPlaces; currencyNegativelInBrackets; currencyTriadsAllowed; thousandsSeparator; decimalSeparator; dateSeparator; timeSeparator; currencySymbol [9]; startOfWeek; summerTime; clockType; dayAbbreviation; monthAbbreviation; workDays; units; spare[9]; CONFIG; . 5-5 PLIB REFERENCE In the context of this chapter we are interested in: currencySymbol a zero terminated string containing the currency symbol currencySymbolPosition which should contain either E_cURRENCY_BEFORE Of E_CURRENCY_AFTER currencySpaceRequired which should contain either E_NosPACE_BETWEEN or E_SPACE_BETWEEN currencyDecimalPlaces the number of decimal places for displaying currency figures currency- TRUE if a negative currency should be displayed in brackets rather than NegativeInBrackets with a minus sign currencyTriadsAllowed zero to disable triad separator insertion, or a threshold number of digits above which triad separator insertion takes place. In practice a value of 1 is used for normal conventions and 4 for the French convention. Note that, despite the name of this element, triad separators are not restricted to currency fields; they may be inserted in any numeric field. thousandsSeparator the character code of the triad (thousands) separator decimalSeparator the character code of the decimal separator (normally either ',' or '.') units either E_IMPERIAL or E_METRIC to indicate a preference for imperial or metric units (for example, to show page dimensions in inches or centimetres) IN NN Long integer functions p_randl Long random number ULONG p_rand1l(ULONG *pseed) ; Return the next pseudo random number and updates *pseed. Used to generate a sequence of pseudo random numbers from an initial value of *pseed. Any given seed will always produce the same sequence of random numbers. The numbers generated may be any value between 0 and 4294967295 (oxffffffFfFf) or, if considered as a signed result, between -2147483648 (0x80000000) and +2147483647 +(0x7ffffffF). For example, to print reproducibly 100 random longs: ULONG seed; UINT i; seed=01; for (i=0;i<100;i++) p_printf("%1ld",p_randl (&seed) ); To generate a different set of numbers each time, seed the number with the system time, as in: seed=p_date(); Scientific functions For all floating point functions that transform a single input parameter it is permissible to use the same address for both parg and pret. All trigonometric functions assume angles are measured in radians. p_sin Sine INT p_sin(DOUBLE *pret, DOUBLE *parg); Write the sine of *parg to *pret. Returns zero if successful or E_GEN_ARG if *parg was an invalid double. 5-6 5 FLOATING POINT p_cos Cosine INT p_cos (DOUBLE *pret, DOUBLE *parg); Write the cosine of *parg to *pret. Returns zero if successful or E_GEN_arRG if *parg was an invalid double. p_tan Tangent INT p_tan(DOUBLE *pret, DOUBLE *parg); Write the tangent of a *parg to *pret. Returns zero if successful or =_cEN_arc if *parg was an invalid double or if it was greater than 149078413. p_asin Arcsine INT p_asin(DOUBLE *pret, DOUBLE *parg) ; Write the angle whose sine is *parg to *pret. Returns zero if successful or zE_GeN_arG if *parg was an invalid double or aps (*parg) >1. p_acos Arccos INT p_acos (DOUBLE *pret, DOUBLE *parg) ; Write the angle whose cosine is *parg tO *pret. Returns zero if successful or z_GEN_aARG if *parg was an invalid double or aps (*parg) >1. p_atan Arctangent INT p_atan(DOUBLE *pret, DOUBLE *parg) ; Write the angle whose tangent is *parg to *pret. Returns zero if successful or z_GEN_aRG if *parg was an invalid double. p_In Natural logarithm INT p_ln(DOUBLE *pret, DOUBLE *parg); Write the natural (base e) logarithm of *parg to *pret. Returns zero if successful or z_GEN_aRc if *parg was less than or equal to zero or if *parg was an invalid double. p_exp Exponential INT p_exp(DOUBLE *pret, DOUBLE *parg); Write the value of the arithmetic constant e (2.71828...) raised to the power of *parg to *pret. Returns zero if successful or one of the following negative error numbers: E_GEN_ARG *parg 1s not a valid double. E_GEN_UNDER underflow has occurred (the magnitude of the result is less than 1E-307), zero is written to *pret. E_GEN_OVER overflow has occurred (the magnitude of the result is greater than 1E307). 5-7 PLIB REFERENCE p_log Logarithm INT p_log(DOUBLE *pret, DOUBLE *parg); Write the base 10 logarithm of *parg to *pret. Returns zero if successful or E_GEN_aRG if *parg was less than or equal to zero or if *parg was an invalid double. p_sqrt Square root INT p_sqrt (DOUBLE *pret, DOUBLE *parg ); Write the square root of *parg to *pret. Returns zero if successful or E_cEN_aRc if *parg was negative or an invalid double. p_pow Raise to the power INT p_pow(DOUBLE *pret, DOUBLE *pargl, DOUBLE *parg2); Write *pargi raised to the power of *parg2 to *pret. Returns zero if successful or one of the following negative error numbers: E_GEN_ARG the arguments are invalid (if *parg1<0, *parg2 must be integral), or at least one argument is not a valid double. E_GEN_UNDER underflow has occurred (the magnitude of the result is less than 1E-307), zero is written to *pret. E_GEN_OVER overflow has occurred (the magnitude of the result is greater than 1E307). p_rand Double random number (long seed) DOUBLE p_rand(ULONG *pseed) ; Return a DouBLE random number in the range zero (inclusive) to one (exclusive). As in the case of p_rand1, the value pointed to by pseed is used to seed the random number generation and is updated for the next call of p_rand. This function requires the 8087 floating point emulator. p_frand Double random number VOID p_frand(DOUBLE *pret, DOUBLE *pseed) ; Write a random number in the range zero (inclusive) to one (exclusive) to *pret. As in the case of p_rand1, the value pointed to by pseed is used to seed the random number generation and is updated for the next call of p_frand. Floating point arithmetic without the 8087 emulator This section describes the PLIB functions that would normally be used to perform floating point arithmetic without having to load the 8087 floating point emulator sys$8087.ldd. For all floating point functions with one parameter it is permissible to use the same address for both parg and pret. p_fid Assignment INT p_fld(DOUBLE *pret, DOUBLE *parg) ; Write the value of *parg to *pret (the 'Id' stands for load). Returns zero if successful or E_GEN_ARG if *parg was an invalid double. 5-8 5 FLOATING POINT p_fadd Add INT p_fadd(DOUBLE *pret, DOUBLE *parg) ; Write the sum of *parg and «pret to *pret. Returns zero if successful or one of the following negative error numbers: E_GEN_ARG *pret OF *parg was not a valid double. E_GEN_OVER overflow has occurred (the magnitude of the result is greater than 1E307). p_fsub Subtract INT p_fsub(DOUBLE *pret, DOUBLE *parg) ; Write *pret minus *parg tO *pret. Returns zero if successful or one of the following negative error numbers: E_GEN_ARG *pret OF *parg was not a valid double. E_GEN_OVER overflow has occurred (the magnitude of the result is greater than 1E307). p_fmul Multiply INT p_fmul (DOUBLE *pret, DOUBLE *parg) ; Write the product of *parg and *pret to *pret. Returns zero if successful or one of the following negative error numbers: E_GEN_ARG *pret OF *parg was not a valid double. E_GEN_UNDER underflow has occurred (the magnitude of the result is less than 1E-307), zero is written to *pret. E_GEN_OVER overflow has occurred (the magnitude of the result is greater than 1E307). p_fdiv Divide INT p_fdiv(DOUBLE *pret, DOUBLE *parg) ; Write *pret divided by *parg to *pret. Returns zero if successful or one of the following negative error numbers: E_GEN_ARG *pret OF *parg was not a valid double. E_GEN_UNDER underflow has occurred (the magnitude of the result is less than 1E-307), zero is written to *pret. E_GEN_OVER overflow has occurred (the magnitude of the result is greater than 1E307). p_fcmp Compare INT p_fcmp (DOUBLE *pargl, DOUBLE *parg2) ; Compare *pargi to *parg2, returning: 1 if *pargl > *parg2 0) if *pargl == *parg2 -l if *pargl < *parg2 The function only returns zero if *pargi and *parg2 are strictly equal. In some cases it may be more appropriate to test for equality by evaluating the difference using p_fsub and then comparing the difference with a suitably small number (such as, for example, 1E-10). 5-9 PLIB REFERENCE The return value is undefined if either *pargi1 or *parg2 is not a valid double. Note that it is bad practice to compare floating point numbers for equality, since rounding errors may make the result meaningless. p_fneg Negate INT p_fneg(DOUBLE *parg) ; Negate *parg. Returns zero if successful, or E_GEN_ARG if *parg was an invalid double. p_mod Modulus INT p_mod(DOUBLE *pret, DOUBLE *pargl, DOUBLE *parg2)j; Write the remainder of *parg1 divided by *parg2 to *pret. Returns zero if successful, or E_GEN_aRG if either *parg1 or *parg2 was an invalid double. p_int Integer part INT p_int (DOUBLE *pret, DOUBLE *parg); Write the integer part of *parg to *pret. Negative numbers are rounded towards zero. Returns zero if successful or E_GEN_ARG if *parg was an invalid double. p_inti Convert double to integer INT p_inti(WORD *pret, DOUBLE *parg) ; Write the integer part of *parg to *pret if *parg is in the range -32768 to +32767 inclusive. Negative numbers are rounded towards zero. Returns zero if successful or one of the following negative error numbers: E_GEN_ARG *parg is not a valid double E_GEN_OVER *parg 1s outside the range -32768 to +32767 p_intl Convert double to long INT p_int1l(LONG *pret, DOUBLE *parg); Write the integer part of *parg to *pret, if it is in the range -2147483648 (0x80000000) to +2147483647 (0x7££fffff) inclusive. Negative numbers are rounded towards zero. Returns zero if successful, or one of the following negative error numbers: E_GEN_ARG *parg is not a valid double E_GEN_OVER *parg is outside the range -2147483648 to +2147483647 p_itof Convert integer to double VOID p_itof (DOUBLE *pret, WORD *parg); Convert *parg to a double and write it to *pret. p_longtof Convert long to double VOID p_longtof (DOUBLE *pret, LONG *parg); Convert *parg to a double and write it to *pret. 5-10 CHAPTER 6 ERROR HANDLING [a =I] Process termination A process can terminate itself or it can be terminated by another process. By whatever means a process terminates, one or more other processes may be interested in being notified that a process has terminated. Terminating this process. A process terminates itself by calling: p_exit for normal graceful termination p_panic for abnormal termination, normally as a result of defective code C programs that fall off the end of main effectively call p_exit with the return from main. That is: GLDEF_C INT main(VOID) { p_printf ("Hello world"); p_sleep (501); /* wait 5 seconds */ return (0); } is equivalent to: GLDEF_C VOID main(VOID) { p_printf("Hello world"); p_sleep (501); /* wait 5 seconds */ p_exit (0); } It is poor practice to fall off the end of a voID main since this is equivalent to calling p_exit with a random number (whatever happens to be in the ax register at the time). If there is a system component (such as the shell) reporting process terminations to the user, it will give a misleading report. Terminating another process A process can terminate another process by calling: p_pterminate or to terminate another process (typically in response to a user request) p_pkill p_ppanic to panic another process (normally following unreasonable behaviour from the process being terminated) To terminate a process, it is recommended that p_pterminate is used in preference to p_pkili as the former allows the process being terminated to run any cleanup code before exiting gracefully. Applications wishing to run cleanup code call p_onterminate to elect to be sent an inter-process message in response to the p_pterminate request. 6-1 PLIB REFERENCE Finding out when other processes terminate A process can request to be notified of the termination of another process by calling: p_logona to be signalled when the specified process terminates p_logon to receive an inter-process message when the specified process terminates (convenient for server processes to keep track of their clients) p_watchall to receive an inter-process message when any process terminates (normally used by the Shell to monitor the termination of all processes) See the chapter Asynchronous Requests and Semaphores for the meaning of the term "signalled". See the chapter Processes and Inter-Process Messaging for more about inter-process messaging and servers. The process termination word Regardless of which one of p_logona, p_logon or p_watcha1l is used, the system delivers a single 16-bit process termination word giving information on how the process terminated. The most significant byte of the process termination word is one of the following (defined in epoc.h) E_NORMAL_EXIT the process terminated itself by calling p_exit or it was terminated by another process calling p_pterminate or p_pkill. The least significant byte of the process termination word contains the nReason parameter to p_exit, p_pterminate Or p_pkill. By convention, an nReason of zero indicates a normal error-free graceful termination. E_PANIC_EXIT the process terminated itself by calling p_panic or it was terminated by another process calling p_ppanic. The least significant byte contains the nPanic ("panic number") parameter to p_panic or p_ppanic. E_TASK_PANIC_EXIT the process terminated because it owned a task that terminated with E_PANIC_EXIT (tasks are lightweight processes and are described in the chapter Processes and Inter-Process Messaging). Panics When the system detects a condition that it believes could only arise from a bug in a the application program, the system terminates the process with a "panic number" in the range 0 to 255 inclusive (where the system is said to "panic the process"). A panic is a fatal exception that causes the process to terminate immediately. There is no way for applications to avoid being terminated when a panic has been started (cf p_leave, described later in this chapter). Panicking a process is more economical in the use of system code and application code than returning an error, since the latter relies on application code to properly process the error return. As well as protecting the system from defective applications, the panic system enforces a greater discipline on application code by terminating a process as soon as the condition is detected. This does not mean that the system always prefers to use panics rather than error returns. Error returns are still used where appropriate; the system certainly never panics a process on a condition that could arise from user action. For example, the p_alloc function to allocate a memory cell returns nutt if there is insufficient free system memory to satisfy the request, but calls p_panic if it detects that the heap has been corrupted. In the majority of cases, such as in the above example, it is quite clear where a panic or an error return is appropriate, but occasionally it is not so clear. From the point of view of an application programmer, however, it is clear that panic conditions should be avoided. In consequence the function descriptions in this manual include any conditions that result in the caller being panicked. If the panic condition is detected within the code of a system function, the function calls p_panic as in the example above. If the condition is detected in a system process (for example, the supervisor or the file server) the application process is terminated by the system process calling p_ppanic. Application programmers can include their own calls to p_panic to catch conditions indicating a bug in their program which might otherwise go unnoticed (until the software is used by a customer!). A common example would be to put a call to p_panic on the default case of a switch statement, to catch an invalid parameter. 6-2 6 ERROR HANDLING System panic numbers The following lists the panic numbersthat are used by the operating system: 00 01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 Used by test code when a test fails Invalid function number (semaphore manager) Invalid semaphore handle Semaphore not allocated Initial semaphore count is negative Signal count is negative Invalid function number for process manager Invalid process ID Task tried to create a task Invalid function number for time manager Invalid function number for segment manager Segment size was negative Type was not one of E_SEGMENT LOW, E_SEGMENT_HIGH, E_SEGMENT_DEVICE OF E_SEGMENT_LOCKED Invalid segment handle Segment copy is out of range Invalid function number for heap manager Heap not initialised A heap cell is being reduced by more than its size Attempt to set heap granularity greater than z_max_GRowByY A heap cell address is outside the boundaries of the heap (the heap has probably been corrupted - try calling p_alichk to catch the corruption sooner) Invalid function number for inter-process message manager Inter-process messaging has already been initialised (ie p_minit has been called twice) Inter-process messaging has not been initialised (ie p_minit has not been called) Cannot initialise with zero messages in the queue Invalid function number for I/O manager Invalid I/O channel (possibly because you did not test that the previous p_open succeeded or you have closed the channel or you overwrote the variable containing the channel) Device requested panic Invalid wait handler handle (possibly nothing to do with wait handlers and just indicative of a low address overwrite of the 4 bytes at address 2, as a result of an uninitialised pointer) Key and pointing device already hooked Key and pointing device requesting process is not a task Invalid function number for device manager Invalid device handle Invalid function number for file manager Process already connected to file server PLIB REFERENCE 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 Reserved for future use Invalid function number for library manager Invalid library handle Invalid function number for library Invalid LIB file channel Invalid DYL index number Invalid message to file server Process has not connected to file server Invalid function number for conversion manager Invalid function number for general manager Attempt to unhook from notify when not already hooked Invalid revector address Invalid function number for conversion manager Leave called before a call to enter (possibly because you were unaware that the function you were calling could call p_leave) No method available to handle message (OOP) Invalid reclass attempted (OOP) Unknown category in LibHandle (OOP) Unknown class in LibCreate (OOP) Supersend called from outside a method (OOP) Attempt to get a handle before being linked (OOP) Missing external categories in LibLink (OOP) Object does not point to a valid class (OOP) Invalid link layer completion code Invalid function number for window server Invalid function number for hardware manager Unexpected interrupt Attempted to write outside of process data segment (possibly because of an uninitialised pointer or a corrupted data structure) Interrupts have been disabled for too long Reserved for future use Divide by zero interrupt Overflow interrupt Invalid function number for Dbf manager Invalid DBF I/O channel Invalid parameter for DBF function Address zero overwrite (possibly because of an uninitialised pointer) 6 ERROR HANDLING 69 The operating system detected less than 0x100 bytes of remaining stack (this amount is reserved for hardware interrupts to run). You probably have declared large data structures as automatics. Consider making them static variables or allocate them from the heap. 70 Environment name size > EnvMaxNameSize 71 Single step interrupt (INT 1) 72 Break point interrupt (INT 3) 73 A request was made while an asynchronous request of the same type and on the same channel was already pending 74 Invalid function number for serial I/O manager 75 Call to an ASIC1 function on an ASIC9 machine 76 Attempt to find a DYL not in a visible bank 77 Floating point emulator exception 78 Semaphore count exceeds Ox7fff 80. Library fatal error, preceded by a notification of the specific error 255 The function p_ailchk detected a corrupted heap Some of the panics, especially those described by "Invalid function number for ...", are unlikely to indicate a specific bug; it is almost impossible to create code that would produce such a panic by a coding error. This type of panic could, however, easily arise from trashing a return address on the stack, with the instruction pointer wandering into arbitrary code. In the above list, those panics that are likely to indicate a specific coding problem are described more fully. If you get a panic 79, or a panic in the range 81 to 254, it may be due to some other system component. See the Window Server manual for panics in the range 81-110 and, when using object oriented programming, see the OLJB manual for panics in the range 130-158. p_exit Terminate this process VOID p_exit (INT nReason) ; Terminate this process with reason nReason in the range -127 to 128 without returning to the caller. Applications that exit normally should terminate with nreason equal to zero. Applications that fail during their initialisation may wish to pass on the error number (in the range -127 to -1 inclusive) that caused the initialisation to fail. Programs that are run as subprocesses can use a positive nReason to pass back an exit status to their parent process. p_panic Terminate after an unrecoverable error VOID p_panic(INT nPanic); Terminate this process with panic number nPanic in the range 0 to 255 without returning to the caller. To avoid system panic numbers, application specific panic numbers should start from 254 downwards. p_pkill Unilaterally terminate a process INT p_pkill (HANDLE pId, INT nReason) ; Terminate process pra for reason nReason (in the range -127 to 128 inclusive) without giving the process being terminated an opportunity to run any cleanup code before exiting. It is recommended that p_pterminate (described below) is used in preference to p_pkill as it gives the process being terminated a chance to run any cleanup code. PLIB REFERENCE Returns zero if successful or one of the following negative error numbers: E_FILE_NXIST the process does not exist E_GEN_FAIL pid is the null process, the supervisor or the file server p_pterminate Terminate a process INT p_pterminate (HANDLE pid, INT nReason) ; Terminate process p1d for reason nReason in the range -127 to 128 inclusive. If process prd has called p_onterminate (described next) the call to p_pterminate will simply send the specified message number to process pid. Otherwise the effect is the same as with p_pki1l. It is recommended that p_pterminate is used in preference to p_pkill. Returns zero if successful or one of the following negative error numbers: E_FILE_NXIST the process does not exist E_GEN_FAIL pid is the null process, the supervisor or the file server p_onterminate Elect to receive termination message VOID p_onterminate (INT nMessage) ; Elect to receive (non-zero) message number nMessage, rather than being summarily terminated, when another process requests termination by calling p_pterminate. On receipt of the termination message a process should execute its cleanup code and must then terminate itself, normally by calling p_exit. The process calling p_onterminate must guarantee to respond promptly when the termination message is sent to it. It should not perform any lengthy, uninterrupted, processing. A process which calls p_onterminate and then (presumably in error) enters an infinite loop will not be terminated by a call to p_terminate. It is recommended that a process should not call p_onterminate unless there is an explicit reason for it to do so. The election may be cancelled by calling p_onterminate (0). Messaging must have been initialised with p_minit prior to the call to p_onterminate, otherwise p_panic will be called. p_ppanic Panic a process by id INT p_ppanic(HANDLE pId, INT nPanic); Terminate process p1d with panic number nPanic in the range 0 to 255 (used for example by server processes that receive a garbage message from a client process to panic the client). Returns zero if successful or one of the following negative error numbers: E_FILE_NXIST the process does not exist E_GEN_FAIL pid is the null process, the supervisor or the file server p_logona Request notification of process termination INT p_logona (HANDLE pId, WORD *pStatus) ; Make an asynchronous request to be notified of the termination of process pid by having this process I/O semaphore signalled when pid terminates. See the chapter Asynchronous Requests and Semaphores for a description of asynchronous requests and the I/O semaphore. Returns zero if successful or E_FILE_NXIST if pId does not exist. After a successful request and before pid has terminated, *pstatus contains E_FILE_PENDING. When pid has terminated, *pstatus contains the (non-negative) process termination word (as described under the heading The Process Termination Word at the beginning of this chapter). The caller can cancel the asynchronous request by calling p_logoffa. 6-6 6 ERROR HANDLING A server process that responds to inter-process messages from client processes will probably find it more convenient to use p_logon, described below. For example, the following function: GLDEF_C INT RunSubProcessWait (TEXT *name, BYTE *pReason) { HANDLE pid; WORD stat; if ((pid=p_execc (name, NULL, 0) ) <0) return (pid); p_logona (pid, &stat) ; p_presume (pid) ; p_waitstat (&stat); *pReason=(BYTE) stat; return (stat>>8) ; } loads the executable name, resumes the process, waits for it to terminate, writes the p_exit to *pReason and returns &£_NORMAL_ExIT. It returns a negative error number if it fails to load name and £_paNIc_Ex1T if the sub-process panics. p_logoffa Cancel notification of process termination INT p_logoffa(HANDLE plId)j; Cancel a previously requested notification of the termination of process pra (as passed to the p_logona being cancelled). Returns zero if successful or —_FILE_NxIsT if no request is pending. If the cancel gets through before pid terminates, *pstatus will contain z_F1LE_caNcEL. In either case, the process I/O semaphore is signalled and p_1ogoffa would normally be followed by a call to p_waitstat (pStatus). p_logon Request message on process termination INT p_logon(HANDLE pId, INT mType); Request to be notified of the termination of process pia by receiving an inter-process message of type mType when pid terminates. See the chapter Processes and Inter-Process Messaging for a description of inter-process messaging. Returns zero if successful or E_FILE_nxist if pra does not exist. When process pid terminates the Supervisor process sends the caller a message of type mtype and whose first word in the message buffer is the pa of the terminating process and whose second word is the process termination word giving information on how that process terminated (as described under the heading The Process Termination Word at the beginning of this chapter). The caller can cancel the request by calling p_logoff or p_logoffx. The process must have messages initialised by calling p_minit - the function calls p_ panic if messages have not been initialised. The p_logon, p_logoff and p_logoffx services were designed for server processes that respond to inter- process messages from client processes to clean up client specific resources should a client process terminate without disconnecting from the server. Processes that are not server process and that do not normally respond to inter-process messages will probably find it more convenient to use p_logona, described above. PLIB REFERENCE p_logoff Cancel message on process termination INT p_logoff (HANDLE pId, INT mType); Cancel a previous p_logon request to be sent an inter-process message when process pId terminates. The value of pta should be as passed to p_logon, and mType is ignored. This form is suitable for applications that do not make no more than one p_logon request to any particular process. Applications that make two or more p_logon requests with the same value of pra (but, presumably, different values of mtype) should cancel them by means of p_logoffx, described below. Returns zero if successful or E_FILE_NXIST if pId does not exist. The function calls p_panic if messages have not been initialised. p_logoffx Cancel message of specific type on process termination INT p_logoffx (HANDLE pId, INT mType); This function is only available in EPOC version 3.18 or later. Cancel a previous p_logon request to be sent an inter-process message of type mrype when pid terminates (p1d and mType should be the values that were passed to the p_logon request that is being cancelled). This function must be used in preference to p_logoff in cases where two or more p_logon requests are made with the same value of pid. Returns zero if successful or E_FILE_NXIST if pId does not exist. The function calls p_panic if messages have not been initialised. p_watchall Watching all exits INT p_watchall(UINT mType) ; Request to be notified of the termination of any process by receiving an inter-process message of type mType when a process terminates. See the chapter Processes and Inter-Process Messaging for a description of inter-process messaging. Only one process at a time can request this service and it is usually reserved for use by a system process (normally the Shell process) to monitor the termination of all processes. The function returns zero if successful or E_GEN_FAIt if a watch is already active. The format of the received message is as for p_logon, described above. Calling p_watchall with an mType of zero cancels the request. The process must have messages initialised by calling p_minit - the function calls p_panic if messages have not been initialised. [ce re FF Error returns System functions that can fail must somehow indicate success or failure and, where appropriate, elaborate on the failure. Where there is no elaboration of the error: e Functions that return an address typically return a NULL (zero) address to indicate failure (this is often used when a function can fail to allocate memory). ¢ Otherwise functions return zero or positive to indicate success and -1 to indicate failure (the constant E_GEN_FAIL is defined as -1 although you can just test for the sign of the returned value). 6-8 6 ERROR HANDLING Where the error is elaborated, the system function returns a system error number in the range -1 to -128, allocated as follows: -1 to-31 Reserved for general errors of the form &_GEN_xxx; (defined in pp_gen.h) -32 to -63 Reserved for I/O device errors of the form &_FILE_xxx (defined in p_file.h) -64 to -95 Reserved for future use -96 to -128 Reserved for OPL run-time errors The system errors include both generic error numbers (eg =_GEN_NoMEMory) and specific error numbers (eg E_FILE_PARITY indicating a parity error in a byte received via a serial port). Application programmers may wish to use the generic error numbers in their own code - see the contents of p_gen.h and p_file.h. The system stores a language dependent description for each of the system error numbers that may be retrieved by calling p_errs. p_errs Convert error number to string VOID p_errs(TEXT *str, INT errno); Convert a system error number to a language dependent zero terminated text string in str. There should be at least z_MAx_ERROR_TEXT_S1ZE (64) bytes at address str. If the error is an unknown error the string "Unknown error [xx]" (or a suitable translation if not an English ROM) is returned, where xx is the value of errno. ————————— SS ————>>E~— ——>E>E>>—e~—L_——_ << s Notifier services The notifier services are used to inform the user of a condition - particularly an error condition - and to present the user with up to three options on how to proceed. There are two variants of the notify service: p_notifyerr which presents the user with a system error message (converted to text from the error number using p_errs) and a contextual message p_notify which presents the user with two messages These services can be used by any application and are particularly useful for processes that do not otherwise have a user interface (for example, the file server). Investigative calls to p_notify may be temporarily inserted into code when debugging programs. At the level of the services described in this manual and except for the simple console functions such as p_printf, the operating system does not define any user interface components and the notifier services rely on a higher level system process! (the "notifier") taking responsibility for implementing a user interface. The notifier "hooks" the user interface (normally at system start up) by calling p_notifyhook. The notifier should pre-allocate any memory it requires so that the call will not fail. The notifier service is used by the file server process to give the user the opportunity to rectify a problem that would otherwise result in a file service request failing (eg to replace an SSD pack that has inadvertently been removed). This scheme works well when the requesting process is an interactive application but poorly if the requesting process is designed to run unattended (eg a communications program) or is itself a server process (eg the window server trying to read a font file). Such processes can use p_setnotify (FALSE) to stop servers such as the file server from using the notify service to give the user the opportunity to rectify an error condition. p_notify Present the user with a message and get response INT p_notify(TEXT *pT1l, TEXT *pT2, TEXT *pOl, TEXT *pO2, TEXT *p0O3); Present the two zero terminated messages pt1 and pt2 to the user, where poi, po2 and po3 are either NULL or zero terminated strings, offering up to three options for the user to select. The function waits for the user to select an option and returns zero if the po1 option was chosen, | if the po2 option was chosen and 2 if the po3 option was chosen. 'When a specialised process hooks the notifier, it is, by convention, called syssntFy. 6-9 PLIB REFERENCE The message pT1 is presented before pt2. So pt1 would typically contain a contextual message (eg "Failed to save notes.tpd") with ptT2 containing a more detailed message (eg "Disk full"). If pt2 is NULL the second message line is blank. To offer the user 2 options rather than 3, pass po3 as NULL. To offer the user no option (that is, just to wait until the user has acknowledged the message) pass both p02 and po3 as NuLL. Alternatively, pass all 3 as NULL as in: p_notify (msgl,msg2,NULL, NULL, NULL) ; which is equivalent (on an English machine) to: p_notify (msgl,msg2, "CONTINUE", NULL, NULL) ; Each message string pT1 and pt2 can be up to E_MAX_NOTIFY_TEXT_SIZE (64) in length including the zero terminator. Each option string po1, po2 and po3 can be up to E_MAX_OPTION_TEXT_S1ZE (16) in length including the zero terminator. This function sends an inter-process message to the process that has "hooked" the notify interface and thereby has taken on the responsibility of presenting the error notification user interface. If no process has hooked the notifier, or if the notifier process has terminated, p_notify returns E_GEN_FAIL. The notifier process may, in some environments, take special action if any of the strings passed to p_notify contain a leading zero. Programmers should therefore ensure that such a string is not passed as a parameter to p_notify. Either pass an explicit NULL (as in the above examples) or intercept the string, as in the following example, where it is assumed that only msg2 may contain a leading zero: LOCAL_C INT NotifyError(TEXT *msgl,TEXT *msg2) { if (msg2 && !*msg2) msg2=NULL; return (p_notify (msgl,msg2,NULL, NULL, NULL) ) ; } p_notifyerr Notify user of error and get response INT p_notifyerr(INT nError, TEXT *pT2, TEXT *pO1, TEXT *pO02, TEXT *p0O3); Equivalent to calling p_errs (nError) followed by calling p_notify with the resulting error string as the second parameter and with pt2 as the first parameter (ie the first two parameters are the other way around compared to p_not ify) with the three option parameters being passed on to p_notify. The message pT2 is presented before the error text corresponding to nError and pt2 would typically contain a contextual message. Only system error numbers (which include all errors returned by the functions described in this manual) should be notified using this service. p_setnotify Set notify state VOID p_setnotify(UINT nState) ; Set the notify state of this process. If nstate is FALSE, the system will not automatically present the notifier as a result of an error in a service requested by this process (the error will be returned directly). If nstate is TRUE (the default state after process creation) the notifier may be called. p_getnotify Get notify state INT p_getnotify (VOID) ; Return the notify state for this process. 6-10 6 ERROR HANDLING p_notifyhook Hook the notifier interface INT p_notifyhook (INT mType) ; -Hook the notifier interface such that this process will get an inter-process message from the notifying process of type mType as a result of the notifying process calling p_notify Of p_notifyerr. Returns zero if successful or z_ceN_ratt if the notify interface has already been hooked. The mtype message contains an array of 5 string pointers into the notifying process data space corresponding to the parameters to p_notify in the order pT1, ptT2, pol, po2 and po3. A process that hooks the notify interface should initialise messaging using p_minit with a value of at least 10 for the message size. The text strings pointed to by the 5 parameters can be fetched from the notifying process using p_pcpyfr (say using the maximum sizes E_MAX_NOTIFY_TEXT_SIZE and E_MAX_OPTION_TEXT_SIzE). The parameter to p_mfree gives the result of the notification. A process that has hooked the notify interface should not call either p_notify of p_notifyerr as it would then try and send itself a message, resulting in deadlock. If the process that has hooked the notify interface terminates, the system automatically frees the notify interface so that another process can hook the interface. p_notifyunhook Unhook the notifier interface VOID p_notifyunhook (VOID) ; Release the notify interface. Calls p_panic if the caller does not have the notifier interface hooked. SS —————————————————————————————————————————————————————————————] Enter and leave The functions p_enter and p_leave work together. You use p_enter to call or "enter" a function. If p_leave(err) is called before the entered function returns, the stack is unwound and the call to p_enter returns err. By convention, a negative value of err indicates an error and a zero (or positive) value signifies an error-free exit. The call to p_leave (or to a variant such as £_leave) may occur in the entered function or in a sub- function and so on. The p_leave performs what is sometimes called a "non-local goto" where the address of the goto is defined by the last call to p_enter. The enter and leave mechanism is commonly used in medium to large interactive applications to implement structured error recovery. When an error occurs, the application has to do the following to recover: e free any dangling resources (eg free memory cells, close open channels, close screen windows) e inform the user of the error ¢ continue Handling all this with ad hoc conditionals in the code can double the size of a program and makes the code hard to follow. An alternative is to set error state variables, driving centralised clean-up code that is invoked by a negative return from a call to p_enter on an error condition. The call to p_enter is placed at an appropriate place to continue after the error. The return value indicates the nature of the error (eg no system memory) and a state variable could give a contextual message (eg "while attempting to open file xxx"). Other error state variables would give the handles of resources that should be freed. 6-11 PLIB REFERENCE p_enter Enter a function INT p_enter(VOID *pfunc,...); INT p_enterl(VOID *pfunc) ; INT p_enter2(VOID *pfunc, VOID *al); INT p_enter3(VOID *pfunc, VOID *al, VOID *a2); INT p_enter4(VOID *pfunc, VOID *al, VOID *a2, VOID *a3); INT p_enter5(VOID *pfunc, VOID *al, VOID *a2, VOID *a3, VOID *a4); INT p_enter6(VOID *pfunc, VOID *al, VOID *a2, VOID *a3, VOID *a4, VOID *a6é); Call function *pfunc where the remaining (up to 5) arguments to p_enter are passed as arguments to *pfunc. You can either use p_enter, which presents the cpEct calling convention, or one of the p_enter? variants, which use a more efficient register calling convention. Once a function has been called using p_enter, that function (or any function that is called before *pfunc returns) may call p_leave (ret) to unwind the stack and return prematurely from the call to p_enter with the return value of ret. If p_leave is not called, p_enter returns the value returned by *pfunc. Functions called with p_enter should return an INT or a UINT (since this is assumed by p_leave). A zero or positive return value is normally taken to indicate that the function completed successfully. Calls to p_enter may be nested, in which case p_leave returns from the last active p_enter on the stack. When p_enter calls *pfunc, it passes parameters to it both on the stack and in registers. Neither calling convention? is compatible with the default calling convention (which passes parameters in registers) as specified in p_std.def. Because of the above, the target of a p_enter must declare the function as using one of the following calling conventions: CDECL where the generated code will take the parameters off the stack ENTER_CALL where the generated code will take the parameters from the registers (which is more efficient) If you also call the entered function directly (without a p_enter) you must ensure that the prototype for the function declares the calling convention consistently. For example, to declare the target for a p_enter using CDECL: LOCAL_C INT CDECL RunTestProgram(TEXT *name) { ..return(0); } or, more efficiently, using ENTER_CALL: #pragma save, ENTER_CALL LOCAL_C INT RunTestProgram(TEXT *name) { ..return (0); } #pragma restore where, in either case, you would enter the function using, say: ret=p_enter((VOID *)RunTestProgram, "fred") ; or, more efficiently: ret=p_enter2 (RunTestProgram, "fred") ; where ret contains zero if RunTest Program returned or the parameter passed to p_leave, if p_leave was called. 2Note that the calling convention applied to the function called by p_enter has nothing to do with the calling convention of p_enter (or p_enter1 etc) as discussed above. 6-12 6 ERROR HANDLING Since it is impossible to construct a general prototype to cover all cases, pfunc is prototyped as a vorpD *. As in the above example, you have to cast the first parameter to p_enter toa (vorp *) to avoid compilation warnings. The header files are organised in such a way that the cast is automatically done when you use one of the fixed parameter p_enter? variants. Note that the requirement for the target of a p_enter to have one of the two special calling conventions described above means that no PLIB or WLIB function may be the direct target of a p_enter. p_leave Unwind stack and return from last p_enter VOID p_leave (INT err); Unwind the processor stack and return from the most recently called p_enter, returning the value err. The function does not return to the caller. Although in practice err is often a negative error number it need not be and p_teave can legitimately be used to unwind the stack on any kind of condition. The function calls p_panic if there is no call to p_enter on the stack. f_leave Unwind stack and return from last p_enter if error INT f_leave(INT err); Similar to p_leave except that it simply returns err if err>=0. That is, it is equivalent to: GLDEF_C INT f_leave(INT err) { if (err<0) p_leave(err); return(err); } Although modest in its function, using f_1eave rather than p_leave produces smaller executables and makes code more readable. For example, compare: pid=f_leave (p_execc (name, NULL, 0) ); with: pid=p_execc (name, NULL, 0) ; if (pid<0) p_leave (pid); You cannot use £_leave on functions that return an address and fail by returning nui. However, the more commonly used functions of this type have corresponding f_ variants. For example, p_alloc and p_realloc have the corresponding £_alloc and £_realloc which internally call p_leave (E_GEN_NOMEMoRY) rather than return NULL. Example LOCAL_C INT RunSubProcessWait (TEXT *name) { HANDLE pid; WORD stat; pid=f_leave (p_execc (name, NULL, 0) ); p_logona (pid, &stat) p_presume (pid) ; p_waitstat (&stat); if ((stat>>8) !=E_NORMAL_EXIT) p_panic(stat); return ( (INT) ( (BYTE) (stat&Oxff))); } 6-13 PLIB REFERENCE LOCAL_C INT CDECL RunTestProgram(TEXT *name) { ret=RunSubProcessWait (name) ; if (ret) p_printf ("Program %s failed with reason %d",name,ret); return (0); } LOCAL_C VOID RunTestPrograms (TEXT *list) { TEXT *p; INT ret; TEXT name[32]; p=p_scpy (&name [32], "testx")-1; while (*p=*list++) { ret=p_enter((VOID *)RunTestProgram, &name[0]); if (ret<0) { p_atos(é&msg[0],"Failed to run %s",&name[0]); ret=p_notifyerr (ret, &msg[0], "CONTINUE", "ABANDON", NULL) ; if (ret==2) p_exit (0); GLDEF_C INT main(VOID) { RunTestPrograms ("abcd"); /* runs testa.img, testb.img, ... */ return (0); } Note the use of cbEct in the declaration of RunTestProgram. There are more examples of the use of p_enter and p_leave in the Files chapter. 6-14 CHAPTER 7 MEMORY ALLOCATION Overview of system memory usage The EPOC operating system runs on the 8086 processor (and also the 80286, 80386 or 80486) where up to 1Mb of memory may be addressed. On SIBO machines and depending on the model, all or part of this address range may be used where the available memory is allocated (from address zero to oxffffFf) as follows: e 1K bytes of interrupt vectors (required by the 8086 architecture) e the screen bit-map (small display models) e the operating system data space e allocated memory segments (including application code segments, process data segments and device driver segments) e unallocated memory e the internal RAM drive (LOC::M:) ¢ environment variables (up to 4K bytes) e any portion of the 1Mb that is not used e the screen bit-map (large display models) e the system ROM (typically 256K bytes) The contents of the internal RAM drive and the environment variables survive a system reset (unless the ESC key is held down) and most system crashes. See p_get res in the chapter General System Services for more on system resets. If we consider only that (greater) part of memory that is dynamically allocated, it is organised into four main sections: Memory segments Unallocated memory RAM drive (M:) Environment variables The system maintains all the unallocated memory in a single chunk - between the allocated memory segments and the memory used by LOC::M:. This means that memory segments have to be moved as a result of other segments being created, deleted or having their size changed. Although application code segments and process data segments can and do move at any time while an application process is running, the operating system automatically adjusts the 8086 segment registers (CS, DS, SS and ES) to follow any movement without explicit support from the application - as discussed in the first chapter of this manual. 7-1 PLIB REFERENCE As well as giving some background on memory usage, this chapter describes functions that allocate and access: ¢ memory cells from the heap in the process data segment (which is a dynamic segment) e memory segments (either device or dynamic) ¢ environment variables Memory segments The allocated memory segments contain both device and dynamic memory segments. Device segments are created when an external device is installed and are deleted when the device is removed. Once created, a device segment does not normally change its size. The first two device segments are special and are created at system startup for the process data segments of the first two processes to be created - the null process (SYS$NULL) and the supervisor (SYS$MANG). Neither of these two process data segments changes size. Dynamic segments are much more volatile. Code and data segments are created and deleted as processes are created and terminated. Process data segments change their size to accommodate heap allocations. Device segments are allocated with a lower address than the dynamic segments so that they are not disturbed by any activity with respect to the more volatile dynamic segments. A device driver normally has to stop working while its segment is moving - which could lead to loss of data (say when receiving data via the serial port). Allocated memory segments are described by: e asegment name e asegment handle e asegment size e asegment address Segment names Segment names are zero terminated strings of up to eight characters followed by an optional period and up to three further characters (the same rules as for file names). Examples of valid names are as follows: NOTES NUMBERS . DAT DATASEG.01 As with file names, the segment name extension normally indicates the usage of the segment. The system has the following conventions: -LDD and .PDD indicate device segments that contain a Logical Device Driver and a Physical Device Driver respectively. Device drivers are normally written in 8086 assembler. See the EPOC O/S System Services reference manual for more about device drivers. $SC indicates the primary shared code segment that is associated with one or more processes. Running say myprog.img will cause the code to be loaded into a segment called myprog.$sc (provided that myprog.$sc isn't already loaded). .DYL indicates a dynamic library code segment (DYL) that is associated with one or more processes. See the chapter Object-Oriented Programming for more about DYLs. $nn indicate process data segments where nn consists of two decimal digits (O1, 02, ...) according to the process number. The name of a process data segment is normally the same as the process name although they are in fact independently held (the process name can be changed using p_prename). The process data segment is described further below. 7-2 7 MEMORY ALLOCATION Segment handle, address and size The segment handle is actually the relative address of the segment table index entry in the operating system data space. This 16-byte entry contains the address of the segment, the segment usage count and the segment name. The usage count normally indicates the number of processes that are using the segment - for example, when there are 2 processes of the same application, the usage count of the application code segment is 2 whereas the usage count of each process data segment is 1. When the usage count drops to zero, the memory segment may be (and normally is) deleted. The order of the segments follows the order of the segment table index entries and the segment size is calculated by subtracting the start addresses of adjacent segments. The segment index table has a fixed total capacity with fixed sub-capacities for device and dynamic segments (typical limits are 96 total segments as 32 device segments and 64 dynamic segments). A segment allocation will fail if one of these limits is reached (which is unlikely unless there is a bugged application that fails to free segments). Although the allocated segments themselves are contiguous, the segment index table may have "holes" in it as a result of segment deletion. When a new segment is allocated, it will tend to fill any holes in the index table first and, in this case, the created segment will be inserted before other segments causing them to be moved. The address and size of memory segments is expressed in 16-byte paragraphs (as used in setting the value of the 8086 segment registers CS, DS, ES and SS). It follows that segments start on 16-byte boundaries and are a multiple of 16 bytes long. From the point of view of the segment allocator, the maximum segment size is 512K which makes it possible to specify a segment size (in 16-byte paragraphs) within a signed 16-bit word. Process data segments When a process is created (normally by loading an image using p_execc Of p_execcasync), a process data segment is also created. When that process is running application code, the 8086 DS, SS and ES registers point to the start of the data segment which can not be greater than oxffe0! bytes long (this is called the small model on PCs). The data segment contains (from low to high address): e the reserved static variables (0x40 bytes) e the floating point emulator data space (0x200 bytes from offset 0x100) e the processor stack e initialised static variables e uninitialised static variables e the process heap The size of the processor stack depends upon the C startup module that is being used but is typically in the range 2K to 8K. Note that the stack size declared by the startup module includes the 64 bytes of reserved static variables and the floating point emulator data space. The word at address zero is initialised to oxpEap and is otherwise unused. If it is not oxpzap, it is probably because of a write using an uninitialised pointer that happened to have a zero in it. In the absence of any further initialisation (such as the oxpEap above), the reserved statics variables are initialised to zero. The bytes in the stack area are initialised to oxe£. The number of oxf bytes from address 0x40 in the process data segment measures the number of spare bytes on the stack provided the program is not using the floating point emulator. If the program is using the floating point emulator, the lowest point the stack should legitimately reach is 0x300. !The segment size is limited to 32 bytes less than the maximum 64K so that a stack underflow will always cause an address trap. PLIB REFERENCE Despite their name, the uninitialised static variables are all initialised to zero. The process heap contains dynamic data structures that are created and destroyed within the lifetime of the process. The heap is placed at the end of the process data segment so that it can be expanded by expanding the process data segment. Since the data segment is limited to 0xffe0 bytes, the maximum heap size is somewhat smaller than 64K, depending on the size of the stack and the space taken by the static variables. The process heap and the allocator are described in detail next The heap allocator The allocator is used to allocate, resize and free variable length memory cells (which typically range from 10s of bytes to a few kilobytes in length) from the process heap. Allocated cells are referenced directly by their address; they do not move to compact free space left by freed cells. The allocator functions are: p_alloc, f_alloc allocates a cell, returning its address. p_free frees a cell, which is returned to the heap. p_realloc, f_realloc changes the size of the cell, returning its new address. p_adjust opens or closes a gap in the middle of the cell (useful for deletion and insertion of cell content), changing the size of the cell as appropriate. p_alen returns the size of the cell. p_hgran sets the heap granularity. p_allwalk walks all cells in the heap calling a supplied function (used for heap diagnosis). p_allchk uses p_allwalk to check the integrity of the heap. p_allspe gets the start address and free space in the heap. The functions f_alloc and f_realloc are identical to p_alloc and p_realloc respectively except that they call p_leave (E_GEN_NOMEMoRY) if the memory could not be found rather than returning a NULL address. See the function p_leave for more details. The allocator functions are used internally by many other PLIB functions. Heap structure After a number of allocate and free calls the heap typically consists of ranges of adjacent allocated cells separated by single free cells (which are linked). Each cell (whether free or allocated) is prefixed by a hidden 16-bit word that gives the size of the cell in bytes. This leading length word is hidden since the address returned by p_alloc, p_realloc and p_adjust skips this header and the length returned by p_alen does not include it. Writing beyond a cell's limits will corrupt a cell length word (and possibly also a free space cell pointer) which destroys the heap's integrity. Such errors are difficult to debug because there is no immediate effect - the corruption is a "time bomb". It will eventually be detected (resulting in a call to p_panic) bya subsequent allocator call (such as p_free). The p_alichk function is provided as a debugging tool to force a call to p_panic sooner rather than later when it is suspected that the heap's integrity has been damaged. Growing and shrinking the heap In EPOC, a process heap is not fixed in size - the system can grow and shrink the heap (and the process data segment that contains it). The heap is grown to satisfy allocation requests that would otherwise fail, up to the 0xffe0 process data segment limit. The heap may be shrunk to release memory to the system when it is required. When the heap is grown, it is normally grown by 2K bytes more than is strictly needed to satisfy the request - see p_hgran for more details. When an application is executed to create a process, the initial size of the heap is taken from a value that is stored in the executable (by default, 2K bytes). This same value also specifies the minimum size of the heap. Allocation is based on "walking" the free space list to find a free cell that is big enough to satisfy the request (using the "first fit" algorithm). If no free cell is big enough, the system will attempt to grow the data segment to add more free space at the end of the heap. 7 MEMORY ALLOCATION If there is no memory in the system to accommodate growth or if the data segment has reached its maximum oxffeo byte value, the allocate request fails. There are few circumstances when an allocate request can be assumed to succeed and calls to p_alloc, p_realloc and p_adjust should have recovery code to handle a failure to allocate. From the point of view of the user of an application, the two causes of an allocation failure produce quite different situations. If there is no memory left in the system, this can normally be remedied by the user taking some action to release memory (such as exiting a task). If the 64K limit has been reached, this is presumably as a result of a heap-based data structure reaching its design limit (rather than a bug as in, for example, "alloc heaven", described below). Applications that contain indefinitely growing heap-based data structures (as in, say, a spreadsheet) should not allow the data structure to grow until the allocation fails when the oxffe0 data segment limit is reached because, at this limit, there may not be sufficient memory in the free space list to perform other tasks (such as saving the data to file!). The growth of such data structures should be monitored by the application and limited to leave sufficient heap capacity for tasks that the user would reasonably expect to be able to perform. The segment allocator can reclaim excess space from a heap if the last cell in the heap is a free cell, reducing the size of the last free cell to a small value. However, the heap is never shrunk below its initial size (as specified by the value in the executable). Alloc heaven There are cases in which programs allocate a sequence of cells which must either exist as a whole or not at all. If during the allocate sequence one of the later allocations fail, the previously allocated cells must be freed. If this is not done, the heap will contain unreferenced cells that consume memory to no purpose. At Psion we say that these cells have gone to "alloc heaven". When designing how to physically organise data structures into alloc cells you should be mindful of the recovery code that must be written to free partially built multi-cell structures. The fewer the cells in a structure, the easier the recovery code. Internal fragmentation The free space in a heap is normally fragmented where the largest cell that may be allocated is substantially smaller than the total free space. Excessive fragmentation, where the free space is distributed over a large number of cells (and where by implication many of the free cells are small) should be avoided because it results in an inefficient use of memory and reduces the speed with which cells are allocated and freed. Practical design hints for limiting internal fragmentation are: e Avoid using the heap for small highly transient data structures that can be placed on the stack (as an automatic). High frequency cycling through allocate and free pairs "churns" the heap and leads to a long free space list. e When you have a large number of variable length data structures (particularly when they are frequently resized), "granularise" them (i.e. round the allocate up to a multiple of some reasonable value) so that you decrease the chance of leaving small unusable free space cells. e Use heap analysis tools to give you a feel for what is going on. You may find that by changing the way you do something you get a better heap and you may even discover some alloc heaven. Don't go too far - there are diminishing returns to heap usage tuning. p_alloc (or f_alloc) Allocate a memory cell VOID *p_alloc(UINT size); VOID *f_alloc(UINT size); Allocate a memory cell of at least size bytes long from the heap and return the address of the allocated cell or nuut if there is insufficient memory. You should always test the result for nuLL and take recovery action. The size actually allocated may be a few bytes more than that requested (see p_alen). The maximum size is 64K minus the combined size of the machine stack and the space taken by static variables. 2Unfortunately, the system does not distinguish between the two causes of allocation failure. 7-5 PLIB REFERENCE If the heap is corrupt, calling p_alloc may or may not detect it - but if it does it will call p_panic. Use p_allchk to check the integrity of the heap thoroughly. The function £_alloc is identical except that it calls p_leave (E_GEN_NOMEMORY) rather than return NULL. Example GLDEF_C TEXT *AllocString(TEXT *str) /* Allocate and copy in a zero terminated string. a7 { TEXT *p; if (p=p_alloc(p_slen(str)+1) ) p_scpy(p,str); /* Copy in the string */ return (p); } This example assumes that any more specific error recovery is handled by the caller. p_free Free an allocated cell VOID p_free(VOID *pcell); Free the allocated memory cell at address pce11, returning the cell to the free memory list. Does nothing if pcell is zero - this is sometimes useful in error clean-up situations. If pcell is non-zero, it should contain the address of a cell as returned by, for example, p_alloc. Passing a value that is not the address of an allocated cell (eg by freeing a cell twice) will corrupt the heap. There is a chance that p_free will detect a bad address and call p_panic. p_realloc (or f_realloc) Change cell size VOID *p_realloc(VOID *pcell, UINT size); VOID *f_realloc(VOID *pcell, UINT size); Change the size of the allocated cell pce11 to be size bytes and return the address of the new cell or NuLL if there was insufficient space for the size change. If nuxt is returned, the original cell is unaffected. The limits on size are as for p_alloc. Calling p_realloc(pcell,size) when pcell is zero is equivalent to calling p_alloc(size) - this can be useful in start up situations. The cell retains its original content which is truncated if the cell size is reduced. The cell start address does not change when the cell size is reduced or stays the same. If the cell size is increased, p_realloc will use any trailing free space of sufficient size but, more likely, it will allocate a new cell, copy the data across and free the old cell where, in this case, the returned address is different from pce1l. If pcell is neither zero nor the address of an allocated cell, the heap will either be corrupted or p_panic will be called. The function £_realloc is identical except that it calls p_leave (E_GEN_NOMEMoRY) rather than return NULL. p_adjust Insert or delete data in cell VOID *p_adjust (VOID *pcell, UINT offset, INT amount); Open or close a gap at offset offset within the allocated cell pce1l, using p_realloc to make the appropriate change to the cell size. As for p_realloc, p_adjust returns the address of the new cell or NULL if there was insufficient memory. If amount is positive, a gap of amount bytes is opened. If amount is negative -amount bytes are deleted. If amount is zero, the function has no effect and returns pcell. Unlike p_realloc, pcell may not be passed as NULL. If pce11 is not the address of an existing allocated cell, the heap will either be corrupted or p_panic will be called. If amount is negative, -amount bytes is deleted by shifting the trailing contents left, closing the gap. 7 MEMORY ALLOCATION old cell XXXXXXXXXXKZZZZZZZZZZYYYVYVYYVYYVYY < offset >< amount > new cell XXXXXXXXXXYYVYVVVVVVVVYYVY < old size - amount > The cell size is decreased by the same amount. There is no data deletion if the offset is greater than or equal to the original cell size. The minimum cell size actually allocated is as for p_alloc. If the new size would be negative, p_panic is called. If amount is positive, the cell size is increased and a gap is then inserted starting from the specified offset by shifting the trailing contents right. old cell XXXXXXXXXXYYYYVVVVVYVYVYYVY < offset >< amount > new cell XXXXXXXXXXKZZZZZZZZZZYVYYVVYVYYVYY < old size + amount > If offset is greater than or equal to the original cell size, no shifting takes place. A typical use is to insert or delete a record (which may be of fixed or variable length) in a contiguous sequence of records contained in an alloc cell. p_alen Get cell length UINT p_alen(VOID *pcell); Return the length in bytes of the allocated cell pceil. The returned cell length will be equal to or slightly larger than that requested using p_alloc Of p_realloc because (1) sizes are rounded up to an even size, (2) there is a minimum cell size and (3) the allocation can't leave a trailing free space cell below the minimum size. If the passed address is not the address of a cell, there is a chance that p_alen will detect it and call p_panic. p_hgran Set heap granularity VOID p_hgran(UINT nparas); Set the heap granularity to nparas (measured in paragraphs where a paragraph is 16 bytes). The maximum value for nparas i$ E_MAXx_GROWByY (16K bytes) - p_hgran calls p_panic if this is violated. Processes are created with heap granularity =_GRowBy_DEFAULT (2K bytes). z_max_GRrowBy and E_GROWBY_DEFAULT are defined in epoc.h. The heap granularity controls the increment by which the heap grows to satisfy an allocation request that cannot be met from the existing free space list. The granularity is added to the amount that is just sufficient to satisfy the request. Growing the heap can be computationally expensive because many other segments may have to be shifted by the growth of the process data segment. Applications that can rapidly create large data structures (such as when loading a large file in the text processor) can improve their performance by setting a larger heap granularity. Setting the heap granularity has no affect on the way the system shrinks the heap to release memory. p_allwalk Visit all cells VOID p_allwalk(VOID (*fptr) (VOID *fpar,INT isalloc,UINT len), VOID *fpar); Walk through every cell in the heap (whether allocated or free) in sequence from low to high address and, if fptr is not nut, call fptr for each cell. The heap is checked for consistency and p_panic is called if an inconsistency (for example an overlap between a free cell and an allocated cell) is detected. In the call to fptr, the parameter isalloc 1s TRUE if the cell is an allocated cell and ratss if it is a free cell. The parameter 1en is the total length of the cell in bytes (1en includes the size of cell header information and is 2 greater than that returned by p_alen). 7-7 PLIB REFERENCE Cell addresses can be calculated from the cell lengths and the heap start address. The heap start address may be found by calling p_allspce. This function is provided for heap diagnosis and is called, for example, by p_allchk. In the following example, NumallocCel1s returns the number of cells allocated: LOCAL_C VOID CountIfAlloc(UINT *pn, INT isalloc,UINT len) { if (isalloc) *pnt=1; } GLDEF_C UINT NumAllocCells () ‘i UINT n; n=0; p_allwalk((VOID (*) (VOID *,INT,UINT) )CountIfAlloc, &n); return (n); } The call to p_allwalk calls back count If£A1lloc (which simply increments the allocated cell count if the cell is an allocated cell) for each cell in the heap. p_allchk Check heap integrity VOID p_allchk (INT num); Walk through all the allocated cells checking for consistency with the free space list. Call p_panic (0xff) if there is something wrong. Used as a debugging aid to detect errors from freeing a cell twice or overwriting the boundaries of an allocated cell. Otherwise, such errors can remain undetected for some time. If it does discover something wrong, and before calling p_panic, it writes 3 words of diagnostic information to reserved statics, as follows: DatApp1 (0x28) a reason code, described below DatApp2 (0x2a) the address at which the corruption was discovered DatApp3 (0x2c) the passed parameter nun, to enable identification of the offending call The reason code is one of: 5 a free cell pointer was probably overwritten 6 a cell length is too small or odd (possibly because it was overwritten) 7 an allocated cell length is too large (possibly because it was overwritten) This information is only useful in conjunction with a debugging tool that allows the process data segment to be examined after a call to p_panic. p_allspc Get heap address and potential free space UINT p_allspc(VOID **pheap) ; Return the potential free space in the heap in bytes and writes the start address of the heap to *pheap. The potential free space is calculated as the sum of the free cells in a process data segment that has been expanded to its full size of 0x££e0 bytes. In practice, the amount that can be allocated will certainly be less than this and depends upon how fragmented the heap is and on the amount of free memory in the system. This function should not be used to predict a successful allocation but it can be used to predict an unsuccessful one. The heap start address *pheap can be used to turn the cell lengths produced by p_allwa1k into addresses. 7-8 7 MEMORY ALLOCATION a aa System memory usage p_getram Get addressable system RAM size UINT p_getram(VOID); Return the size of the addressable system RAM in paragraph (16 byte) units. Since the memory in machines containing more than 512 kilobytes of RAM is bank-switched, this function will never return a value larger than 32768 (corresponding to 512 kilobytes of RAM). p_totalK Get total system RAM size UINT p_totalK (VOID); This function is only available in EPOC version 3.50 or later. Return the total amount of memory in the machine in kilobytes. This function reports the total amount of RAM present in the machine, irrespective of bank-switching. Thus, on a machine containing 1 megabyte of RAM, a call to p_totaix will return the value 1024, whereas a call to p_getram on the same machine will return 32768 (corresponding to 512 kilobytes). p_sgfree Get size of available segmented memory UINT p_sgfree(VOID); Returns the amount of available (i.e. currently unused) addressable segmented memory in paragraph (16 byte) units. Since the memory in machines containing more than 512 kilobytes of RAM is bank-switched, this function will never return a value larger than 32768 (corresponding to 512 kilobytes of RAM). The value returned should be treated with some caution as the amount of available memory in a multi- tasking environment is a dynamic function of the memory requests of all the currently running processes. p_sgramdisk Get memory used by internal RAM disk UINT p_sgramdisk (VOID) ; -Returns the number of 16-byte paragraphs in the addressable RAM that are currently used by the internal RAM disk (LOC::M:). On machines containing more than 512 kilobytes of RAM, the RAM disk will be created in an upper bank. In this case, unless the RAM disk overflows into the addressable RAM, p_sgramdisk will generally return zero. On machines containing not more than 512 kilobytes of RAM, the return value will be larger than that obtained from p_dinfo, which measures storage capacity rather than the total number of bytes used. EE Memory segments Applications that need more memory to store data, can allocate one or more external memory segments. Each segment can be up to 512K bytes long, subject to the availability of free system memory. The most common use of the functions described in this section is to implement a potentially large data structure without being constrained by the 64K (or less) limit of the heap. To create and access a data structure in an external data segment, you would use: p_sgcreate to create an external data segment of a specified initial size p_sgcopyto to write to the external segment p_sgcopyfr to read from the external segment p_sgadjust to adjust the size of the data segment (say to increase its size to accommodate additional content) p_sgdelete to delete the data segment after it is no longer required 7-9 PLIB REFERENCE Memory segments can be used to implement a data structure that is accessed by more than one process. For example, a "pipe" in which one process creates a data segment and writes to it and where a second process reads from it. In this case, the second process would use: p_sgfind to locate the pipe by its memory segment name p_sgopen to open the segment p_sgcopyfr to read from the segment p_sgclose after finishing with the segment You would need to use an associated semaphore to synchronise access to the segment (as described in the chapter Asynchronous Requests and Semaphores). If a memory segment is locked by a process calling p_sglock, the segment will survive the demise of the creating process p_sgcreate Create memory segment HANDLE p_sgcreate(TEXT *pName, INT nParas, INT uMode) ; Creates a memory segment with the zero terminated name pName and size nParas (in 16-byte paragraphs) and, if successful, return the positive handle to the created segment. Otherwise, it returns one of the following negative error numbers: E_GEN_NOMEMORY Not enough memory to satisfy the request E_GEN_NOSEGMENTS No memory segment handles are available E_FILE_EXIST A memory segment of the requested name already exists E_FILE_NAME The requested name is invalid The memory segment created is not initialized in any way and will contain random data. The returned handle allows access to the contents of the segment using p_sgcopyto and p_sgcopyfr. After being created, the memory segment is automatically opened and given a usage count of 1. It should be closed when no longer required by calling p_sgclose. When a process terminates, any open memory segment is automatically closed, decrementing the segment usage count. If the access count become zero or negative, the segment is deleted. The initial size nParas (in 16-byte paragraphs) must be positive (so the maximum segment size is 512K bytes). The parameter uMode should be one of the following: E_SEGMENT_HIGH to create a dynamic memory segment. E_SEGMENT_LOW is provided for future expansion and currently has the same effect as E_SEGMENT_HIGH. E_SEGMENT_DEVICE to create a device segment. This will result in all devices being held while memory is moved and then resumed. All dynamic segments will be moved up in memory to make room. This service is called, for example, by the File Server when loading external device drivers and should not be used by applications. E_SEGMENT_LOCKED is the same as E_SEGMENT_HIGH except that no process owns the created segment. Like E_SEGMENT_DEVICE this mode is used internally by the operating system and should not be used by applications. The function calls p_panic if the requested size was negative or if uMode was not one of E_SEGMENT_LOW, E_SEGMENT_HIGH, E_SEGMENT_DEVICE Or E_SEGMENT_LOCKED. 7-10 7 MEMORY ALLOCATION p_sgdelete Delete memory segment INT p_sgdelete (TEXT *pName) ; Delete the memory segment identified by the zero terminated name pName. Returns zero if successful or one of the following negative error numbers: E_GEN_INUSE the segment usage count is greater than zero (eg because it is opened by another process) E_FILE_NXIST the memory segment does not exist E_FILE_NAME the memory segment name is invalid p_sgopen Open memory segment HANDLE p_sgopen(TEXT *pName) ; Open the memory segment identified by the zero terminated name pName and, if successful, return the handle to the opened memory segment. Otherwise, it returns one of the following error numbers: E_FILE_NXIST the memory segment does not exist E_GEN_OPEN the memory segment is already open to this process E_FILE_NAME the memory segment name is invalid The returned handle allows access to the contents of the segment using p_sgcpto and p_sgepfr. Opening a segment increments the segment usage count. When a process terminates, any open memory segment is automatically closed, decrementing the segment usage count. If the access count becomes zero or negative, the segment is deleted. There is no limit on the number of memory segments that may be opened by a process. p_sgcopyto Copy to memory segment INT p_sgcopyto(HANDLE nHandle, LONG pos, VOID *source, UINT len); Copy len bytes from source in the current process data segment to offset pos in the open memory segment nHandle (as returned from p_sgcreate OF p_sgopen). The system ensures that the copy is not interrupted by another process. Address trapping is automatically switched off for the duration of the copy. The return value has no significance. The function calls p_ panic if pos+ien is greater than the size of the memory segment or if nHandle is not a valid memory segment handle. A program can check that the segment is still in existence before a call to p_sgcopyto by calling p_sgopen. If you want to write to a process data segment, it is more convenient to use p_pcpyto, which takes a process ID rather than a segment handle. p_sgcopyfr Copy from a memory segment INT p_sgcopyfr(HANDLE nHandle, LONG pos, VOID *target, UINT len); Copy len bytes from offset pos in the open memory segment nHandle (as returned from p_sgcreate or p_sgopen) tO target in the current process data segment. The system ensures that the copy is not interrupted by another process. The return value has no significance. The function calls p_panic if pos+1en is greater than the size of the memory segment or if nHandle is not a valid memory segment handle. 7-11 PLIB REFERENCE A program can check that the segment is still in existence before a call to p_sgcopyfr by calling p_sgopen. If you want to copy from a process data segment, it is more convenient to use p_pcpyfr, which takes a process ID rather than a segment handle. p_sgsize Get size of memory segment UINT p_sgsize (HANDLE nHandle) ; Returns the size (in 16-byte paragraphs) of the open memory segment nHandle (as returned from p_sgcreate Or p_sgopen). The function calls p_panic if nHandle is not a valid memory segment handle. p_sgadjust Adjust the size of a memory segment INT p_sgadjust (HANDLE nHandle, INT nParas); Adjust the size of the open memory segment nHandle (as returned from p_sgcreate or p_sgopen) to nParas 16-byte paragraphs. Return zero if successful or the negative E_GEN_NomEmory if there is not enough memory to satisfy the request. The new segment size nParas must be positive (it follows that the maximum segment size is 512K bytes). Setting nParas to zero discards all the memory allocated to the memory segment but does not delete the segment. The function calls p_panic if nParas is negative or if nHandle is not a valid memory segment handle. p_sgfind Find segments by name HANDLE p_sgfind(HANDLE fHandle, TEXT *pMatch, TEXT *pName) ; Write the next segment name that matches the zero terminated match string pMatch as a zero terminated string to pName where fHandle is NULL for the first call and is subsequently the positive return value from the previous call. When there are no further segments matching pMatch, it returns E_FILE_NXIST. Used repeatedly to find all the segments that match the wild card string pointed to by pMat ch. The buffer at pName should be big enough to receive E_MAX_NAME+2 bytes. The wild card string pMatch should remain the same between successive calls. No memory is used by this service and it can be abandoned at any time without taking any further action. The function calls p_panic if fHandle is not a valid memory segment handle. Example GLDEF_C VOID ListCodeSegments (VOID) { HANDLE fH; TEXT buf [ E_MAX_NAME+2]; £H=NULL; while ((fH=p_sgfind(fH,"*.$SC", &buf[0]))>0) p_puts (&buf[0]); p_sgclose Close memory segment INT p_sgclose (HANDLE nHandle); Close the open memory segment nHandle (as returned from p_sgcreate or p_sgopen) decrementing the usage count. Returns zero if successful or the negative E_GEN_NOTOPEN if the memory segment is not open to this process. Should the access count become zero or negative, the segment is deleted. The function calls p_panic if nHandle is not a valid memory segment handle. 7-12 7 MEMORY ALLOCATION p_sglock Increment segment usage count VOID p_sglock (HANDLE nHandle); Lock the open memory segment nHand1le (as returned from p_sgcreate Of p_sgopen) by incrementing the segment usage count. If p_sglock is called after creating a segment, the segment will not be deleted when the process terminates. The function calls p_panic if nHandle is not a valid memory segment handle. p_sgunlock Decrement segment usage count VOID p_sgunlock (HANDLE nHandle) ; Unlock the open memory segment nHandle (as returned from p_sgcreate Of p_sgopen) by decrementing the segment usage count. There is no harm in unlocking a segment that is already unlocked, although if this done inadvertently the segment could be deleted by another process. The function calls p_panic if nHandle is not a valid memory segment handle. Environment variables The system allocates up to 4K bytes for environment variables at the high address end of the system RAM. Environment variables are a scarce resource and should be used sparingly. An environment variable consists of: a name of up to —_MAx_ENv_s1zkE (16) bytes containing any byte except '*' or '2! a value of up to P_ENvmax-1 (256) bytes with no restriction on the content Each environment variable is stored as two successive leading byte count strings (see the example in the description of p_findenviron, below). If you are dealing with environment variables where both the names and the values are character strings you can use: p_getenv to get the value of an environment variable p_setenv to replace the value of an existing environment variable or to create one if necessary p_delenv to delete an environment variable p_fndenv to get a list of environment variables and their values A more general but less convenient set of environment variable functions (which can be used, for example, when the values are binary) are: p_getenviron to get the value of an environment variable p_setenviron to replace the value of an existing environment variable or to create one if necessary p_delenviron to delete an environment variable p_findenviron to get a list of environment variables and their values 7-13 PLIB REFERENCE p_getenv Get environment variable value INT p_getenv(TEXT *pMatch, TEXT *pValue) ; Copy the value of the environment variable that matches the zero terminated name pMatch to pValue and add a zero terminator to the end of the copied value. The name pMatch may include the wild card characters '?' and '*' in which case the value of the first matching name is copied. Returns zero if successful or the negative E_FILE_NxIst if no matching environment variable exists. The maximum length of an environment variable value is P_ENvMax-1 bytes so up to P_ENvmax bytes can be written to pvalue (including the zero terminator). p_getenviron Get environment variable value INT p_getenviron(TEXT *pMatch, INT mLength, VOID *pValue); Copy the value of the environment variable that matches the name pMatch of length mLength to pvalue and return the number of bytes copied. Return the negative E_FILE_Nx1st if no matching environment variable exists. The name pMatch may include the wild card characters '?' and '*' in which case the value of the first matching name is copied. The maximum length of an environment variable value is P_ENvmMax-1 bytes. p_setenv Set environment variable value INT p_setenv (TEXT *pName, TEXT *pValue) ; Copy the content of the zero terminated string pvalue (excluding the terminating zero) into the value of the environment variable with the zero terminated name pName, replacing any previous value. If the environment variable does not exist it is created with the specified name and value. The name pName may not include wild cards and must not exceed E_MAX_ENV_s12E in length. The length of pValue should not exceed P_ENvmax-1 (255) - any non-zero value in the high byte of the length of pValue is ignored. Returns zero if successful or one of the following negative error numbers: E_GEN_NOMEMORY the system was unable to allocate room to store the environment variable and its value either because there is not enough free system memory or because of the 4K limit on the environment variable space E_GEN_FAIL if pName contains a wild card character The function calls p_panic if the length of pName exceeds E_MAX_ENV_SIZE. Note that you can't delete an environment variable by giving it a null value - use either p_delenv or p_delenviron. p_setenviron Set environment variable value INT p_setenviron(TEXT *pName, INT nLength, VOID *pValue, INT vLength) ; Copy vLength bytes from pvalue into the value of the environment variable with name pName of length nLength, replacing any previous value. If the environment variable does not exist it is created with the specified name and value. The name pName may not include wild cards, nuength must not exceed E_MAX_ENV_SIZE and vLength should not exceed P_ENvMax-1 (255) - any non-zero value in the high byte of vLength is ignored. Returns zero if successful or one of the following negative error numbers: E_GEN_NOMEMORY the system was unable to allocate room to store the environment variable and its value either because there is not enough free system memory or because of the 4K limit on the environment variable space E_GEN_FAIL if pName contains a wild card character The function calls p_panic if nLength exceeds E_MAX_ENV_SIZE. 7-14 7 MEMORY ALLOCATION p_delenv Delete environment variable INT p_delenv (TEXT *pMatch) ; Delete the environment variable that matches the zero terminated name pMatch. The name pMatch may include the wild card characters '?' and '*' in which case the first matching environment variable is deleted. Returns zero if successful or the negative =_rF1LE_nxist if no matching environment variable exists. p_delenviron Delete environment variable INT p_delenviron(TEXT *pMatch, INT mLength) ; Delete the environment variable that matches name pmatch of length mLength. The name pMatch may include the wild card characters '?' and '*' in which case the first matching environment variable is deleted. Returns zero if successful or the negative —_FILE_nxist if no matching environment variable exists. p_fndenv Find environment variables INT p_fndenv (TEXT *pMatch, TEXT *pName, TEXT *pValue, HANDLE *pHandle); Called repeatedly to find the name and value of all environment variables that match the zero terminated wild card name pMatch. On the first call *pHandie should contain zero and subsequent calls pass the value that is written by the previous call. The function returns zero if a matching environment variable was found or the negative E_FILE_EOF when there are no more matching names. The wild card match string pMatch should remain the same on successive calls. Each successful call writes the name as a zero terminated string to pName (which should have room for E_MAX_ENV_S1ZE+1 bytes) and its value followed by a zero terminator to pvalue (which should have room for p_ENvMax bytes). A wild card name of "*" will match all the environment variables. p_findenviron Find environment variables INT p_findenviron(TEXT *pMatch, INT mLength, UBYTE *pBuf, HANDLE *pHandle); Called repeatedly to find the name and value of all environment variables that match the wild card name pMatch of length mLength. On the first call *pHandie should contain zero and subsequent calls pass the value that is written by the previous call. The function returns zero if a matching environment variable was found or the negative E_FILE_EOF when there are no more matching names. The wild card match string should remain the same on successive calls. Each successful call writes the name and value to pBuf as two successive leading byte count strings giving the environment variable name followed by its value. The maximum length of an environment variable name is E_MAX_ENV_s1zeE and the maximum length of a value is p_ENvmax-1 bytes so pBuf should have room for E_MAX_ENV_SIZE+P_ENVMAXx+1 bytes. A wild card name of "*" will match all the environment variables. 7-15 PLIB REFERENCE In the following example, PrintaAllEnv prints the name and (potentially binary) value of all the environment variables: LOCAL_C VOID PrintData(TEXT *p,UINT len) { UBYTE *pe; p_print ("sd [",len); for (pe=ptlen;p", *p) ; GLDEF_C VOID PrintAllEnv (VOID) { UBYTE *p; HANDLE h; UBYTE b[E_MAX_ENV_SIZE+P_ENVMAX+1]; for (h=0;p_findenviron("*",1,&b[0],&h) >=0;) { p=é&b[0]; PrintData(pt1,*p); /* print name */ pt=*pt+1; PrintData(pt+l,*p); /* print value */ } 7-16 CHAPTER 8 ASYNCHRONOUS REQUESTS AND SEMAPHORES This chapter describes asynchronous requests, semaphores, the I/O semaphore and wait handlers. Semaphores Semaphores are provided to synchronise cooperating processes (where, in this context, a process includes a hardware interrupt). There are three common uses: e Synchronising access to a shared resource e —Synchronising supplier-consumer relationships e Synchronising the completion of asynchronous requests The first two are described briefly in this section. The third use is far more important and is discussed more extensively in the following section. The semaphores in EPOC are counting semaphores, having a signed value that is incremented by calling p_signal and decremented by calling p_wait. A semaphore with a negative value implies that a process must wait for the completion of some other event, such as the freeing of a shared resource. The mechanism by which a process waits on a semaphore is part of the overall management of process scheduling. Process scheduling In EPOC, if a process is not the currently running process, it is either suspended or in a queue. A process remains suspended until it is resumed by another process (typically its creator) by that process calling p_presume. If a process is not suspended, it is in one of the following three types of queues: The ready queue The ready queue contains processes ordered by process priority. On a reschedule, the process at the high priority end of the ready queue runs. If there is more than one ready process at the highest priority, they take it in turns to run every 4 system ticks. The time delta queue There is a single (possibly empty) time delta queue, effectively containing both processes and timer device entries. The processes are waiting for a relative or an absolute time as a result of calling p_sleep, p_sleept or p_sleepa. The timer device entries are associated with processes that have requested an asynchronous timer. The head of the queue has its delta time decremented every system tick and is removed when it reaches zero or negative. If it is a process, it is inserted into the ready queue. If it is a timer device entry, the associated process I/O semaphore is signalled. The semaphore queues _ There is a (possibly empty) queue of processes for each created semaphore in the system. If, on calling p_wait, the decremented semaphore is negative, the calling process is placed at the end of the appropriate semaphore queue. When that semaphore is subsequently incremented by a call to p_signal, the process at the head of the semaphore queue is either made current (if it has the highest priority) or it is inserted into the ready queue (after any processes of equal priority). 8-1 PLIB REFERENCE See also the description of Preemptive scheduling in the chapter Processes and Inter-process Messaging. Shared access A mutual exclusion semaphore may be used to serialise access to a shared resource (for example, a shared memory segment). The creator of the shared resource uses p_semcrt to create an associated mutual exclusion semaphore with an initial value of one. Any process wishing to access the resource first calls p_wait on the resource semaphore and then calls p_signa1 after completing its access. Because the semaphore was created with an initial value of one, the first process to call p_wait will return immediately but any other processes that call p_wait will wait in the semaphore queue. Waiting processes are released on a first-in first-out basis when the process currently accessing the resource calls p_signal. The nature of the shared resource should be such that any access completes in a relatively short time so that processes do not wait for extended periods on the mutual exclusion semaphore. Another consideration is that whereas the resource may survive the demise of its creating process, semaphores are automatically deleted when the creating process terminates. In many cases, you may find that EPOC is better suited to supporting the use of server processes for serialising access to a shared resource (as in, for example, the file server and the window server) rather than using a mutual exclusion semaphore. The design of EPOC's inter process messaging was largely driven by the requirements of server processes and their clients. Supplier-consumer In this usage, the semaphore is associated with a pool of data (say a circular list in a shared data segment) and is created with an initial value equal to the number of elements in the pool. The consumer calls p_wait when it is ready to extract an item from the pool and the supplier calls p_signa1 after it inserted an item into the pool. If there are one or more elements in the pool, the consumer's call to p_wait returns immediately. Otherwise, the call to p_wait returns when the supplier inserts an element and calls p_signal. Asynchronous requests Many system services are implemented in two steps: e make the service request e wait for the requested operation to complete In most cases, as well as providing functions for each step, the system provides a function containing both the above steps. Such functions are called synchronous because they automatically synchronise the requesting process by waiting until the operation has completed. The internal function that makes the request without waiting for completion is called an asynchronous function. Examples of asynchronous request functions are: p_ioa, p_ioc for requests on an open I/O channel p_mreceive to receive an inter process message p_execcasync for loading images p_logona for being informed of a process termination There are also synchronous versions of all the above functions except p_logona (but see the example below). Applications use asynchronous requests in situations like the following: e make request A e make request B e wait for either of the requested operations to complete Processes wait for the completion of asynchronous requests by waiting on their //O semaphore where each request is associated with a status word. 8-2 8 ASYNCHRONOUS REQUESTS AND SEMAPHORES The I/O semaphore When a process is created, the system automatically creates an I/O semaphore on its behalf (a more accurately descriptive name would have been the asynchronous request semaphore). After making one or more asynchronous requests, a process calls p_iowait to wait on the I/O semaphore for one of the requests to complete. A typical application process spends most of its time waiting on its I/O semaphore. For example, an interactive application process that is waiting for user input from the window server is waiting on the I/O semaphore. The process or the hardware interrupt handler that implements the requested operation typically uses p_iosignalbypid to indicate that the operation has completed. If one or more wait handlers have been installed (wait handlers are described below), they may process the signal and re-signal using p_iosignal. In some cases, it is convenient for the requestor to use p_iosignal to signal itself and to subsequently process that signal in a central call to p_iowait. Status words Although the parameters to asynchronous request functions vary, they all take the address of a signed 16-bit status word which subsequently contains the status of the requested operation. All asynchronous requests exhibit the following behaviour: e While the request is pending, the status word contains the negative E_FILE_PENDING (defined in p_file.h) e When the operation has completed, a value other than =_rFILE_PENDING Is written to the status word. This value should be zero or positive to indicate success or a negative error number to indicate failure. e The requesting process's I/O semaphore is signalled (after the status word has been written). Making a request while a previous request on the same status word is still pending will normally result in a call to p_panic. When there are multiple requests, each request is associated with a different status word. After returning from p_iowait, the caller typically polls each status word until one is found that contains other than E_FILE_PENDING. That completion is then processed (which might include renewing the asynchronous request) and p_iowait is called again to process the next completion. Every p_iosignai should be matched by a call to p_iowait (or a function that calls p_iowait). The status word associated with the p_iowait must have completed (ie must contain a value other than E_FILE_PENDING) at the time that p_iosignal is called. A common programming error is to introduce a p_iosignal without correctly associating it with a status word, such that the poll after the p_iowait cannot find a completed status word. At Psion this is known as a "stray signal" and programmers should detect this as early as possible by (say) calling p_panic when the poll is unable to find a completed status word. For Series 3, Series 3a and Workabout developers, using the Spy application supplied with this SDK may help identify an accumulation of such unused signals. In the following example, the opened asynchronous timer TimerChannel is used to construct a synchronous function which attempts to write the passed string to the opened serial channel SerialChannel. If it takes more that 5 seconds to complete the write, the function calls p_leave (SERIAL_TIMEOUT) . For simplicity it is assumed that there are no other outstanding events which could complete. Thus it is certain that on return from the call to p_iowait, one of the two asynchronous requests has completed. 8-3 PLIB REFERENCE LOCAL_C VOID StringToSerial (TEXT *str) { UWORD len; WORD TimerStatus; WORD SerialStatus; ULONG timeout; len=p_slen(str); p_ioc(SerialChannel, P_FWRITE, &éSerialStatus, str, &len) ; timeout=50; /* 5 second timeout */ p_ioc(TimerChannel, P_FRELATIVE, &TimerStatus, &timeout) ; p_iowait (); if (SerialStatus==E_FILE_PENDING) { /* must have timed out */ p_iow(SerialChannel,P_FCANCEL) ; p_waitstat (&SerialStatus) ; p_leave (SERIAL_TIMEOUT); /* never returns */ } p_iow(TimerChannel, P_FCANCEL) ; p_waitstat (&TimerStatus) ; } The functions p_ioc and p_iow are described in the next chapter: /O System. Cancelling an asynchronous request In the above example, the asynchronous request which does not complete is cancelled by making a P_FCANCEL request using p_iow (the synchronous version of p_ioc or p_ioa). Most asynchronous request functions have an associated cancel function; the P_FCcANCEL, to cancel I/O requests on a device channel, is one example. Other examples are: p_mcancel to cancel a call to p_mreceive to receive an inter process message p_logoffa to cancel a call to p_logona for being informed of a process termination The following general principles apply to all functions that cancel an asynchronous request: e the cancel precipitates the completion of the operation (it does not stop the operation from completing) e the cancel may or not be effective (that is, the operation may complete naturally before the cancel is processed) e after a cancel, you must still process the completion of the asynchronous request (typically by immediately calling p_waitstat to "use up" the signal) Waiting for a particular completion When waiting for the completion of a particular asynchronous request, the wait on the I/O semaphore must be sure that it is not fooled into a premature return by the completion of any other pending asynchronous request. This is done by calling p_waitstat which behaves in a similar way to p_iowait except that it only returns when the associated status word is other than E_FILE_PENDING. In general, p_waitstat is a safer option than p_iowait to "use up" the signal resulting from the cancelled operation. If the cancel is not immediately effective and another completion causes p_iowait to return, the program could continue and make another request before the cancelled operation completes (which would result in p_panic being called). The above example illustrates the technique although, in this case, it is not strictly necessary since there can be no confusion as to which event has completed. 8-4 8 ASYNCHRONOUS REQUESTS AND SEMAPHORES Constructing synchronous functions General purpose functions that provide a synchronous interface must use p_waitstat rather than p_iowait since they can not assume that there are no other pending asynchronous requests. The use of p_waitstat Is illustrated by the following example of a synchronous function: GLDEF_C INT ResumeWait (HANDLE pid) { WORD stat; INT ret; if (!(ret=p_logona (pid, &stat) ) ) { p_presume (pid) ; p_waitstat (&stat); ret=stat; } return (ret); } This is intended to be used in place of p_presume and behaves like p_presume except that it returns only when the resumed process has terminated. It effectively implements a synchronous version of p_logona. Wait handlers Wait handlers are functions that handle the completion of asynchronous requests from within p_iowait (or p_waitstat). Active wait handler functions are called just before p_iowait would have otherwise returned. Many I/O devices install a device wait handler when the device is opened (see the System Services reference manual for information on writing device drivers and I/O device wait handlers). An application can install any number of application wait handlers using p_svecadd (p_svecrem removes an application wait handler). Before p_iowait will call an installed wait handler, it has to be activated using p_sveccall (p_sveccali can also be used to deactivate a wait handler). An installed wait handler is automatically deactivated when called (which stops it being called recursively since a wait handler often itself calls p_iowait, directly or indirectly). In the following example (which does not check for errors), setupHeapChecker installs the wait handler CheckHeap which then calls p_alichk every 2 seconds or so without any cooperation from the rest of the program. typedef struct { UBYTE *Channel; WORD Status; ULONG Timeout; } HEAP_TIMER; LOCAL_C INT CheckHeap(HEAP_TIMER *pTimer) { if (pTimer->Status==E_FILE_PENDING) return (P_SIGNAL_UNUSED) ; p_allchk (0); p_ioc(pTimer-—>Channel, P_FREAD, &épTimer->Status, &pTimer-—>Timeout) ; return (P_SIGNAL_ENABLE) } GLDEF_C VOID SetupHeapChecker (VOID) { HEAP_TIMER *pHeapTimer; pHeapTimer=p_alloc(sizeof (HEAP_TIMER) ); p_open (&pHeapTimer->Channel,"TIM:",-1); pHeapTimer->Status=0; pHeapTimer->Timeout=20; /* 2 second tick */ p_sveccall (p_svecadd (CheckHeap, pHeapTimer) , TRUE) ; } Wait handlers are only called when the process calls p_iowait (or a function such as p_waitstat that calls p_iowait). While an application performs a computationally intensive task that takes an extended time, it should consider calling p_ioyieid (which effectively calls p_iosignal1 followed by p_iowait) to allow any installed wait handlers to be called. Application programs must never assume that no wait handlers have been installed. PLIB REFERENCE Installed device wait handlers and application wait handlers are represented as a doubly linked queue of data structures allocated out of the process heap. The queue is built from a 4-byte queue header in a reserved static at address 2 in the process data segment. If this reserved static is corrupted (say because of a write using an uninitialised pointer that happens to have a low value), p_iowait will almost certainly detect an invalid wait handler and call p_panic (27) although the cause of the panic may have nothing to do with wait handlers. Polling rather than waiting In a multi-tasking operating system it is extremely anti-social to wait for an operation to complete by polling the status word in a tight loop rather than call p_iowait (because the polling will "hog" the processor to no benefit). However, when there is useful work to be done between each poll, it can be appropriate to poll - for example, to check periodically for user input while performing an extended calculation. If this approach is used, you should be aware that in some cases asynchronous requests are completed by a wait handler and it is necessary to call p_ioyield before each poll to give any wait handlers a chance to run. This case occurs when making requests on a device driver that services hardware interrupts. For example, the serial port driver services the hardware interrupt generated by the receipt of a serial frame. A hardware interrupt handler cannot write directly to the data segment of the requesting process because that data segment may be moving when the interrupt occurs. Instead, the interrupt handler must write first to a fixed memory location (either in the operating system variables if it is an in-built driver or in a device segment if it is an external driver) and then signal the I/O semaphore of the requesting process. When the requesting process next calls p_iowait (directly or indirectly through say p_ioyield), the device driver's wait handler is called to copy the data safely to the process data segment. Device drivers that are implemented by a server process (for example, the window server) do not require a wait handler to complete operations. When a poll detects a completed status word it is still obligatory to "use up" the signal by calling p_iowait (otherwise you will get a "stray signal" later). Attached I/O devices Wait handlers are often used in attached I/O drivers that layer over an existing driver (which may be another attached driver or a hardware device driver) to extend or modify its services. The attached driver typically installs a wait handler to handle the completion of requests on the underlying driver. Although attached drivers can be written in C, the interface between the I/O system and the functions that are called requires some assembly language programming. See the //O System chapter for more information on attached drivers. EEE Primitive semaphore functions p_semcrt Create a semaphore HANDLE p_semcrt (INT nCount) ; Create a semaphore with an initial positive count ncount. Returns the handle of the semaphore if successful, or E_GEN_NosEM if no semaphores are available. The created semaphore is owned by the calling process and, in version 3 and later of EPOC, may be deleted using p_semde1 before exiting. However, the semaphore will always be automatically deleted on termination of the process. Calls p_panic if ncount is negative. p_semdel Delete a semaphore VOID p_semdel (HANDLE sHandle); Delete semaphore sHandle. Any processes waiting on the semaphore are automatically signalled. Calls p_panic if sHand1e is not the handle of a previously created semaphore. 8-6 8 ASYNCHRONOUS REQUESTS AND SEMAPHORES Prior to version 3 of EPOC the recommended action is not to delete a semaphore, but to let the operating system perform any necessary clean-up actions on termination of the application. This is an acceptable solution for all versions of EPOC. Except in extreme cases, where large numbers of semaphores are created, there is no need for an application ever to call p_semdel. p_wait Wait on a semaphore VOID p_wait (HANDLE sHandle) ; Decrement semaphore sHand1e by one and return immediately if it is zero or positive. If sHandle is negative after being decremented, it waits for semaphore sHandl1e to be signalled by another process or by an interrupt handler (or for sHand1e to be deleted). More than one process can be waiting on a particular semaphore at a time. When there are multiple processes waiting on a semaphore, they are released on a first-in first-out basis. If a semaphore is deleted, all processes waiting on that semaphore are released. Calls p_panic if sHandle is not the handle of a previously created semaphore. p_signal Signal a semaphore VOID p_signal (HANDLE sHandle) ; Signal semaphore sHandle, incrementing it by one. If sHandie was less than zero, the first process waiting on it is released and a reschedule takes place (before p_signal returns). Calls p_panic if sHandle is not the handle of a previously created semaphore. p_signaln Signal a semaphore n times VOID p_signaln(HANDLE sHandle, INT nTimes); Equivalent to calling p_signal(sHandle) nTimes times. Calls p_panic if sHand1e is not the handle of a previously created semaphore or if nTimes is not greater than or equal to 1. p_signalnr Signal a semaphore with no re-schedule VOID p_signalnr (HANDLE sHandle) ; Behaves as for p_signa1 except that the re-schedule does not take place. In the absence of any other cause, a re-schedule will not occur until the next system tick. A re-schedule can always be forced by calling p_sieept (OL). Calls p_panic if sHand1e is not the handle of a previously created semaphore. The I/O semaphore Although the I/O semaphore is indeed associated with I/O operations, it is not used exclusively for I/O operations. In retrospect, a more accurate name would have been the "asynchronous request semaphore". p_iosignal Signal the 1|O semaphore VOID p_iosignal (VOID) ; Increment the process I/O semaphore. Used in wait handlers to signal the completion of an asynchronous request after writing the completion status to the associated status word. It can also be used outside wait handlers to generate "internal events" where the call to p_iosignal necessarily precedes the call to p_iowait. 8-7 PLIB REFERENCE p_iosignalbypid Signal the 1O semaphore of another process VOID p_iosignalbypid (HANDLE pid); Signal the I/O semaphore of process pid. Used to signal the completion of a request from process pid (normally a different process but it still works if it is the same process). Before calling p_iosignalbypid, the process should already have set the pia's status word using say p_pepyto. p_iowait Wait on the lO semaphore VOID p_iowait (VOID) ; Wait for the I/O semaphore to be signalled (of course, it returns immediately if the I/O semaphore has already been signalled). When the I/O semaphore is signalled, any active wait handlers are called. Only when all the active wait handlers indicate that the signal has nothing to do with them (ie they all return P_stGNAL_uNUSED) will the p_iowait call return. When it does return, the signal must be associated with an external (ie outside any wait handler) status word. The application should then poll the request status words to determine which operation has completed. p_ioyield Allow any wait handlers to run VOID p_ioyield(VOID) ; Give an opportunity for any active wait handler to run. Equivalent to calling p_iosigna1 followed by a p_iowait. Any application that polls a status word for the completion of an I/O operation (presumably in between performing chunks of a computationally intensive task) should call p_ioyieid before polling to give any wait handlers (which are commonly required to complete an asynchronous request) a chance to run. p_waitstat Wait for a particular request to complete VOID p_waitstat (WORD *pstat); Wait for the particular asynchronous request associated with *pstat to complete. It is similar to p_iowait except that rather than just wait for the I/O semaphore to be signalled, it also waits until *pstat is not E_FILE_PENDING. It correctly adjusts the I/O semaphore if any other I/O requests completes in the meantime, as illustrated in the following code: GLDEF_C VOID p_waitstat (WORD *pstat) /* Wait for *pstat!=E_FILE_PENDING */ { INT i; i=(-1); do p_iowait (); itt; } while (*pstat==E_FILE_PENDING) ; while (i--) p_iosignal(); } 8 ASYNCHRONOUS REQUESTS AND SEMAPHORES The principle may be extended to wait for the completion of more than one asynchronous event. This is illustrated in the following code, which waits until both of two status words are not equal to E_FILE_PENDING: GLDEF_C VOID waitstat2(WORD *pstat1,WORD *pstat2) /* Wait until both *pstat1l and *pstat2 are not E_FILE_PENDING xf { INT i; i=(-1); do p_iowait (); Ltt; } while ((*pstat1==E_FILE_PENDING) && (*pstat2==E_FILE_PENDING) ); if (*pstat2==E_FILE_PENDING) pstatl=pstat2; p_waitstat (pstat1); while (i--) p_iosignal(); Wait handlers p_svecadd Add a wait handler function HANDLE p_svecadd(INT (*vec) (VOID *), VOID *pcb); Add function vec to the I/O semaphore wait handler list and return the non-zero handle of the wait handler if successful or zero if there is insufficient memory. The returned handle is subsequently used to activate (by calling p_sveccaii) and remove the wait handler (by calling p_svecrem). Initially the wait handler is inactive. The installed wait handler is normally activated to process the completion of one or more asynchronous requests. When the wait handler is active, the wait handler function is called from within p_iowait when the I/O semaphore is signalled. In the interests of efficiency, a wait handler should only be active while there is an associated pending request. The wait handler function should poll the one or more status words associated with the one or more requests it is monitoring and return one of the following values: P_SIGNAL_DISABLE the status word was other than &_FILE_PENDING and the completion has been processed. There are no more requests to process and the wait handler can now be deactivated. P_SIGNAL_ENABLE a status word was other than &_FILE_PENDING and the completion has been processed. However, there are still pending requests (either because the wait handler is associated with more than one request or because another request was queued) and the wait handler should remain active. P_SIGNAL_UNUSED all status words contained &_FILE_PENDING and no processing took place. The wait handler remains active. In the first two cases, where the signal is consumed, p_iowait loops back and waits on the I/O semaphore again. If the wait handler does not detect the completion of an internal request and returns p_sIGNAL_UNUSED, p_iowait will call any other active wait handlers and will only return to the caller when there are no active wait handlers or when all the active wait handlers return p_s1GNAL_UNUSED. If in the processing of the completion of one request the wait handler cancels another request, the wait handler would normally use up the signal from the cancelled requests by calling p_waitstat. 8-9 PLIB REFERENCE An active wait handler is deactivated before being called and is only reactivated when it returns with the value P_SIGNAL_ENABLE Or P_SIGNAL_UNUSED. This normally works such that any calls to p_iowait within a wait handler will not cause the same wait handler to be called recursively. Other active wait handlers may still be called within a p_iowait within the wait handler. The wait handler function vec is called with pcb as its single parameter. Where re-entrant code is required, pcb would normally be an address leading to the status word (or words) associated with the requests the wait handler is interested in. In non re-entrant code, where static data is used, it may not be necessary to use pcb. p_sveccall Activate/deactivate a wait handler VOID p_sveccall (HANDLE hand, INT isactive); If isactive is TRUE activate wait handler hand (where hand was returned by p_svecada). If isactive is FALSE deactivate wait handler nana. In the interests of efficiency, wait handlers should be deactivated when they have no pending requests to process. As well as being externally deactivated by calling p_svecca11, a wait handler can deactivate itself by returning P_SIGNAL_DISABLE. p_svecrem Remove a wait handler VOID p_svecrem(HANDLE hand) ; Remove wait handler hand (where hand was returned by p_svecadd) from the I/O semaphore wait handler list. 8-10 CHAPTER 9 /O SYSTEM This chapter describes the EPOC I/O system in general and the C functions used to access I/O devices. To use a particular device you need (also) to read a description of the device driver. The files device driver and the asynchronous timer device driver are described in this manual - in the chapters Files and Time, Timers and Dates respectively. Other device drivers are described in the J/O Devices manual. The last section of this chapter describes a set of console services for constructing rudimentary user interfaces with the minimum of effort. Worthier user interfaces may be implemented using the services described in the Window Server reference manual. I/O Device Drivers The principal purpose of device drivers is to provide convenient software interfaces that hide the internal details of the underlying hardware. For example, the software interface to the RS232 driver is independent of the interface to the underlying hardware. The fact that the RS232 hardware is different between SIBO machines and PCs is not apparent to the user of the RS232 driver. Many device drivers do not themselves interface to hardware but layer over one or more other device drivers that ultimately access the hardware. For example, the majority of the code in the RS232 driver is independent of the hardware interface and the driver is implemented in 2 layers - an upper hardware- independent layer and a lower hardware-dependent layer. Some device drivers do not access hardware at all - even indirectly. In such cases, the device driver mechanism is used to extend the services available to applications. For example, the C standard floating point library is implemented as a device driver. LDDs and PDDs In EPOC there are two types of device driver: e physical device drivers (PDDs) which are hardware dependent e logical device drivers (LDDs) which are hardware independent Applications normally interface to LDDs only. An LDD may use one or more PDDs in its implementation. For example, the RS232 driver is an LDD (the upper layer) using an appropriate PDD (the lower layer) depending on the underlying hardware. PDDs are also used by the file server system process to access different types of SSDs. Some device drivers are built into the operating system where their code is in the ROM. For example, the RS232 LDD and its PDD are normally built into the operating system whereas a bar code reader device driver is normally external and has to be loaded. PLIB REFERENCE External device drivers External device drivers are loaded from a device driver file into a device memory segment. The device driver file has the file name extension .LDD or .PDD depending on whether it is an LDD or PDD respectively. The device memory segment is allocated by the segment allocator as described in the chapter Memory Allocation. The ability to load and remove external device drivers without a system reset is a key feature of the EPOC I/O system and contrasts with most other operating systems, which require a system reset to install a device driver. On a SIBO machine you can physically attach a peripheral device (for example, a bar code wand) and load a device driver without having to exit any application processes. The I/O system also notifies devices when the machine switches off and on so that the driver can take appropriate device specific action before losing power and to recover when the power is resumed. The interface between the operating system and an LDD does not conform to a C calling convention. An LDD may be written in C but some 8086 assembly language is required to provide the LDD interface. See the System Services reference manual for more about device drivers. Opening a channel to a device The I/O device driver functions are accessed by opening a channel to the device by calling p_open and passing it a name consisting of a 3 character device name and a terminating colon. Examples of device names are: FIL: for opening a file PAR: for opening a parallel port TTY: for opening an RS232 port TIM: for opening an asynchronous timer Note that device names of external LDDs bear no relation to the file name from which they were loaded. Depending on the nature of the device, the ':' may be followed by further text. Where the device driver supports more than one unit, the device name may be followed by a unit letter. For example, "Try:a" is used to open a channel to serial port A and "Par:B" is used to open a channel to parallel port B. In the file system device r1u:, the qualifying text is normally a file name or full path name. Because the file system has more p_open calls made to it than any other device, the p_open function effectively inserts a "FIL:" before a device name if it fails to recognise a valid device name - making the leading "FIL:" optional. For example, calling p_open with the name "c:\NOTES\NEW.TxT" has the same effect as the name "FIL:C:\NOTES\NEW. TXT". Keeping the FIL: prefix removes any possibility of mistaking the file specification for a device name - for example to open a file on the default directory with file name "TTv:". The p_open function works such that a loaded device driver supersedes any existing device of the same device name. Operations on an open I/O channel Once a channel has been opened on a device, the primitive p_ioa function is used (directly or indirectly) to make an asynchronous I/O function request on the channel. Asynchronous requests are described in the chapter Asynchronous Requests and Semaphores. All I/O function requests are asynchronous in principle and the process I/O semaphore is always signalled as a result of an I/O function request. In practice, many I/O functions are implemented synchronously, which means that the I/O operation will have completed before p_ioa returns. A typical I/O device will provide zero, one, two or three truly asynchronous functions (where the request will probably not have completed before p_ioa returns) with the remaining functions provided synchronously. For example, the I/O function that closes an I/O channel is always implemented synchronously, whereas the I/O function that reads input from a device is commonly implemented asynchronously. In practice, p_ioa is often called indirectly by: p_ioc which makes an asynchronous I/O request in a way that simplifies the handling of error returns - this should generally be used in preference to p_ioa p_iow which makes the I/O request and then waits for the request to complete (by calling p_waitstat) 9-2 9 /O SYSTEM The particular I/O function requested by a call to p_ioa, p_ioc Or p_iow is specified by a function number parameter of the form p_Fxxx, defined in p_file.h. Some I/O functions are specific to a particular device (for example p_rsEtTEor, which sets the end of file position on an open file channel). Some I/O functions apply to more than one device, including: P_FREAD to read data from a channel P_FWRITE to write data to a channel P_FCLOSE to close a channel P_FCANCEL to cancel outstanding asynchronous requests on a channel P_FSENSE to sense channel characteristics P_FSET to set channel characteristics P_FFLUSH to flush out data held in buffers This manual uses the notation p_ioa(P_Fxxx), p_ioc(P_Fxxx) and especially p_iow(P_Fxxx) to refer to the corresponding function call with p_rxxx passed as the I/O function number. (In the descriptions of p_ioa, p_ioc and p_iow later in this chapter the I/O function number has the parameter name func.) The most commonly used I/O functions are supported by their own synchronous convenience functions as follows: p_close which calls p_iow (P_FCLOSE) p_read which calls p_iow (P_FREAD) p_write which calls p_iow(P_FWRITE) p_seek which calls p_iow (P_FSEEK) The functions p_close, p_read and p_write are used across many devices (p_close is applicable to all devices) and are described in this chapter. The function p_seek applies to an open file channel only and is described in the Files chapter. The file server The file server is a high priority system process (with process name SYS$FSRV) that performs all file related operations. These include those that are accessed using the I/O channel services (the r1L: device) and those that do not require a channel to be opened (eg deleting a file, making a directory). The file server is also responsible for loading executables (see the chapter Processes and Inter-Process Messages) and for loading external device drivers (described in this chapter). The file server serialises access to shared file storage devices (eg SSD drives on a SIBO machine) which may be local or remote. On SIBO machines, the file server uses PDDs extensively to access the many different types of SSD (different configurations of FLASH, RAM, ROM). The file server also uses an installable PDD to connect to remote devices via a remote file server. The file server is a process rather than an LDD because, in a multi-tasking system, more than one process can be accessing a particular file storage device at a time (at a lower level only the file server owns an SSD drive where it performs all operations on the SSD on behalf of requesting processes). Application processes that use the file server are called "clients". To become a client of the file server, a process must connect to it. Because most applications need the file server, the normal code that precedes main connects to the file server. The client processes send the file server an inter-process message to request a file server service. If the service accesses local devices, it is implemented synchronously since the file server runs at a higher priority than any application process. If the service accesses remote devices, it may be implemented asynchronously. The application programmer does not program at the message passing level but uses the interfaces provided by the ri: device and a number of ROM resident functions (most of which are described in the Files chapter). Whether provided by a function or via the r1L: device, all access to the file server ultimately involves the sending of an appropriate inter-process message. 9-3 PLIB REFERENCE Attached drivers An attached driver is an LDD using the services of an another LDD to provide a different set of services to its user. Some of the devices described in the J/O Devices manual are attached drivers. For example, the PRD: device is an attached driver, layered over a suitable print output device to provide primitive printer driver services (involving translates, preambles and postambles as driven from a .PRD file). In this case a suitable print output device is any device that supports the normal P_rwRITE operation as provided by the par:, TTY: and FIL: devices. The user of the PRD: device does the following: ¢ open the print output device using p_open (eg PAR: Or TTY:) e perform any initialisation on the channel (eg to set the Baud rate on a tty: channel) ¢ open the prp: device using p_open, attaching it to the opened print output device Once the prp: device has been opened, it replaces the P_FWRITE, P_FCANCEL and P_FCLOSE functions of the underlying device. In general, an attached driver may completely replace the original driver's functions or it may augment them (possibly also replacing or disabling some functions). It may also allow some functions through to the underlying driver or beyond. Like any other LDD, an attached driver may be written in C but some assembly language code is required to provide the LDD vector interface. See the System Services reference manual for more about writing attached device drivers. Attached drivers that provide one or more of their services asynchronously make internal asynchronous requests on the device they are attached to. Where this is the case, the attached driver processes the completion of its internal requests in code that is entered via its wait handler vector. The attached device installs its device wait handler when the channel is opened and the wait handler vector is subsequently called from within the p_iowait of the process which opened the device whenever the process I/O semaphore is signalled. Except for the way in which they are called, device wait handlers are the same as application wait handlers as described in the chapter Asynchronous Requests and Semaphores. Channel-based I/O functions p_open (or f_open) Open a channel to a device INT p_open(VOID **ppfcb, TEXT *name, UINT mode); INT f_open(VOID **ppfcb, TEXT *name, UINT mode); Open a channel to the device with the zero terminated device name name or attach driver name to an existing channel. The parameter name consists of a 3 character device name terminated by a':’ and optionally followed by further data, depending on the device. The interpretation of the mode parameter depends upon the device and some devices ignore mode. When a device ignores mode, the caller should pass a mode of -1. The files device (device name "F1L:") and the asynchronous timer device (device name "TIM:") are described in the chapters Files and Time, Timers and Dates in this manual. These devices are also described in the //O Devices manual along with many other devices. If the device name is not an attached device and it is opened successfully, the address of the channel control block is written to *ppfcb. If the open fails, *ppfcb is not written to - setting *ppfcb to zero before calling p_open can simplify clean-up code since p_close(0) has no effect and p_close can subsequently be called regardless of whether the open call was successful. If device name is an attached device, the driver control block is attached to *ppfcb - the value in *ppfcb is not changed. 9-4 9 VO SYSTEM Returns zero if successful or a negative error number if it failed. As well as device specific errors (as described in the description of each device) the following error numbers may be returned: E_FILE_ALLOC failed to allocate memory for the control block E_FILE_DEVICE the device does not exist E_GEN_ARG the value of the mode parameter was invalid (possibly as a result of falling through to the rr: device, as described next) The function £_open is identical to p_open except that it calls p_leave (passing the error number) rather than return a negative error number. If p_open fails to locate a device that matches name, then name is passed to the r1L: device driver (in which case the process must be connected to the file server). To put it another way, the leading FrL: device name is optional when opening files and, for example, calling p_open with a name of "LOC: :A:\DEF.EXT" is equivalent to a name Of "FIL: LOC: :A:\DEF.EXT" (the FIL: device is described in more detail in the Files chapter). This behaviour has an undesirable side effect: an attempt to open a device that does not exist does not give the expected &_FILE_DEVICE error since name is passed on to the Fru: device with a result that depends upon mode (with some values of mode the open might even be successful). Passing a mode of -1 is guaranteed to cause the Fru: device driver open to fail - albeit with a misleading error number (&_GEN_ARG). p_ioa Start an I/O operation INT p_ioa(VOID *pcb, INT func, WORD *pstat, ...); INT p_ioa3(VOID *pcb, INT func, WORD *pstat); INT p_ioa4(VOID *pcb, INT func, WORD *pstat, VOID *al); INT p_ioa5(VOID *pcb, INT func, WORD *pstat, VOID *al, VOID *a2); Start the I/O operation func with zero, one or two parameters on the opened channel pcb and return without waiting for the operation to complete. You can either use p_ioa, which presents the cpzct calling convention, or one of the p_ioa? variants, which uses a more efficient register calling convention. Note that it is almost always preferable to use p_ioc in preference to p_ioa. The legitimate values of func depends upon the device. The number of function parameters (zero to two) and the interpretation of the function parameters (if any) depends on the function and the device. If a function parameter exists, it is normally an address that may be used as input, output or both input and output. The function returns zero if the I/O request was started successfully or a negative error number. Returns E_FILE_INv if func is not valid for this device. Other device specific errors may be returned. When the successfully started operation completes, the process I/O semaphore is signalled and *pstat contains the completion status. While the operation is pending (ie before the I/O semaphore has been signalled), *pstat contains E_FILE_PENDING. Asynchronous requests in general are described in the chapter Asynchronous Requests and Semaphores. After the operation has completed, *pstat contains zero if the operation completed successfully or a negative error number if the operation completed with an error. The possible error numbers depend upon the device but any asynchronous operation that is successfully cancelled completes with *pstat containing E_FILE_CANCEL. Drivers normally only support one pending request per I/O operation per channel. For example, on the TTy: (serial port) device you must wait for an asynchronous write request to complete before you can make another write request on the same channel (the driver will call p_panic if this is attempted). However, it is legitimate to have one read request and one write request simultaneously pending. 9-5 PLIB REFERENCE The storage pointed to by pstat must be retained for the duration of the operation. A common error, which has disastrous results, is to allocate the status word on the stack and then to return from the function before the operation has completed - as in the following example: GLDEF_C INT DisastrousWrite(VOID *pcb, TEXT *buf) { WORD stat; UWORD len; len=p_slen (buf) ; return (p_ioa(pcb, P_FWRITE, &stat,buf, &len) ); } The following generic I/O functions (where a1 and a2 are further parameters to p_ioa) are commonly (but not always) implemented asynchronously by device drivers: P_FREAD request a read operation where a1 is the address of the buffer to take the data and a2 is the address of a worp length to read (or the maximum length to read if the device is record oriented). When the read completes, *pstat contains zero if the read was successful or a negative error number if the read failed and «a2 contains the number of bytes written to a1. P_FWRITE request a write operation where a1 is the address of the data to write and a2 is the address of a worp length to write. When the write completes, *pstat contains zero if the write was successful or a negative error number if the write failed. p_ioc Start an I/O operation with guaranteed completion VOID p_ioc(VOID *pcb, INT func, WORD *pstat, ...); VOID p_ioc3(VOID *pcb, INT func, WORD *pstat); VOID p_ioc4(VOID *pcb, INT func, WORD *pstat, VOID *al); VOID p_ioc5(VOID *pcb, INT func, WORD *pstat, VOID *al, VOID *a2); Behaves as for p_ioa except that if the I/O request func fails to start, the failure is reported as if it had started successfully but completed with that error. You can either use p_ioc, which presents the cbEct calling convention, or one of the p_ioc? variants, which uses a more efficient register calling convention. The implementation of p_ioc is effectively as follows: GLDEF_C VOID p_ioc(VOID *pcb, INT func,WORD *pstat,VOID *al,VOID *a2) { INT ret; if (ret=p_ioa(pcb, func, pstat,al,a2) ) { *pstat=ret; p_iosignal(); } } In most cases, p_ioc is preferred to p_ioa because there is only one place (*pstat) to check for an error rather than two (the return from p_ioa and *pstat). It is rarely necessary to differentiate between a failure to start the I/O operation and a failure in its completion. In the following example, WriteTimeout writes the len bytes at buf to channel pcb and returns as for p_write. However, if the write does not complete within secs seconds, the write operation is cancelled and £_FILE_CANCEL is returned. For simplicity, it assumes that completion of the write or expiry of the timer are the only two events that are expected to cause a return from the call to p_iowait. This will be true if there is no other asynchronous activity. 9-6 LOCAL_C INT WriteTimeout (VOID *pcb, UBYTE *buf, UWORD len, { WORD tstat; WORD wstat; ULONG tval; p_ioc (pcb, P_FWRITE, &wstat, buf, &len) ; tval=10L*secs; p_ioc(tcb,P_FRELATIVE, étstat, &tval) ; p_iowait (); if (tstat!=E_FILE_PENDING) /* the timer expired */ p_iow(pcb,P_FCANCEL); /* cancel write */ p_waitstat (&wstat); else if (wstat!=E_FILE_PENDING) /* the write completed */ p_iow(tcb,P_FCANCEL); /* cancel timer */ p_waitstat (&tstat); else p_panic(254); /* unexpected, unrecognised signal */ return (wstat) ; } UINT secs) 9 VO SYSTEM The call to p_iowait returns when either request completes. If the timer has completed, the write request is cancelled. Otherwise, the timer request is cancelled. If both requests have completed by the time p_iowait returns (quite possible if a higher priority process "hogged" the CPU), the cancel of the write request will have no effect and wstat will contain the completion code of the write. In the example, the static variable tcb is the channel of a previously opened asynchronous timer, as in: p_open (&tcb, "TIM:",-1); The following version of writeTimeout is more general, and caters for the presence of other asynchronous activity. LOCAL_C INT WriteTimeout (VOID *pcb, UBYTE *buf, UWORD len, { WORD tstat; WORD wstat; WORD count; ULONG tval; p_ioc (pcb, P_FWRITE, &wstat, buf, &len) ; tval=10L*secs; p_ioc(tcb, P_FRELATIVE, étstat, &tval) ; count=0; FOREVER { p_iowait (); if (tstat!=E_FILE_PENDING) { /* the timer expired */ p_iow(pcb,P_FCANCEL); /* cancel write */ p_waitstat (&wstat); break; } else if (wstat!=E_FILE_PENDING) { /* the write completed */ p_iow(tcb,P_FCANCEL); /* cancel timer */ p_waitstat (&tstat); break; } else count+=1; /* count unrecognised signals */ } while (count--) p_iosignal(); /* replace unrecognised signals */ return (wstat) ; } UINT secs) PLIB REFERENCE In such a situation, with other asynchronous activity, it may be better to handle the expiry of the timer in the main p_iowait loop. The wait for the write to complete could then be handled by: p_waitstat (&wstat) ; p_iow(tcb,P_FCANCEL); /* cancel timer */ p_waitstat (&tstat); p_iow Start an I/O operation and wait for completion INT p_iow(VOID *pcb, INT func, ...); INT p_iow2(VOID *pcb, INT func); INT p_iow3(VOID *pcb, INT func, VOID *al); INT p_iow4(VOID *pcb, INT func, VOID *al, VOID *a2); Behaves as for p_ioa except that it waits for operation func to complete and returns the completion status, providing a synchronous (as opposed to an asynchronous) interface. You can either use p_iow, which presents the cbEct calling convention, or one of the p_iow? variants, which uses a more efficient register calling convention. The code for p_iow is effectively: GLDEF_C INT p_iow(VOID *pcb, INT func,VOID *al,VOID *a2) { WORD stat; INT ret; if (!(ret=p_ioa (pcb, func, &stat,al,a2))) { p_waitstat (&stat); ret=stat; } return (ret); } Calling p_iow is simpler and requires one less parameter (since the status word is returned) than p_ioa or p_ioc and should be used in preference to p_ioa or p_ioc unless there is a need for an asynchronous request. The following generic I/O functions are essentially synchronous and are normally requested using p_iow: P_FCANCEL to cancel outstanding requests on a channel P_FSENSE to sense channel characteristics P_FSET to set channel characteristics P_FFLUSH to flush out data held in buffers p_close Close an I/O channel INT p_close(VOID *pcb); Close I/O channel pcb and return zero if successful. If pcb is NULL, just return zero. The code for p_close is effectively: GLDEF_C INT p_close(VOID *pcb) { if (!pcb) return (0); return (p_iow(pcb, P_FCLOSE) ) ; } Although p_close can return an error, it will always succeed in closing the channel (and pcb should not be used subsequently). If the device buffers written data, the close operation may need to perform one or more write operations on closing, in which case P_FCLOSE can return similar errors to P_FWRITE. However, the failure to flush the data will not (assuming a competently written device driver) cause the close operation to be aborted although the failure to flush will be reported. Carefully written applications avoid this problem by using p_FFr1uss to flush the data (and taking appropriate action if this fails) before closing the channel without risk of failure. 9-8 9 1/0 SYSTEM p_read (or f_read); Read from an I/O channel INT p_read(VOID *pcb, VOID *buf, UINT len); INT f_read(VOID *pcb, VOID *buf, UINT len); Request a P_FREAD Of up to 1en bytes of data into bur from channel pcb, wait for the request to complete and, if successful, return the number of bytes written to buf or a negative error number if the read failed. The code for p_read is effectively: GLDEF_C INT p_read(VOID *pcb, UBYTE *buf, UINT len) { INT ret; UWORD 1; l=len; ret=p_iow (pcb, P_FREAD, buf, &1); if (!ret) ret=1; return (ret); } The function £_read is identical to p_read except that, if there is an error, it calls p_leave (err) rather than return the negative error number err. p_write (or f_write); Write to an I/O channel INT p_write(VOID *pcb, VOID *buf, UINT len); INT f_write(VOID *pcb, VOID *buf, UINT len); Request a P_FWRITE Of len bytes of data from buf to channel pcb, wait for the request to complete and, if successful, return zero or a negative error number if the write failed. The code for p_write is effectively: GLDEF_C INT p_write(VOID *pcb, UBYTE *buf, UINT len) { UWORD 1; l=len; return (p_iow(pcb, P_FWRITE, buf, &1)); } The function £_write is identical to p_write except that, if there is an error, it calls p_leave (err) rather than return the negative error number err. p_iow(P_FCANCEL) Cancel requests on an I/O channel INT p_iow(VOID *pcb, P_FCANCEL) ; Cancel any outstanding asynchronous requests on channel pcb and return zero. Harmless if there are no pending requests. Device drivers that support truly asynchronous services provide a cancel service. The detailed effect of the cancel depends upon the device driver. However, the following general principles apply: e the cancel precipitates the completion of the request (it does not stop the request from completing) e the cancel may or not be effective (that is, the request may complete naturally before the cancel is processed) e after a cancel, you must still process the completion of the asynchronous request (typically by immediately calling p_waitstat to "use up" the signal) The above principles actually apply to cancelling any asynchronous request (not just an asynchronous I/O request). Although legitimate, using p_ioa(P_FCANCEL) Of p_ioc(P_FCANCEL) (rather than p_iow(P_FCANCEL) ) is somewhat perverse since you then have two signals to use up - one for the request being cancelled and one for the cancel itself. 9-9 PLIB REFERENCE Device driver functions p_loadidd Load a logical device driver INT p_loadldd(TEXT *pName) ; Load the logical device driver (LDD) from the zero terminated file name pName. If pName contains no extension an extension of .LDD is assumed (an LDD file would normally have the extension .LDD). If pName is not a full path name the current path is assumed. Returns zero if successful or one of the following negative error numbers: E_FILE_EXIST an LDD of the same file name has already been loaded E_FILE_NXIST the LDD file does not exist E_FILE_DEVICE the supplied file name is that of a PDD E_GEN_IMAGE the LDD file does not have the correct format or has been corrupted E_GEN_NOMEMORY Not enough memory to satisfy the request E_GEN_NOSEGMENTS No memory segment handles are available After the LDD has been loaded, a channel may be opened to it by calling p_open. Applications that rely on an external LDD should use p_1oadidd and not care if it fails with E_FILE_EXIST. p_loadpdd Load a physical device driver INT p_loadpdd(TEXT *pName) ; Load the physical device driver (PDD) from the zero terminated file name pName. If pName contains no extension an extension of .PDD is assumed (an PDD file would normally have the extension .PDD). If pName is not a full path name the current path is assumed. Returns zero if successful or any of the same negative error numbers as for p_1oadidd above except that, in this case, the error E_FILE_DEVICE is returned if the specified file name is that of an LDD. p_devdel Delete a device driver INT p_devdel (TEXT *pName, INT devType) ; Delete the logical (devType is E_LDD) or physical (devType is E_PDD) device driver with the zero terminated device name pName (as used in p_open but without a trailing ':'). Only device drivers that have been loaded into RAM (and not those devices that are built into the ROM) can be deleted by this service. Returns zero if successful or one of the following negative error numbers: E_FILE_DEVICE The device driver is not currently loaded. E_GEN_NSUP The device driver is a ROM device driver and cannot be deleted. E_GEN_INUSE The device driver is currently open and cannot be deleted. A device dependent Returned by the device driver code. error number If you have loaded an external device driver and finished with it, it is good practice to attempt to delete it by calling p_devdel and to ignore the return value. The call is harmless if the device is loaded by another process or if the device is built into the ROM. WARNING: If pname points to a null string, the first located unloadable device driver of the type specified by devType will be deleted. 9-10 9 1/0 SYSTEM p_devqu Query the number of units supported by a device INT p_devqu (TEXT *pName) ; Returns the number of units supported by logical device driver with the zero terminated device name pName (as used in p_open but without a trailing ':'). The function is not applicable to PDDs. Returns a positive number if successful or one of the following negative error numbers: E_GEN_FAIL Unlimited units (as returned by the r1L: device driver as it can support a large number of files). E_FILE_DEVICE Device driver not found. For example: p_devqu ("TTY"); returns 2 if there are two serial expansion boards fitted. p_devfnd Find all devices HANDLE p_devfnd(HANDLE fHandle, TEXT *pMatch, INT devType, TEXT *pName) ; Write the next device name (as used in p_open but without a trailing ':') of type devType (either z_ppp to find PDD devices or z_upp to find LDD devices) that matches the zero terminated match string pMatch as a zero terminated string to pName where fHand1e is zero for the first call and is subsequently the positive return value from the previous call. When there are no further devices of type devType that match pmatch, p_devfnd returns E_FILE_DEVICE. Used repeatedly to find all the PDD or LDD devices that match the wild card string pointed to by pmatch. The buffer at pName should be big enough to receive &_max_NamE+2 bytes. The wild card string pMatch should remain the same between successive calls. No memory is used by this service and it can be abandoned at any time without taking any further action. Calls p_panic if fHandle is invalid. Example LOCAL_C VOID PrintDevices (VOID) { HANDLE fh; TEXT bb[E_MAX_NAME+2]; fh=0; FOREVER { fh=p_devfnd(fh,"*",E_LDD, &bb[0]); if (fh<0) break; p_puts (&bb[0]); } Simple console I/O The simple console functions are provided for "quick and dirty" applications - for example test programs and software tools. They are not suitable for constructing quality user interfaces. The console functions use the services of the con: device driver to implement the following screen output and keyboard input functions: p_putch to write a character to the screen p_puts to write a line of text to the screen and move to the beginning of the next line p_printf£ to convert numbers to printable form and write them as a line of text to the screen and then move to the beginning of the next line p_print to convert numbers to printable form and write them to the screen 9-11 PLIB REFERENCE p_getch to get (without echo) a single character from the keyboard p_gets to input (with simple backspace editing) a line of text from the keyboard p_getl to display a prompt and then input (with simple backspace editing) text from the keyboard Redirecting console writes These functions automatically open con: when they are first used. The open channel is stored in the global static: GLREF_D VOID *winHandle; which is initialised to nuLL. You can open a suitable alternative device (eg a file or Try: ) before the first usage of a console function to redirect the output functions p_putch, p_print and p_printf. For example, to redirect to the file a.Jis in the current path, use: p_open (&winHandle, "a.lis",P_FREPLACE |P_FUPDATE) ; Note that if you do redirect the console to such a device, you should not use p_getch, p_gets or p_getl. Changing the size of the console window If you want to change the size of the console window, you can do this by declaring the global P_REcT structure _DefScreenRect and initialising it to the required size before the first usage of a console function. The P_REcT struct is defined in p_graf-h as: typedef struct { WORD x; /* Horizontal coordinate */ WORD y; /* Vertical coordinate */ } P_POINT;. typedef struct { P_POINT tl; /* Top left point */ P_POINT br; /* Bottom right point */ } P_RECT;. where the top left coordinates should be (0,0) and the bottom right coordinates should reflect the required dimensions in character columns and rows as in, for example: GLDEF_D P_RECT _DefScreenRect={{0,0},{10,20}}; for 10 columns by 20 rows, or: GLDEF_D P_RECT _DefScreenRect; _DefScreenRect.t1l.x=0; _DefScreenRect.tl.y=0; _DefScreenRect.br.x=columns; _DefScreenRect.br.y=rows; Changing the console window mode By default the console starts up in the native mode of the machine. At the time of writing, all SIBO machines default to single pixel (non-compatibility) mode, with no access to grey. If you want to change the mode, you can do this by declaring the global variable _DefscreenMode and initialising it to the required value before the first usage of a console function. The possible modes are: use the native mode of the machine - this is the default value compatibility mode, allowing Series 3 software to run on the Series 3a non-compatibility mode, with grey enabled compatibility mode, but with grey enabled WN FO Thus, compatibility mode may be set on the Series 3a as follows: GLDEF_D INT _DefScreenMode; _DefScreenMode=1; Values that are not relevant to a particular type of machine are simply ignored. 9-12 9 1/0 SYSTEM p_putch Write a character to the console VOID p_putch(UINT c); Write character c to the console, opening the console if necessary. p_puts Write a string to the console VOID p_puts(TEXT *str); Write the zero terminated string str to the console and start a new line. The console is opened if necessary. p_printf Convert arguments and write line to console VOID p-printé#(TEAT *fstry +44 )¥ Converts multiple arguments to an internal buffer under control of the format string fstr, writes the complete string to the console screen and then moves the print position to the beginning of the next line. The console is opened if necessary. The internal buffer is p_maxsysto (258) bytes long and the length of the output (which depends on the arguments) must be limited to p_maxsys1o-2 (256) bytes per call of p_print£. The format of ¢str is exactly the same as for p_atob, which is described in the chapter Integer Conversion and Rectangle functions. p_print Convert arguments and write to console VOID p_print (TEXT *fstr, ...); Behaves as for p_printf above except that the print position is not automatically moved to the beginning of the next line after the write. You can embed \r and \n characters in fstr to move to the beginning of the line and to move down a line respectively. p_getch Get a character from the console INT p_getch(VOID); Wait for a key to be pressed and return its character code. The console is opened if necessary. p_gets Get a string from the console INT p_gets(TEXT *str); Input (with simple backspace editing) a line of up to p_maxsys1o-1 characters from the keyboard, creating a zero terminated string at str. The input is terminated by the user pressing the Enter key. Returns the length of the string at str. There should be at least p_maxsyszio bytes at str. The console is opened if necessary. p_getl Get a string with prompt from the console INT p_get1l(TEXT *pmt, TEXT *str, INT len); Write the zero terminated string pmt to the console and input (with simple backspace editing) up to 1en characters from the keyboard, creating a zero terminated string at str. The input is terminated by the user pressing the Enter key. Returns the length of the string at str. There should be at least 1en+1 bytes at str. The console is opened if necessary. CHAPTER 10 TIME, TIMERS AND DATES System time The system time is set and sensed as a ULONG, counting the number of seconds since 00:00:00, January 1, 1970 (compatible with UNIX system time). The system time overflows approximately 136 years after 1970 (which is sometime in the year 2106). p_date Return the system time ULONG p_date (VOID) Return the system time as the number of seconds since 00:00:00, January 1, 1970. p_sdate Set the system time VOID p_sdate(ULONG newTime) ; Set the system time to newTime (the number of seconds since 00:00:00, January 1, 1970). Note that setting the system time forward by an interval will cause any absolute timers due in that interval to expire. Absolute and relative timers In the EPOC operating system a process can be waiting on a timer in two ways: e The process is in the time delta queue as a result of calling p_sleep, p_sleept Of p_sleepa. e The process is waiting for its I/O semaphore to be signalled by a timer device entry in the time delta queue after making an asynchronous timer request by calling p_ioc(P_FRELATIVE) or p_ioc(P_FABSOLUTE) on an open timer channel. Each entry in the time delta queue contains the signed long delta time in system ticks relative to its predecessor while the head of the queue contains the delta time relative the current system time (for more on delta queues see the chapter Characters, Strings, Buffers and Queues). The head of the time delta queue is decremented every system tick and the corresponding timer expires (and is removed from the queue) when its delta time is zero or negative. If the entry is a process, the process will either run (if it has the highest priority) or it will wait in the ready queue. If the entry is a timer device entry, the associated process I/O semaphore is signalled. See the chapter Asynchronous Requests and Semaphores for information on the I/O semaphore, asynchronous requests and the ready queue. As well as being marked as processes or timer device entries, entries in the time delta queue are also marked as being absolute or relative. PLIB REFERENCE Absolute timers An absolute timer is characterised by the following: e the timer expiry is set in terms of an absolute time (the number of seconds since 00:00:00, January 1, 1970) e SIBO machines (as opposed to a PC running EPOC) that have switched off will automatically switch on when an absolute timer is due to expire e setting the system time forward by an interval will cause any absolute timers due in that interval to expire Absolute timers are used to implement, for example, alarms for the Diary and Alarms applications on the MC GI machines. You can set absolute timers to expire after a relative time interval (expressed in seconds) by adding the required interval to the time returned by p_date. Although absolute timers are converted to relative signed long delta time in the time delta queue, absolute timer entries can re-launch themselves to cover ranges in excess of 0x7£ffffFfF ticks. Relative timers A relative timer is characterised by the following: e the timer expiry is set as a time interval relative to the current system time (either as a number of 1/10ths of a second or as a number of system ticks) e the timer stops running while SIBO machines are switched off and it follows that relative timers do not wake up the operating system up e changing the system time has no effect on relative timers If a relative timer is set to expire after 5 seconds when the system time is advanced by | hour the timer still expires after 5 seconds (provided the machine is not switched off). If a relative timer is due to expire in 5 seconds when the machine switches off for | hour, the relative timer actually expires after 1 hour plus 5 seconds. SIBO machines are normally configured to switch off automatically after a period of inactivity (typically 5 minutes). A process that repeatedly waits on a relative timer with an interval shorter than the switch off inactivity period (for example to implement a clock or a flashing cursor) would by default stop the machine from ever switching off. Keeping a battery powered machine on indefinitely is normally undesirable and, in this situation, the application should call p_unmarka (described in the chapter Processes and Inter-Process Messaging) to stop such activity from keeping the machine on. Relative timers are ultimately converted to system ticks in a signed long - giving them a range of Ox7f£f£ffFFE ticks, or approximately 2.1 years, on a SIBO machine (which ticks 32 times a second). p_sleep Suspend process for n tenths of a second INT p_sleep(ULONG n); Return zero after n 1/10ths of a second has elapsed. Returns E_GEN_OVER immediately if the conversion from 1/10ths to ticks overflows (ie the number of ticks is greater than 0x7fffffff). This function uses a relative timer. If the machine switches off before the function returns, it will take indefinitely longer than n tenths of a second to return. p_sleept Suspend process for n system ticks INT p_sleept (LONG nTicks) ; Returns zero after ntTicks system ticks if successful or E_GEN_aRc if nTicks is negative. On the SIBO hardware the system ticks 32 times a second. On IBM PCs and compatibles the system ticks 18.2 times a second. The constant E_TICKS_PER_SECOND in epoc.h contains the number of ticks per second, to the nearest integer. 10-2 10 TIME, TIMERS AND DATES A request to sleep for zero ticks is valid and will just force a re-schedule, with the process calling this service losing the remainder of its time slice if there are other processes at the same priority. This function uses a relative timer. If the machine switches off while the process is suspended, it will take indefinitely longer than nticks system ticks to return. In the absence of any switching off, the actual time the process is suspended is greater than or equal to nticks and less than nTicks+1. p_sleepa Suspend process until absolute time INT p_sleepa(ULONG time) ; Return when the system time is time (the number of seconds since 00:00:00, January 1, 1970). Returns zero 1f successful or E_GEN_aRG if time is earlier than the current system time. This uses an absolute timer which will wake the machine up if necessary when the timer expires. Another process setting the system time past the expiry time (using p_sdate) will cause p_sleepa to return. eee eee ——————EEEEEEEEeeeEeEeeseeee— Asynchronous timers The functions p_sleepa, p_sleep and p_sleept are synchronous functions in the sense that they return only when the requested operation (in this case the expiry of a timer) has completed. With asynchronous timers, the function call to start the timer returns immediately. This allows other processing to take place before waiting for any one of a number of events (including the expiry of the timer) by calling p_iowait. See the chapter Asynchronous Requests and Semaphores for an explanation of asynchronous requests in general. In EPOC, asynchronous timers are implemented as an I/O device with the device name "TIM:". To use an asynchronous timer, you open a channel to "TIM:" and use: p_ioc (P_FRELATIVE) to start a relative timer p_ioc (P_FABSOLUTE) to start an absolute timer p_iow (P_FCANCEL) to cancel a timer p_close to close a timer channel The constants p_FRELATIVE, P_FABSOLUTE and p_FcaANCcEL are defined in p_file.h. See the chapter //O System for a description of the I/O system in general. If you want to run multiple timers in parallel, you open a channel for each timer needed. Although you can open a timer channel and use p_iow(P_FRELATIVE) Of p_iow(P_FABSOLUTE) (rather than p_ioc) to the same effect as p_sleep Or p_sleepa respectively, the latter should be preferred for their simplicity and efficiency. p_open(“TIM:”) Open a timer channel INT p_open(VOID **pptcb,TIM:,-1); Open a timer channel and write the address of the timer control block to *ppt cb. Returns zero if successful or the negative error number &_cEN_Nomemory if, for example, it failed to allocate memory for the control block. p_ioc(P_FRELATIVE) Start a relative timer VOID p_ioc(VOID *ptcb, P_FRELATIVE, WORD *pstat, ULONG *pn); Start a relative timer to expire after *pn tenths of a second. Calls p_panic if a timer is already pending on channel ptcb. While the timer is pending, *pstat contains E_FILE_PENDING. When the timer expires successfully, *pstat is set to zero and the I/O semaphore is signalled. The timer is not started and *pstat is set to E_GEN_OVER if the conversion from tenths of a second to ticks overflows (ie the number of ticks is greater than ox7£f£ffFfF). 10-3 PLIB REFERENCE p_ioc(P_FABSOLUTE) Start an absolute timer VOID p_ioc(VOID *ptcb, P_FABSOLUTE, WORD *pstat, ULONG *ptime) ; Start an absolute timer to expire when the system time is *pt ime (the number of seconds since 00:00:00, January 1, 1970). Calls p_panic if a timer is already pending on channel pt cb. While the timer is pending, *pstat contains E_FILE_PENDING. When the timer successfully expires, *pstat is set to zero and the I/O semaphore is signalled. The timer is not started and *pstat is set to E_GEN_aRG if *ptime is earlier than the current system time. p_iow(P_FCANCEL) Cancel a timer INT p_iow(VOID *ptcb, P_FCANCEL) ; Cancel a timer on channel ptcb and return zero. The operation is harmless if no timer is pending. This is important because there is always a chance that a timer will expire before the cancel gets to it. If the cancel does get to the timer before it expires, the I/O semaphore is still signalled but *pstat is set to E_FILE_CANCEL rather than zero. However, when cancelling a timer, you don't normally care how the timer actually completed. It is common to call p_waitstat immediately after the cancel to "use up" the signal. To reset a timer, you first cancel the timer using p_iow(P_FCANCEL) followed by a call to p_waitstat and then call p_ioc (P_FRELATIVE) to start the timer again. p_close Close a timer channel INT p_close(VOID *ptcb); Close the timer channel ptcb and return zero. You should close a timer channel when you no longer need it. Converting between binary representations of time The system time format sacrifices range to obtain high compression and is the most convenient and efficient representation for adding and subtracting small time intervals such as seconds, minutes, hours and days. There are 86,400 seconds in a day (P_NseEcpay is defined as 86400L in p_date.h). There are three different binary representations for time in PLIB: e The number of seconds since 00:00:00, January 1, 1970 (system time format) e Days since January 1, 1900 and seconds in day e (Year since 1900,Month,Day) and (Hour, Minute,Second) Note that the first representation works from a base year of 1970 while the second two representations work from a base year of 1900. PLIB contains conversion routines that convert both ways between adjacent representations in the above list. By combining conversion functions, you can convert between the first and third representation. The second format measures the days since January | 1900 (day=0 for Jan 1) and the seconds since 00:00:00 and is stored in a p_payszc struct, defined in p_date.h as follows: typedef struct { ULONG day; /* day number since Jan 1 1900 */ ULONG sec; /* seconds in day, 0 to 86399 */ } P_DAYSEC;. 10-4 10 TIME, TIMERS AND DATES The day number is useful for date calculations not involving time. Examples are to count the number of days between two dates, or to calculate a new date from a base date and a number of days (positive or negative). The third format is closest to a human understandable representation of the date and time and is defined by the p_pate struct in p_date.h: typedef struct { UBYTE year; /* Year since 1900 (0 is 1900) */ UBYTE month; /* Month number in year, 0 to 11 */ UBYTE day; /* Day number in month, 0 to 30 */ UBYTE hour; /* Hour in day, 0 to 23 */ UBYTE minute; /* Minute in hour, 0 to 59 */ UBYTE second; /* Second in minute, 0 to 59 */ UWORD yrday; /* Day in year */ } P_DATE;. The last member, yrday is a function of year, month and day. It is provided when p_parte is generated from p_pays«c but is ignored when converting from P_paTE. p_sttods Convert system time to P_DAYSEC time VOID p_sttods(ULONG *pstim, P_DAYSEC *pds) Convert *pstim from system time format (the number of seconds since 00:00:00, January 1, 1970) to the number of days since 1900 and the number of seconds in the day, both being written to pas. p_dstost Convert P_DAYSEC time to system time INT p_dstost (P_DAYSEC *pds, ULONG *pstim) ; Convert pds from the number of days since 1900 and the number of seconds in the day to system time format (the number of seconds since 00:00:00, January 1, 1970). The result is written to *pstim. Returns zero if successful, or one of the following negative error numbers: E_GEN_OVER date in pds is too late for system time E_GEN_UNDER date in pds is too early for system time (ie before 1970) E_GEN_ARG seconds is greater than seconds in day p_dstodt Convert P_DAYSEC time to P_DATE time INT p_dstodt (P_DAYSEC *pds, P_DATE *pdt); Convert p_paysec time pds (days since 1900, seconds in day) to p_paTE time pdt (year, month, day, hour, minute, second and day in year) and return zero if successful or one of the following negative error numbers: E_GEN_OVER the year is greater than 255 (year 2155) E_GEN_ARG seconds is greater than seconds in day As well as the date and time, this function calculates pdt->yrday (the day number of the year, where day zero 1s January 1). Although you could calculate this yourself from the year, month and day it is not a simple calculation since it involves adding the number of days in preceding months and thus depends on leap years. This function actually performs two independent calculations: ¢ converting the number of days since 1900 to year, month, day and day in year (date calculation) ¢ converting the number of seconds since 00:00:00 to hours, minutes and seconds (time of day calculation) Both calculations may be abandoned if either calculation fails. If you are only interested in one of the calculations, set the other member of p_paysEc to zero (which is a legal input for both calculations). 10-5 PLIB REFERENCE p_dttods Convert P_DATE time to P_DAYSEC time INT p_dttods(P_DATE *pdt, P_DAYSEC *pds); Validate the p_DaTE format pdt and convert it to the p_payszEc format pds and return zero if successful or the negative E_GEN_arc if the content of pdt is invalid (ie no such date or time exists). The value of pdt->yrday is ignored. The validation can fail for one or more of the following reasons: @ pdt ->month is outside the range 0 to 11 ¢ pdt ->day is outside the range for the particular month taking into account leap years for February @ pdt ->hour is outside the range 0 to 23 @ pdt->minute is outside the range 0 to 59 @ pdt->second is outside the range 0 to 59 This function actually performs two independent calculations: e converting the year, month and day to the number of days since January 1 1900 (date calculation) e converting the hours, minutes and seconds to the number of seconds since 00:00:00 (time of day calculation) Both calculations may be abandoned if either calculation fails. If you are only interested in one of the calculations, set the members of P_DATE corresponding to the other calculation to zero (zero is a legal input for all members in both calculations). p_dayinm Find the number of days in the specified month INT p_dayinm(INT year, INT month); Return the number of days in month month of year year where year is the number of years since 1900 (zero is 1900) and month is 0 to 11 inclusive. (Returns the negative E_GEN_aRc if month is greater than 11.) The year is required because it affects the number of days in February. This may be used to test for a leap year, for example: if (p_dayinm(year,1)==29) p_wkday Convert day since 1900 to day in week INT p_wkday(ULONG nDay) ; Returns the week day number given the number of days nDay since January 1 1900. The value returned is in the range 0 to 6, with 0 being Monday and 6 being Sunday. The day number since 1900 would normally come from a structuresP_DAYSECc struct. p_weekno Calculate week number in year INT p_weekno(ULONG nDay) ; Return the week number, in the range | to 53 inclusive, of the week containing day nDay (the number of days since January 1 1900). Returns zero if successful or E_GEN_aRG if nDay exceeds the year 2155. The value returned is dependent on the value of the system structuresE_conrte structure field startOfWeek (see p_getctd in this chapter). 10-6 10 TIME, TIMERS AND DATES Time and date components in text form The functions in this section get a range of date and time components in text form. They constitute a more primitive set of functions than those, described in the following section, that generate strings containing full date and/or time representations. These functions are based on language-dependent information that is built into the ROM. If you use these functions, your code should automatically work on, for example, English, French and German machines. The functions are: p_nmday to get the day names (eg Monday, Tuesday) p_nmmon to get the month names (eg January, February) p_nmdaya to get the day name abbreviations (eg Mon, Tue) p_nmmona to get the month name abbreviations (eg Jan, Feb) p_getsuffixes to get the 31 day in month number suffixes (eg st, nd) p_getampmtext to get the am and pm suffixes p_getctd to get a copy of the setable country-dependent data and time preferences (such as whether to use a 12 or 24 hour clock) The following example displays the system time in the form: Monday, 11th April 1988 11:03 and uses many of the functions described in this chapter. #include GLDEF_C VOID PrintDateTime() { ULONG st; P_DAYSEC ds; P_DATE dt; E_CONFIG cfg; TEXT DayName [32]; TEXT MonthName [32]; TEXT Suffix[31] [3]; st=p_date(); p_sttods (&st, &ds) ; p_dstodt (&ds, &dt) ; p_nmday (&DayName[0],p_wkday (ds.day) ); p_nmmon (&MonthName[0],dt.month) ; p_getsuffixes (&Suffix[0][0]); p_getctd(&cfg) ; p_printf("%s, Suss Ss Su SO2uscs02u", &DayName[0],dt.day+1, &Suffix[dt.day] [0], &MonthName[0],dt.year+1900, dt .hour,cfg.timeSeparator,dt.minute) ; } This example is somewhat artificial since the same action can be performed more simply by the use of p_nowtostr, described in the following section. Object-oriented programmers may prefer to use the time class in OLIB (described in the OLIB Reference manual of the SIBO SDK Object Oriented Extension) to produce textual representations of the date and time. 10-7 PLIB REFERENCE p_nmday Get the day name VOID p_nmday (TEXT *buf, INT daynum) ; Write the language dependent name of day daynum as a zero terminated string to buf where daynum should be in the range 0 to 6 inclusive and day 0 is Monday. If you are working in a fixed language, you will know how long the longest day name is. If you are writing a program that has to work with different language ROMs, you can use the fact that the tool that builds the day name list for the ROM limits a particular day name to E_MAX_DAY_NAME (32), including the zero terminator. p_nmdaya Get the day name abbreviation VOID p_nmdaya(TEXT *buf, INT daynum) ; This function is only available in EPOC version 3.18 or later. Write the language dependent abbreviation for the name of day daynum as a zero terminated string to buf where daynum should be in the range 0 to 6 inclusive and day 0 is Monday All day name abbreviations for a particular language are of the same length and could be one, two or three characters. In no language will day name abbreviations exceed three characters. p_nmmon Get the month name VOID p_nmmon(TEXT *buf, INT monthnum) ; Write the language dependent name of month monthnum as a zero terminated string to buf where monthnum should be in the range 0 to 11 inclusive and month 0 is January. If you are working in a fixed language, you will know how long the longest month name is. If you are writing a program that has to work with different language ROMs, you can use the fact that the tool that builds the month name list for the ROM limits a particular month name to E_Max_MONTH_NAME (32), including the zero terminator. p_nmmona Get the month name abbreviation VOID p_nmmona(TEXT *buf, INT monthnum) ; This function is only available in EPOC version 3.18 or later. Write the language dependent abbreviation of the name of month monthnun as a zero terminated string to buf where monthnum should be in the range 0 to 11 inclusive and month 0 is January. All month name abbreviations for a particular language are of the same length and could be one, two or three characters. In no language will month name abbreviations exceed three characters. p_getsuffixes Get the day-in-month suffixes VOID p_getsuffixes (TEXT *buf); Write the language dependent array of the 31 day-in-month number suffixes to buf. The suffixes are written as an array of 31 3-byte fixed length elements where each element contains 0, 1 or 2 characters followed by a zero terminator (suffixes contain at most 2 characters in any language). There must be at least 31*3 bytes of memory at buf. Each element in the array contains the suffix for the corresponding day of the month. For example, in English, the first element contains "st" and the second element contains "nd". p_getampmtext Get the am and pm suffixes VOID p_getampmtext (TEXT *buf, INT n); Write the language dependent am or pm time suffix as a zero terminated string to buf. If n is 0, write the am suffix. If n is 1, write the pm suffix. The am and pm suffixes are limited to 2 characters in any language. 10-8 p_getctd VOID p_getctd(E_CONFIG *pcfg); 10 TIME, TIMERS AND DATES Get time representation preferences .Write a copy of the system structuresz_conFiIe struct to pcfg where the &_conFice struct is defined, in p_config.h, as: typedef struct { UWORD countryCode; WORD gmtOffset; UBYTE dateType; UBYTE timeType; UBYTE currencySymbolPosition; UBYTE currencySpaceRequired; UBYTE currencyDecimalPlaces; UBYTE currencyNegativelInBrackets; UBYTE currencyTriadsAllowed; UBYTE thousandsSeparator; UBYTE decimalSeparator; UBYTE dateSeparator; UBYTE timeSeparator; UBYTE currencySymbol [9]; UBYTE startOfWeek; UBYTE summerTime; UBYTE clockType; UBYTE dayAbbreviation; UBYTE monthAbbreviation; UBYTE workDays; UBYTE units; UBYTE spare[9]; } E_CONFIG; In the context of this chapter we are interested in the following items: gmtOffset dateType timeType dateSeparator timeSeparator startOfWeek summerTime cloc dayA mont work kType bbreviation hAbbreviation Days the offset in minutes of the local system time from Greenwich Mean Time. one of &_pate_usa for MM/DD/YY, £_pate_euRopE for DD/MM/YY or E_DATE_gapan for YY/MM/DD. either =_Trme_12 for a 12 hour clock or E_tT1ime_24 for a 24 hour clock. the character code of the date separator. For example, the character '/’. the character code of the time separator. For example, the character ':'. the day number (in the range 0 to 6 inclusive where day 0 is Monday) of the first day in the week - as used by p_weekno. This is normally either Sunday (day 6) or Monday (day 0). For example, it is normally Sunday for USA machines and Monday for UK machines. a bit pattern indicating summer time-zones as follows: E_pst_Home if the system time should be adjusted for summer time; E_DsT_EUROPEAN if a European time-zone should be adjusted for summer time; z_DsST_NORTHERN if a non-European time-zone in the Northern hemisphere should be adjusted for summer time; E_DST_SOUTHERN if a time-zone in the Southern hemisphere should be adjusted for summer time. either E_ANALOGUE_CLOCK to indicate a preference for an analogue clock display or =_p1G1ITAL_cLock to indicate a preference for a digital clock display how many leading characters to take from the day name (as returned by p_nmday) to abbreviate the day name how many leading characters to take from the month name (as returned by p_nmmon) to abbreviate the month name a bit mask of 7 bits indicating (by being set) which days are to be considered work days where the least significant bit corresponds to Monday The data may be set using p_setctd - as described in the chapter General System Services. 10-9 PLIB REFERENCE Generating time and/or date strings The functions in this section construct text strings that represent the date and time in a range of formats. The functions are based on language-dependent information that is built into the ROM, in particular, the system E_CONFIG struct (see the description of p_getctd in the Language and country section of the General System Services chapter). If you use these functions, your code should automatically work on, for example, English, French and German machines. Note that the implementation of these functions uses static data. In consequence they may not be used in the code of a dynamic library (DYL). Date and time format strings The form of the output of the date and time conversion functions described below is controlled by a format string, in a similar way to the format strings used by p_printf, p_atob and p_atos. The format string consists of literal text intermixed with embedded commands. The literal text is simply copied to the output and the embedded commands are replaced by the corresponding time or date element. An embedded command is of the form %c or %*c, where c is one of the characters listed below, and * indicates that the output should be in an abbreviated form. The abbreviated form can be specified for all commands, but in some cases there is no difference between the full and abbreviated forms. The available commands are as follows: %% replaced by a single '%' character. Abbreviation has no effect. %: replaced by the time separator character as specified by the timeSeparator field of the system E_conFie struct. Abbreviation has no effect. %/ replaced by the date separator character as specified by the dateSeparator field of the system E_conrié struct. Abbreviation has no effect. aN depending on the supplied time of day, this is replaced by the appropriate am or pm text (as obtained by use of p_getampmt ext). Abbreviation supplies just the first character of this text. Note that abbreviation may not be appropriate in some languages. &D replaced by the day-in-month number, in the range 01 to 31, as two digits with a leading zero as necessary. Abbreviation suppresses any leading zero. %E replaced by the day name, as supplied by p_nmday. Abbreviation causes the name to be truncated to the number of characters specified in the dayAbbreviation element of the system E_CONFIG struct. 3H replaced by two digits in the range 00 to 23 (24-hour format) corresponding to the hours component of the supplied time of day. Abbreviation suppresses any leading zero. SI replaced by two digits in the range 01 to 12 (12-hour format) corresponding to the hours component of the supplied time of day. Abbreviation suppresses any leading zero. oM replaced by two digits in the range 01 to 12 corresponding to the month number for the supplied date. Abbreviation suppresses any leading zero. 3N replaced by the month name for the supplied date. Abbreviation causes the name to be truncated to the number of characters specified in the monthAbbreviation element of the system E_CONFIG struct. %S replaced by two digits in the range 00 to 59 corresponding to the seconds component of the supplied time of day. Abbreviation suppresses any leading Zero. ST replaced by two digits in the range 00 to 59 corresponding to the minutes component of the supplied time of day. Abbreviation suppresses any leading Zero. ow replaced by two digits in the range 01 to 53 corresponding to the week number for the supplied date, as provided by p_weekno. Abbreviation suppresses any leading zero. 10-10 ole x ole iS) ole Ww ole & oe uo \ oO) ole a 10 TIME, TIMERS AND DATES replaced by the suffix text corresponding to the (p) day number for the supplied date. Abbreviation has no effect. replaced by four digits, in the range 1900 to 2155, corresponding to the year number for the supplied date. Abbreviation discards the first two digits (note that, for example, both 1900 and 2000 will appear as 00). replaced by three digits, in the range 001 to 366, corresponding to the day-of- year number for the supplied date. Abbreviation discards any leading zeros. replaced by the first component of a three-component (day, month and year) date, where the order of the components is determined by the value of the dateType component of the system &_conrie struct. Abbreviation has no effect, but the form of the generated text is conditioned by the toggle commands described below. In the absence of any toggles, %1 is equivalent to: sp if dateType 1S E_DATE_EUROPE gm if dateType iS E_DATE_USA sy if dateType iS E_DATE_JAPAN replaced by the second component of a three-component (day, month and year) date, where the order of the components is determined by the value of the dateType component of the system &_conrie struct. Abbreviation has no effect, but the form of the generated text is conditioned by the toggle commands described below. In the absence of any toggles, ¢2 is equivalent to: gm if dateType 1S E_DATE_EUROPE zp if dateType iS E_DATE_USA gm if dateType iS E_DATE_JAPAN replaced by the third component of a three-component (day, month and year) date, where the order of the components is determined by the value of the dateType component of the system &_conrie struct. Abbreviation has no effect, but the form of the generated text is conditioned by the toggle commands described below. In the absence of any toggles, s3 is equivalent to: sy if dateType iS E_DATE_EUROPE sy if dateType iS E_DATE_USA sp if dateType iS E_DATE_JAPAN replaced by the first component of a two-component (day and month) date, where the order of the components is determined by the value of the dateType component of the system &_conFie struct. Abbreviation has no effect, but the form of the generated text is conditioned by the toggle commands described below. In the absence of any toggles, %4 is equivalent to: sp if dateType 1S E_DATE_EUROPE zm if dateType iS E_DATE_USA Of E_DATE_JAPAN replaced by the second component of a two-component (day and month) date, where the order of the components is determined by the value of the dateType component of the system E_conFie struct. Abbreviation has no effect, but the form of the generated text is conditioned by the toggle commands described below. In the absence of any toggles, %5 is equivalent to: gm if dateType 1S E_DATE_EUROPE sp if dateType iS E_DATE_USA OF E_DATE_JAPAN replaced by the hour in the format determined by the value of the timeType component of the system &_conrte struct. Abbreviation discards any leading zero. The action of 6 is equivalent to: 3H if timeType 1S E_TIME_24 %1 if timeType 1S E_TIME_12 replaced by the am/pm text (as for sa) if the timeType component of the system E_CONFIG Struct has the value z_t1mz_12, otherwise produces no output. If output is produced, abbreviation reduces the output to just the first character of the text, as for za. Note that format strings of the form "s1%/%2%/%3" and "4%/%5" respectively generate three- and two- component dates that automatically conform to the system configuration dateType setting, and that a format string of the form "%63:%T%:%s%7" generates a time that automatically conforms to the system configuration timeType setting. 10-11 PLIB REFERENCE The commands 31, %2, %3, 4 and %5 are conditioned by the following toggles: ole F produces no output, but toggles the behaviour of subsequent day items between numeric (the default) and name generation. For example, if dateType is E_DATE_EUROPE and the supplied date is 09/03/1993, the format string "$1 %F%1 %F%1" will generate the (English) string "09 Tuesday 09". Abbreviation has no meaning, and is ignored. The items conditioned by this toggle depend on dateType as follows: %1 and %4 for E_LDATE EUROPE %2 and %5 for E_LDATE_USA %3 and %5 for E_DATE_JAPAN ole [e) produces no output, but toggles the behaviour of subsequent month items between numeric (the default) and name generation. For example, if dateType iS E_LDATE_EUROPE and the supplied date is 09/03/1993, the format string "$2 %0%2 %0%2" will generate the (English) string "03 March 03". Abbreviation has no meaning, and is ignored. The items conditioned by this toggle depend on dateType as follows: 32 and %5 for E_DATE EUROPE %1 and 34 for E_LDATE_USA %2 and %4 for E_DATE_JAPAN ole (9) produces no output, but toggles the behaviour of subsequent day items between their full (the default) and their abbreviated forms. For example, if dateType is E_DATE_EUROPE and the supplied date is 09/03/1993, the format string "SF%1 %G%1 %G%1" will generate the (English) string "Tuesday Tue Tuesday". Abbreviation has no meaning, and is ignored. The items conditioned by this toggle depend on datetype as follows: %1 and %4 for E_LDATE EUROPE %2 and %5 for E_LDATE_USA %3 and %5 for E_DATE_JAPAN ole ae] produces no output, but toggles the behaviour of subsequent month items between their full (the default) and their abbreviated forms. For example, if dateType 18 E_LDATE_EUROPE and the supplied date is 09/03/1993, the format string "80%2 %P%2 %Pp%2" will generate the (English) string "March Mar March". Abbreviation has no meaning, and is ignored. The items conditioned by this toggle depend on dateType as follows: 32 and %5 for E_DATE EUROPE %1 and %4 for E_LDATE_USA %2 and %4 for E_DATE_JAPAN ole aq produces no output, but toggles the behaviour of subsequent year items between their full (the default) and their abbreviated forms. For example, if dateType is E_DATE_EUROPE and the supplied date is 09/03/1993, the format string "$3 %U%3 %U%3" will generate the string "1993 93 1993". Abbreviation has no meaning, and is ignored. The items conditioned by this toggle depend on dateType as follows: 33 for E_LDATE EUROPE %3 for E_DATE_USA %1 for E_DATE_JAPAN ld fed! produces no output, but toggles between the absence (the default) and the presence of a day number suffix following a day-in-month number (but not a day name) generated by 1, 2 or 33. For example, if dateType is E_DATE_EUROPE and the supplied date is 09/03/1993, the format string "$G%1 %L%1 %L%1" will generate the (English) string "9 9th 9", but "SF%G%1 %L%1 %L%1" will generate the (English) string "Tue Tue Tue". Abbreviation has no meaning, and is ignored. 10-12 10 TIME, TIMERS AND DATES Function return values The Window Server animates time displays either by flashing the last time separator character in the text or, if the text contains a seconds display, by updating the time every second. All the functions described below return a value that indicates the form of animation required. The possible return values are: -2 The display does not show seconds and contains no time separators. The display is not animated. -1 The display shows seconds. Animation updates the display every second. any other value The return value is the offset in the string to the last time separator character. Animation flashes this character. p_dt2str Convert a P_DATE time to a string INT p_dt2str(TEXT *buf, TEXT *fstr, P_DATE *pdt); Write a zero terminated string containing formatted date and time text to buf, controlled by the zero terminated format string pointed to by fstr and the content of the p_paTE struct pointed to by pat. The format string fstr contains literal text, embedded with commands, as described above. p_ds2str Convert a P_DAYSEC time to a string INT p_ds2str(TEXT *buf, TEXT *fstr,P_DAYSEC *pds); Write a zero terminated string containing formatted date and time text to buf, controlled by the zero terminated format string pointed to by fstr and the content of the p_payszc struct pointed to by pas. The format string fstr contains literal text, embedded with commands, as described above. p_st2str Convert a system time to a string INT p_st2str(TEXT *buf, TEXT *fstr, ULONG *pst); Write a zero terminated string containing formatted date and time text to buf, controlled by the zero terminated format string pointed to by fstr and the system time (seconds since 00:00:00, January 1, 1970) pointed to by pst. The format string fstr contains literal text, embedded with commands, as described above. p_now2str Convert the current time to a string INT p_now2str(TEXT *buf, TEXT *fmt); Write a zero terminated string containing formatted date and time text to buf, controlled by the zero terminated format string pointed to by fstr and the currently set time. The format string fstr contains literal text, embedded with commands, as described above. 10-13 CHAPTER 11 FILES Files in EPOC The file server The file server is a high priority system process (with process name SYS$FSRV) performing all file related operations on behalf of "client" processes. An application process must connect to the file server before using its services. However, this is normally taken care of by the C startup module (the code that precedes main) and supplied as standard for use with the PLIB library. (Most applications need the services of the file server and the overhead of connecting to the file server is modest.) A client process sends the file server an inter-process message to request a file server service. However, the application programmer does not program at the message passing level but uses the interface provided by the r1L: device (ie using p_open) together with the interface provided by a number of ROM resident functions (eg p_delete to delete a file) as described in this chapter. Any process that attempts to send a message to the file server without having connected is panicked with panic number 41. File systems The file server supports multiple file systems (also called nodes). In principle, the file server supports any number of file systems (p_open may be used to get a list of the file systems as described later in this chapter). At the time of writing, three file systems have been implemented: LOC: : The local filing system with devices M: (the RAM drive) and SSD drives A:, B:, ...(where the quantity depends upon the hardware). REM: : The remote filing system, available while the file server is connected to a remote file server. The structure of the filing system depends upon what the remote system is. If the remote system is a PC or another SIBO machine, the structure is the same as for Loc: :. ROM: : The ROM filing system, used to access ROM-based files. This filing system is not normally visible to the user. The Rom: : file system does not support devices or directories. Within the toc: : file system (and where REM: : is connected to a PC or another SIBO machine), the devices and directories structure is compatible with the MSDOS filing system. File systems are implemented as PDDs (Physical Device Drivers) which are normally resident in the ROM (PDDs are described in the chapter //O System). If you use (as described in I/O System): GLDEF_C VOID PrintPDDs (VOID) { HANDLE h; TEXT b[E_MAX_NAME+2]; for (h=0; (h=p_devfnd(h,"*",E_PDD, &b[0]))>=0;) p_printf(" %s",&b[0]); } 11-1 PLIB REFERENCE to get a list of PDDs, the list would include: FSY.LOC the toc:: PDD FSY.ROM the Rom:: PDD FSY.REM the REM:: PDD SSD drives SIBO machines have 2 to 4 (depending on the machine) Solid State Disk (SSD) drives, taking SSDs of various type and capacity (from 32K bytes to 8Mb and beyond). SSDs are so called because they are based on silicon memory with no moving parts. The different types of SSD (in order of decreasing unit cost) include: e static RAM with integral Lithium battery e Flash EPROM ¢ one-time-programmable ROM e masked ROM An SSD drive can physically and logically mount any type of SSD. The physical interface between an SSD and the drive has only 6 connectors (4 for power, and 2 for data). The 2 data connections are an instance of a high speed serial channel. This is a fundamental part of the SIBO hardware architecture, implemented in custom chips. It can be used to communicate with other peripherals. The high speed serial channel is driven synchronously by a clock which normally runs at 3.84 MHz, giving a data transfer speed comparable to the more expensive hard disks on PCs but, without any latency for the drive head to position to the required track. Writing to a Flash SSD is slower because of the time taken to program the EPROM and, in this case, the speed of writing is twice as fast as writing to a typical floppy disk on a PC. SSDs are driven by the Loc: : file system using a number of subsidiary PDDs (Physical Device Drivers) handling the different SSD types and the internal m: device. The list of PDDs produced by calling PrintPDDs (described above) would include: LOC. TYO the RAM SSD PDD LOC. TY1 the Flash SSD PDD LOC.TYM the internal RAM (“:) PDD Files are stored in a completely different way on RAM SSDs from Flash SSDs. SSD drive doors have a switch that keeps the file server informed of possible SSD removals and insertions. After an SSD drive door has been opened the file server checks each drive to see if it contains a new SSD. When the file server detects a new SSD it automatically mounts the new SSD. If a previous occupant of the SSD drive had one or more files open on it, the file server keeps a record of the SSD. When access is subsequently attempted on a file channel on that SSD, the file server uses p_notify (described in the chapter Error Handling) to request the user to replace the SSD and to retry or to abandon the channel. If the user replaces the SSD and selects retry such that the operation completes successfully, the application process is not made aware of any problem. If the user chooses to abandon the channel, the operation completes with the error E_FILE_ABoRT. When this occurs, the channel is disconnected from the file and put into an abort state where the only possible operation is to close the channel (any operation other than p_close fails with the error E_FILE_ABORT). An application that receives an E_FILE_ABORT error can either make do without the file (which probably means having to exit) or to appeal yet again to the user to put the SSD back in the drive and, if the user does put the SSD back, to locate and re-open the file and recover. The latter takes more code but is kinder to the user. 11 FILES Unattended applications The file server will also normally call p_notify to give the user a chance to retry any file operation that fails on a mounted medium. This kind of failure is rare on SSDs but is more common on magnetic media - especially floppy disks. In some cases, the user may be able to correct the error (eg to close the door on a floppy disk drive) and successfully retry. If the user abandons, the file operation completes with an error other than £_FILE_ABORT (eg E_FILE_READ if a floppy disk read fails). This scheme whereby the file server uses p_notify to give the user a chance to correct the problem and retry works well when the client process is an interactive application but poorly if the requesting process is designed to run unattended (eg a communications program) or is itself a server process (eg the window server trying to read a font file). Such processes can use p_setnotify (FALSE), described in the chapter Error Handling, to stop the file server from using the notify service. In this case all errors are returned directly. RAM SSDs RAM SSDs use static RAM backed up by an integral Lithium battery. While the RAM SSD is inserted in an SSD drive, it draws current from the SIBO machine. When the SSD is removed, it relies on its internal battery to maintain its data. The allocation of the space for directories and files on a RAM SSD is block structured - identical to that which is used on PC hard disks and floppy disks. Just like floppy disks on a PC, there is a limit to the number of files that may appear in the root directory (where this limit includes directory files). This root directory limit does not limit the number of files that can be stored on a RAM SSD since any number of files may be stored in subdirectories. The format function (described below in this chapter) automatically allocates the capacity of the root directory as a function of the capacity of the SSD. The dependence of the number of files that may appear in the root directory on SSD size is as follows: less than 128K 128K or greater but less than 256K or greater but less than 512K or greater but less than 1M 1M or greater but less than 2M 2M or greater but less than 4M 4M 6M 8M The RAM SSD PDD on the toc: : file system on SIBO machines does not buffer written data. This protects the integrity of the SSD contents from being corrupted by application process or system crashes occurring while files are open, or by the removal of the SSD while files are open on it. Flash SSDs Flash SSDs use Flash EPROM chips in which all the data bits are initially set. A particular bit may be cleared by programming but it can not be set again unless all the bits on the chip are set (by erasing the chip). Unlike older generation EPROMs, Flash EPROMs are erased electrically (rather than by exposure to UV light). The speed with which Flash can be programmed is also substantially faster than the old UV erasable EPROMS. A Flash SSDs may be erased in its SSD drive by formatting (formatting is described later in this chapter). Since Flash memory does not require a battery to maintain it, the data on a Flash SSD is very secure - more so than on RAM SSDs or magnetic media. Flash memory is also cheaper than RAM. The Flash filing system is designed such that the logical interface to files on a Flash SSD (whether reading or writing) is entirely equivalent to that on other devices (RAM SSD, hard disk etc). However, when overwriting or when deleting a file, space is consumed and is not recovered until the SSD is next formatted. Note also that renaming a file, setting the date and time or setting the file attributes all use up additional space and can therefore fail through lack of remaining capacity (the function returns E_FILE_FULL). However, deleting a file does not consume any further space. 11-3 PLIB REFERENCE You can randomly access a Flash file and overwrite sections of it in exactly the same way as for any other file. However, in contrast to read/write block storage devices, overwriting incurs a storage overhead. For example, the following code: pos=0L; do { p_seek (chan, F_FABS, pos) ; } while (!p_write(chan,"Pointless", 8) ); continuously overwrites the first 8 bytes of the file on channel chan. On a RAM file, this would work indefinitely and just exercise the hardware. On a Flash file, the code would work but each iteration would use up space on the Flash SSD and the p_write would eventually fail and return E_FILE_FULL. In practice, you should not worry about limited overwriting of file data - for example, to patch a program file or to update a header. However, you cannot reasonably repeatedly and indefinitely overwrite a Flash file and, for example, Flash SSDs are unsuitable for B-tree index files. See the chapter Database Files for a description of a Flash-friendly set of functions allowing random access to, and the deleting and updating of, variable length records. These functions take advantage of the fact that a byte can be physically overwritten (with no storage penalty) provided that the overwrite is such that each bit in the byte either stays the same or is cleared. Flash SSDs are particularly attractive for storing program files, read-only data (say for data-referral applications), for securely storing logged data in the field and for archiving data. The storage of files on a Flash SSD is very different from that used on RAM SSDs. The scheme is not at all block structured but is based on linked variable length records. All this is hidden from the caller and the logical interface to a file on a Flash SSD is the same as for a file on a RAM SSD (or any other medium). The data in each write to a file on a Flash SSD is written straight to the SSD (ie it is not buffered)!. However, in the interests of efficient storage, the record just written is kept open (with a oxtf££ length) for as long as possible - until the file channel is closed or flushed using P_FFLus# or until a write to another file on the same SSD occurs. The date and time stamp is also not written until the file channel is flushed or closed. If the SSD is removed or the machine resets while there is an open file channel with an outstanding write, the record is closed when the file is next accessed (the file is then also stamped with the current date and time). On a Flash SSD, there is no limit on the number of files or directories in the root directory. You will also find that it is possible to fit slightly more data on a freshly formatted Flash SSD than on a RAM SSD of the same nominal capacity. This is due to the fact that files are allocated space in multiples of whole blocks on a RAM SSD whereas Flash files are stored in exactly sized variable length records. File specifications A full file specification has the form: where the components are: the file system node (eg Loc: :) the device name (eg B:) the directory name (eg \NOTES\OLD\) the file name (eg PLaNs). the extension name (eg .TPD) !This may not be the case for text files, which are handled by a layer over the FL: device. The buffering of such files is thus outside the file server's control. 11 FILES An example of a full file specification is: LOC: :B: \NOTES\OLD\PLANS . TPD Although the file server assumes that a file specification may be decomposed into a , , , and , it avoids any assumption of the detailed syntax of the , , and components. This is left to the file system code that implements the file specification manipulation functions p_fparse and p_chdir. Leaving such detailed considerations to the file system is designed to allow the file stores of remote foreign file systems to be mapped transparently to the file server model (which is compatible with the MSDOS filing system). For example, in the VMS operating system, the above example of a file specification might translate to: REM: : USER: [NOTES.OLD]PLANS.TPD and on an Apple Macintosh, it might be: REM: :HD40:NOTES:OLD:PLANS.TPD and on Unix, it might be: REM: : USER: :NOTES/OLD/PLANS.TPD To prepare for such foreign systems, applications should follow the example of the file server and avoid making assumptions about the syntax of file specifications and use functions such as p_fparse and p_chdir to manipulate file specifications. File specifications satisfy the following rules: e a file specification will not require more than p_FNamEs1zE (128) bytes, including a zero terminator e the component is always p_FsySNAMESIzE bytes long (excluding any zero terminator) where P_FNaMESIzE and p_FsysNameEsi1zeE are defined in p_file.h. Except for and the p_FNAMESIZE total, you should not make any assumptions about the maximum size of the components of a file specification. Default path The file server stores a default node, device and directory, also called the default path, for each of its clients. In addition, the file server stores a lower level default path, the system-wide default path. This is the default path assigned to new clients when they connect to the file server. A process that is a client of the file server uses: p_setpth to set its default path p_getpth to get a copy of its default path A process (normally a system process such as the shell) can change the system-wide default path - the path subsequently assigned to connecting clients - by calling p_setdefaultpath. Channel-based services A client of the file server can use p_open on the Fru: device with different values of mode to do the following: P_F STREAM, to open a file and manipulate it as a flat binary file P_FSTREAM_TEXT P_FTEXT to open a file and manipulate it as a record-oriented text file P_FDIR to get a list of the files and subdirectories in a directory P_FDEVICE to get a list of the available devices P_FNODE to get a list of the available file systems P_F FORMAT to format a Loc: : device 11-5 PLIB REFERENCE Once a channel has been opened, one or more I/O functions (depending on mode) may be requested synchronously using p_iow (or a convenience function such as p_read) or asynchronously using p_ioc or p_ioa. There is no practical limit to the number of file channels that may be opened in the system or by a particular process. Non-channel-based services These are file operations not involving the F1L: device driver. They are requested by the following function calls: p_fparse, to parse a file specification p_fparseasync p_chdir, to change the directory component of a file specification p_chdirasync p_ninfo, to get file system node information p_ninfoasync p_dinfo, to get information on the medium in a device p_dinfoasync p_finfo, to get information on a file or a directory p_finfoasync p_rename, to rename a file or a directory p_renameasync p_delete, to delete a file or a directory p_deleteasync p_mkdir, to make a new directory p_mkdirasync p_sfstat, to set the attributes of a file (and to set the volume label of a medium) p_sfstatasync p_fdate, set the modification date and time of a file p_fdateasync The name of asynchronous equivalents are generated by appending async to the name of the corresponding synchronous function. The asynchronous function takes the same parameters as the synchronous function but with the addition of a status word parameter at the end of the parameter list. Asynchronous file operations The file server is essentially asynchronous in its operation and most functions are provided in both asynchronous and synchronous forms. Because the file server has a higher priority than any of its clients, any operation that does not wait for a slow external device will have completed by the time an asynchronous request has returned. This includes any operation on the Rom: : file system or where the toc: : file system accesses SSDs or the internal RAM drive (M:). However, when accessing the REM: : file system where the connection is via an RS232 cable, any asynchronous request is almost certain to return before the operation is complete. Although, in practice, most file operations complete in a fraction of a second, a particular file request may take an extended time. For example, a read of 8K bytes from a Rem: : file, connected over a 1200 baud modem link, would take well over a minute. However, a request will never take an indefinite time to complete (as, for example, a write to the parallel port can do when the printer is off line). For these reasons, the file server does not actively support a cancel service. (Incidentally, it is also worth considering the state in which a file would be left if a partially completed write operation were cancelled.) A request on an open file channel may be cancelled using p_iow(P_FCANCEL) but the cancel will not cause the operation to complete any sooner (and it will not complete with E_FILE_CANCEL). In general, it is preferable to simulate a cancel by waiting for current service to complete and forcing the status word to E_FILE_CANCEL, as illustrated below. 11-6 11 FILES The cancelling of an asynchronous request such as: p_ioc (pcb, P_FREAD, &stat,buf,1len); may be simulated by: if (stat==E_FILE_PENDING) { p_waitstat (&stat); /* wait for completion */ stat=E_FILE_CANCEL; /* report a cancel */ } This will normally complete almost immediately. You should, however, be aware that in rare cases when using the remote filing system (say, when the remote computer is re-booted and the communications link is set up to have a long timeout period) it may take an extended time to complete. Manipulating file specifications p_fparse (or f_fparse) Parse a file specification INT p_fparse(TEXT *name, TEXT *related, TEXT *full, P_FPARSE *pcrk); INT f_fparse(TEXT *name, TEXT *related, TEXT *full, P_FPARSE *pcrk); INT p_fparseasync(TEXT *name, TEXT *related, TEXT *full, P_FPARSE *pcrk, WORD *stat); Builds a full file specification of the form: as a zero terminated string in fu11, where the components are: the file system node (eg Loc: :) the device name (eg B:) the directory name (eg \NoTES\OLD\) the file name (eg PLans). the extension name (eg . TPD) The parameters name and full may point to the same address, but related and ful should not have the same address. There should be at least p_rNamestze (128) bytes of memory reserved at fu11. Note that p_rNamEs1zE bytes are always written to fu11 even when the generated file specification is shorter. If you have a name produced by a previous call to p_fparse and you are calling p_fparse again to fill in the struct at pcrk, you still need p_rnames1ze bytes at full. Subject to the restrictions described below, under the heading Using p_fparse across filing systems, the components making up the full file specification are taken from the following (in order of precedence): e the zero terminated file specification name e the zero terminated related file specification re1atea (the related file specification may be omitted by passing nuLL). e the default path (the node, device and directory as set by p_setpth) Thus components are only taken from related if they are missing from name. If there are still missing components, they are taken from the default path. Note that the and fields in name and related may contain wildcard characters. The output file specification in fu11 is converted to upper case characters. Prior to EPOC version 2.31, conversion to upper case was performed by folding (as by using p_tofoid). From EPOC version 2.31 onwards the conversion is by converting to upper case (as by using p_toupper). This has no significant effect on UK applications, but improves the handling of file names containing, for example, accented characters. 11-7 PLIB REFERENCE Further information on the content of £u11 is written to the P_FPARSE struct pcrk. If this information is not required, pcrk may be passed as NULL. If perk is not NULL, it should be the address of a P_FPARSE struct, defined in p_file.h as follows: typedef struct { UBYTE system; /* file system name length */ UBYTE device; /* device name length */ UBYTE path; /* path name length */ UBYTE name; /* name length */ UBYTE ext; /* extension length */ UBYTE flags; /* information on the presence of wildcards */ } P_FPARSE;. The members system, device, path, name and ext are set to the lengths of their corresponding fields in full, including delimiters. The flags field contains information on the occurrence of wild card characters in the extended specification according to the following masks: P_PWILD_ANY if set, the file specification contains one or more wild card characters and at least one of the following bits are set P_PWILD_NAME if set, the name field contains one or more wild card characters P_PWILD_EXT if set, the extension contains one or more wild card characters The function returns zero if successful or one of the following negative error numbers: E_FILE_NAME either name or related contains an invalid name E_FILE_DEVICE either name or related contains an invalid device name E_FILE_DIR either name or related contains an invalid directory name E_GEN_FSYS either name or related contains an invalid file system name or the file system does not exist The function f_fparse is identical to p_fparse except that it calls p_leave (passing the error number) rather than return a negative error number. As an example, if the current default path is "Loc: :a:\", then p_fparse ("FRED", "\\FILES\\DOCS\\JOE.DOC", buf, &crk) ; writes the zero terminated string Loc: :A: \FILES\DOCS\FRED.DOC to buf and {5,2,12,4,4,0} tocrk. The function is often used to change the extension of a file as, for example, in: p_fparse (".BCK", "\\FILES\\DOCS\\JOE.DOC", buf, &crk) ; Using p_fparse across filing systems Since the syntax of a file specification may differ between filing systems (see File specifications, earlier in this chapter) the functions p_fparse and p_chdir are implemented in the code of each file system. The function p_fparse builds a full file specification from up to three sources, which may therefore specify two or more different filing system nodes. This section describes the rules that determine which implementation performs the function p_fparse in any particular case, and which of the three sources contribute towards the full file specification. The three sources will be referred to as name, related and the default path, as in the description of p_fparse. The node which performs the p_fparse is determined from name, related and the default path, according to the following rules: e If neither name nor related contain an explicit component, the node specified by the default path is assumed (the default path always has an explicit component). e If either name or related (but not both) contain an explicit component, this is taken to be the node which performs the p_fparse. e If both name and related contain explicit components, the specified in name is taken to be the node which performs the p_fparse. 11-8 11 FILES If an explicit in related differs from that determined from the above rules, related is assumed not to be valid for the specified node and is ignored by p_fparse. If the in the default path differs from that determined from the above rules, the default path is assumed not to be valid for the specified node and is ignored by p_fparse. Thus, assuming that the default path is "Loc: :a:\", then p_fparse ("PLIB.MAK", "REM: :HD40:SOURCE:",buf,NULL) ; is handled by the remote filing system and writes the string REM: :HD40:SOURCE:PLIB.MAK to buf, but p_fparse ("LOC: : PLIB.MAK", "REM: : HD40: SOURCE: PLIB.MAKE", buf, NULL) ; is handled by the local filing system and writes the string Loc: :A:\PLIB.MAK tO buf (related is ignored). All sources that are not ignored by p_fparse are checked as being valid for the specified node, even if they do not contribute to final full file specification. Thus p_fparse ("LOC::C:\\PLIB.MAK", "PLIB.MAKE", buf, NULL) ; will fail with error =_F1LE_NamE because the in related is not valid for the Loc: : filing system, even though all the components of a full file specification are present in name. This type of situation is typically likely to occur when copying files between the rem:: and toc: : filing systems. p_chdir Change the directory in a file specification INT p_chdir (TEXT *src, TEXT *outp, INT mode, TEXT *subdir) ; INT p_chdirasync(TEXT *src, TEXT *outp, INT mode, TEXT *subdir, WORD *stat); Parse src with a nut related name and change its directory to produce a new file specification outp according to mode, which should be one of: P_CD_ROOT to move to the root directory P_CD_PARENT to move to the parent directory P_CD_SUBDIR to move to the zero terminated subdirectory subdir The parameter subdir is ignored if mode is not P_CD_SUBDIR. If src contains a file name then this name is retained and appended to the new directory specification outp. There should be at least p_rnames1zz (128) bytes of memory reserved at outp. Note that p_rNAMESIZE bytes are always written to outp even when the generated file specification is shorter. The parameters src and outp may point to the same address. Note that p_chdir is just a means of setting up outp from mode, src and subdir with no consideration to the existence of the directories in src and subdir. The operation is performed by the appropriate file system (as specified by the component of the full file specification) allowing a file specification to be manipulated without knowledge of the detailed structure (eg the delimiters used) of file specifications on that . When mode is P_cD_SUBDIR, subdir is just inserted into the full file specification at the appropriate position. Except for checking that the resultant full file specification length does not exceed p_FNAMESIZE bytes, the validity of outp is not checked. The function returns zero if successful or a negative error number if it fails. As well as the errors that can be returned by the internal parse of src, p_chdir can return: E_FILE_NXIST mode Was P_CD_PARENT and src was already at the root directory E_FILE_DIR subdir contains an invalid directory name E_FILE_NAME inserting subdir would make the file specification length exceed p_FNAMESIZE bytes 11-9 PLIB REFERENCE For example, if the current default path is "Loc: :a:\", then p_chdir("\\fred\\*.c", buf, P_CD_SUBDIR, "bill") writes LOC: :A:\FRED\bill\*.C to buf p_chdir ("\\fred\\aa.c",buf,P_CD_SUBDIR, "jim") writes LOC: :A:\FRED\jim\AA.C to buf p_chdir("\\fred\\aa.c", buf, P_CD_SUBDIR, "bill\\jim") writes LOC: :A:\FRED\bill\jim\AA.C to buf (although, depending on where bi11\4jim came from, this breaks the spirit of p_chdir by including a delimiter in subdir) p_chdir ("\\fred\\jim\\*", buf, P_CD_PARENT, NULL) writes LOC: :A:\FRED\* to buf p_chdir("\\fred\\jim\\*", buf, P_CD_ROOT, NULL) writes LOC: :A:\* tO buf p_chdir ("rem: :hd40:fred:aa.c",buf,P_CD_SUBDIR, "jim") writes REM: :HD40:FRED: jim:AA.C to buf The default node, device and directory p_setdefaultpath Set the system-wide default path INT p_setdefaultpath(TEXT *name) ; Set the file server system-wide default path to name. This is the default path that is assigned to a process when it connects to the file server. The parameter name is a zero terminated string of the form: where is the file system node (eg Loc: :) is the device name (eg B:) is the directory name (eg \NOTES\OLD\) The parameter name is parsed with a nut related file name and any file name and extension component is discarded. The function returns zero if successful or the same error numbers as for p_fparse if name failed to parse. A successful call implies that file system exists at the time of the call (note that some file systems such as REM: : are not permanently installed) and that and are valid for the file system. It does not mean that contains a medium, or that exists. For example, after: p_setdefaultpath ("LOC::B:\\"); all new file server clients will initially have the default path of Loc: :B:\. p_setpth Set the default path of this process INT p_setpth(TEXT *name) ; INT p_setpthasync(TEXT *name, WORD *stat); Set the default node, device and directory for this process to name where name is a zero terminated string of the form: where is the file system node (eg Loc: :) is the device name (eg B:) is the directory name (eg \NOTES\OLD\) The parameter name is parsed with a nut related file name and any file name and extension component is discarded. The specified directory must exist. 11-10 11 FILES The function returns zero if successful or a negative error from p_fparse if name failed to parse or: E_FILE_DEVICE if the device does not exist E_FILE_NOTREADY if the device does not contain a medium E_FILE_DIR if the directory does not exist When a process connects to the file server, it is assigned the file server system-wide default path, set by the last call to p_setdefaultpath. For example, if the current default path is Loc: :m:\, then: p_setpth("B:"); sets the default path to Loc: :B:\. p_getpth Get the default path of this process VOID p_getpth(TEXT *name) ; Write the default node, device and directory for this process as a zero terminated string to name of the form: where is the file system node (eg Loc: :) is the device name (eg B:) is the directory name (eg \NOTES\OLD\) There should be p_rnames1zeE bytes of memory reserved at name. There is no asynchronous version of p_getpth because the process default node, device and directory is stored by the file server without recourse to the appropriate file system (although the interpretation of and may depend on ). p_getpthbyid Get the default path by process id INT p_getpthbyid(HANDLE pid, TEXT *name); Write the default node, device and directory of process pia as a zero terminated string to name. The content of name is as for p_getpth, described above. Returns zero if successful or E_FILE_Nxist if pia is not a client of the file server. Operations on nodes and devices p_open(P_FNODE) Get a list of nodes INT p_open(VOID **ppfcb, TEXT *name, UINT mode); INT p_iow(VOID *pfcb, INT func, TEXT *buf, P_NINFO *pinfo); INT p_close(VOID *pfcb) ; To get a list of file system node names, you: e = call p_open with a mode of P_FNoDE to open a node list channel e repeatedly call p_iow with a func of p_rREap to read each node name (until it returns E_FILE_EOF) e = call p_close to close the node list channel 11-11 PLIB REFERENCE The name parameter to p_open may be "FIL:" or NULL. The mode parameter must be P_FNODE. Each successful call to p_iow(P_FREAD) writes the next file system node name as a zero terminated string to buf (buf should have a capacity of P_FSYSNAMESIZE+1 (6) bytes). The call to p_iow(P_FREAD) returns E_FILE_EOF after all the node names have been read. If the parameter pinfo is not NULL it is taken as the address of a P_NINFo struct which is filled with the same node information as would be obtained by calling p_ninfo, described below. For example: LOCAL_C VOID ListNodes (VOID) { VOID *ncb; TEXT buf [P_FSYSNAMESIZE+1]; p_open(&ncb, "FIL:",P_FNODE) ; while (!p_iow(ncb,P_FREAD, &buf[0],NULL) ) p_puts (&buf[0]); p_close(ncb); } lists the current node names. p_ninfo Get node information INT p_ninfo(TEXT *node, P_NINFO *pninfo); INT p_ninfoasync(TEXT *node, P_NINFO *pninfo, WORD *stat); Write information on the zero terminated file system node to the P_NINFo structure at pninfo and return zero if successful or the negative E_GEN_Fsys if node is invalid or does not exist. The P_NrnFo struct is defined in p_file.h as: typedef struct { UWORD version; UWORD type; UWORD formattable; UBYTE spare[26]; } P_NINFO;. where pninfo->type contains: P_FSYSTYPE_FLAT if file system node does not support hierarchical directories P_FSYSTYPE_HIER if file system node does support hierarchical directories and pninfo->formattable is TRUE if file system node supports the formatting of its devices and FALSE otherwise. The version field pninfo->version is designed to allow future versions of p_ninfo (which would write further information to pninfo->spare[]) to be identified by the caller. At the time of writing, pninfo->version is Set to 2. p_locchg Check if LOC:: has changed INT p_locchg(INT mask) ; This function is only available in EPOC version 2.16 or later. Check if Loc: : has changed (for example, if the SSD door has been opened) since the last call to p_locchg. The return value has the bits that were set in mask either set or cleared, depending on whether or not LOC:: has changed. The use of a mask allows multiple independent calls from within one application, with each caller consistently using one specific bit of mask. Bit 15 is ignored, so as never to return a negative value (even though the call can never fail). Bits 8 to 14 inclusive are reserved for system use. An application may therefore make up to eight independent calls, using bits 0 to 7. Typical calling code would make an initial call to p_locchg, to ensure that only subsequent changes are detected. It would then poll for changes by calling p_locchg, say, every two seconds. 11-12 11 FILES p_open(P_FDEVICE) Get a list of devices INT p_open(VOID **ppfcb, TEXT *name, UINT mode) ; INT p_iow(VOID *pfcb, VOID *buf, NULL); INT p_close(VOID *pfcb) ; To get a list of device names for a particular file system you: e call p_open with a mode of P_FDEVICE to open a device list channel e repeatedly call p_iow with a func of p_FReEap to read each device name (until it returns E_FILE_EOF) e call p_close to close the device list channel The name parameter to p_open should be a zero terminated node name (with or without a leading F1L:). The parameter mode must be p_rpevice. If the node name is illegal or does not exists, p_open fails and returns E_GEN_Fsys. If the node does not support devices (as is the case for Rom: :), p_open fails and returns E_GEN_NSUP. Each successful call to p_iow(P_FREAD) Writes the next device name as a zero terminated string to buf (buf should have a capacity of p_rNames1zeE (128) bytes). The call to p_iow(P_FREAD) returns E_FILE_EOF after all the device names have been read. The parameter following but in the call to p_iow (P_FREAD) should be nut. For example: LOCAL_C VOID ListDevices (VOID) { VOID *ncb, *dcb; INT ret; TEXT node[P_FSYSNAMESIZE+1]; TEXT device [P_FNAMESIZE]; p_open(&ncb, "FIL:",P_FNODE) ; while (!p_iow(ncb,P_FREAD, &énode[0],NULL) ) { p_puts (&node[0]); if ((ret=p_open (&dcb, &node[0],P_FDEVICE) ) <0) { p_puts("\tDevices not supported"); continue; } while (!p_iow(dcb,P_FREAD, &édevice[0],NULL) ) p_printf("\t%s",&device[0]); p_close(dcb); } p_close(ncb); } lists the current node names with their devices. p_dinfo Get device information INT p_dinfo(TEXT *dname, P_DINFO *pdinfo) ; INT p_dinfoasync(TEXT *dname, P_DINFO *pdinfo, WORD *stat); Write information on the device with zero terminated name dname and on the medium (eg SSD or diskette) that is mounted on device dname to the p_DINFo struct at pdinfo. Returns zero if successful or one of the following negative error numbers: E_FILE_DEVICE if an invalid or non-existent device is specified E_FILE_NOTREADY if the device does not contain a medium E_FILE_CORRUPT the Loc: : sub file system on a SIBO machine has recognised a RAM or Flash SSD without a valid boot record (probably because the SSD is not formatted) E_FILE_UNKNOWN none of the file systems recognise this medium and formatting is unlikely to make it readable 11-13 PLIB REFERENCE The P_p1nFo struct is defined in p_file.h as: typedef struct { UWORD version; UWORD mediatype; UWORD removable; ULONG size; ULONG free; UBYTE name [P_VOLUMENAME] ; WORD batterystate; UBYTE spare[16]; } P_DINFO;. Except for pdinfo->removable, which is TRUE if the device has removable media, all information describes the medium mounted on device dname. The least significant byte of pdinfo->mediatype takes one of the following values: P_FMEDIA_UNKNOWN unknown media type P_FMEDIA_FLOPPY drive takes 3.5 inch or 5.25 inch diskettes P_FMEDIA_HARDDISK drive contains a hard disk P_FMEDIA_RAM drive contains a RAM disk (read/write) P_FMEDIA_FLASH drive contains a Flash disk P_FMEDIA_ROM drive contains a ROM disk (read only) P_FMEDIA_WRITEPROTECTED drive contains a write protected medium The most significant byte of pdinfo->mediatype takes a combination of the following bit flags: P_FMEDIA_COMPRESSIBLE it is worth compressing out logically deleted records from a file on this medium to reduce both the file size and the storage consumed by the file (not set for Flash SSDs) P_FMEDIA_DYNAMIC the media capacity pdinfo->size can change over time P_FMEDIA_INTERNAL media is internal (implicitly not removable) P_FMEDIA_DUAL_DENSITY device drive is dual density (if this is set then p_open (P_FFORMAT) can take the optional p_FLowpENsiTy for a low density format) P_FMEDIA_FORMATTABLE media is formattable The members pdinfo->size and pdinfo->free give the total capacity of the medium in bytes and how much of that capacity is free (also in bytes) respectively. If p_rMzDIA_DYNAMIC is set in pdinfo->mediatype (as it is for the internal RAM device m:), pdinfo->size and pdinfo->free may change over time. The volume name, with a maximum of 12 characters (for example, "DISKNAME.DSK"), 1s written as a zero terminated string to spdinfo->name [0]. The system is designed to be able to take advantage of hardware which can detect a low battery voltage in a RAM SSD. When this is not possible (either because the medium is not a RAM SSD or because the hardware does not support it), pdinfo->batterystate contains E_GEN_NsupP. If the SSD does contain a battery and the hardware to detect a low voltage, pdinfo->batterystate contains FALSE if the battery is low or TRUE (more precisely, a non-zero value other than E_cEN_NsupP) if the battery voltage is acceptable. The content of this field is undefined for values of pdinfo->version less than 3. The version field pdinfo->version is designed to allow future versions of p_dinfo (which would write further information to pdinfo->spare[] to be identified by the caller. In the following example, ListDevices produces a device list with some device information, including the devices on REM:: (if the file server is connected to a remote file server) as well as Loc: :. 11-14 LOCAL_C TEXT *GetTypeText (UINT type) { switch (type) { case P_FMEDIA_FLOPPY: return "Floppy"; case P_FMEDIA_HARDDISK: return "Hard"; case P_FMEDIA_FLASH: return "Flash"; case P_FMEDIA_RAM: return "RAM"; case P_FMEDIA_ROM: return "ROM"; case P_FMEDIA_WRITEPROTECTED: return "Protected"; } return "Unknown"; LOCAL_C VOID ListDevices (VOID) VOID *ncb, *dcb; INT ret; TEXT device [P_FNAMESIZE]; TEXT bb[E_MAX_ERROR_TEXT_SIZE]; P_DINFO dinfo; p_printf(" Device Name Type Size p_printf ("====s======= =S==S=S=S=S==== S=S=SSS==5== SSS 555 p_open(&ncb, "FIL:",P_FNODE) ; while (!p_iow(ncb,P_FREAD, &device[0],NULL) ) { if (p_open(&dcb, &device[0],P_FDEVICE) ) continue; while (!p_iow(dcb,P_FREAD, &édevice [P_FSYSNAMESIZE],NULL) ) { if ((ret=p_dinfo(&device[0],&dinfo) ) <0) { p_errs (&bb[0],ret i p_printf("%- 12s %- 12s<%s>", &device[0],"**Failed**", sbb[0]); continue; } p printf ("S- 12s 4- 12sS- lis S7IdK >10, (dinfo.free+512)>>10); } p_close(dcb); } p_close(ncb); } p_open(P_FFORMAT) INT p_open(VOID **ppfcb, TEXT *name, UINT mode) ; INT p_read(VOID *pfcb, VOID *buf, UINT len); INT p_close(VOID *pfcb); To format a medium in a device you: 11 FILES Format a device e call p_open with a mode of p_FrormatT to open a device format channel e call p_read to get the total format count (optional) e repeatedly call p_reaa until it returns E_FILE_EOF e call p_close to close the device format channel 11-15 PLIB REFERENCE The name parameter to p_open should be a zero terminated file specification with a root directory (name is parsed with a nuut related name) of the form: LOC: :\ to format the medium in , giving it the volume name . The volume name may subsequently be changed using p_sfstat, described later in this chapter. The mode parameter to p_open must be Pp_Frormat. If the device supports dual density formatting (as may be established by calling p_dinfo), P_LFLOWDENSITY may be or'ed into mode to format at the lower density. The call to p_open returns zero if successful. As well as the error numbers that can be returned by p_fparse, p_open(P_FFORMAT) can also return one of the following negative error numbers: E_GEN_NSUP the file system, device or medium does not support formatting E_FILE_PROTECT the medium is write protected or is read-only (eg a ROM SSD) At the time of writing, formatting is only supported on the Loc: : system on a SIBO machine. You can tell in advance if a file system supports formatting by calling p_ninfo (described above). If the file system does support formatting, you can tell if a device contains a formattable medium by calling p_dinfo (also described above). The first call to p_read writes a uworD total count to buf and returns zero. This count represents the number of subsequent p_read calls required to complete the format. The 1en parameter to p_read is ignored in all cases. A call count can be combined with the total format count to present a percentage done indication. When the format is complete, p_read returns E_FILE_EoF; you should then call p_close. Abandoning the format, by calling p_close prematurely, will leave the medium in a corrupted state. For example, the following function: LOCAL_C VOID FormatDevice (TEXT *name) { INT err,i; UWORD count; VOID *chan; TEXT bb[E_MAX_ERROR_TEXT_SIZE]; chan=NULL; if ((err=p_open(&chan, name, P_FFORMAT) ) <0) goto exit; if ((err=p_read(chan, &count, 0) ) <0) goto exit; p_printf ("Formatting %s count=%d",name, count) ; i=1; while ((err=p_read (chan, &val,0) ) >=0) ploprint ( \rse05u", 2 ++); if (err==E_FILE_EOF) err=0; exit: p_close(chan); if (err<0) { p_errs (&bb[0],err); p_printf("\r\nFormat failed: %s",&bb[0]); } else p_printf("\r\nFormat complete") ; } could be called with: FormatDevice ("LOC: :A:\\BACKUP") ; to format the SSD in drive a:, giving it the volume name BACKUP. 11-16 11 FILES p_locdevice Read media information of a local device INT p_locdevice (INT aDevice, UWORD *pMedia) ; This function is only available in EPOC version 3.18 or later. Write, to *pMedia, the media type of the local device (that is, a device on the toc: : file system) specified by aDevice. The value of aDevice must be one of: @ = 'M' (0x4d) e ='T (0x49) e ='A' (0x41) to 'H' (0x48) inclusive. where 'T' (Internal) is an alias for 'M'. Apart from two additional flags, the value written to *pmedia is the same as the value written to the mediatype of a p_DINFo struct by p_dinfo. The call is more efficient than a call to p_dinfo, provided that the media type is the only information that is required. The two extra flags that may be written to *pMedia are &_FMEDIA_BATTERY_VALID and E_FMEDIA_BATTERY_GooD, defined in epoc.h. If —_FMEDIA_BATTERY_VALID is not set then the device does not support battery measurement. If it is set then E_FMEDIA_BATTERY_Goop Will be clear if the battery voltage is too low, otherwise it will be set. Returns zero or a negative error number. Errors that may be returned are: E_FILE_NOTREADY no device is available E_FILE_DEVICE the device specified by aDevice is not in the valid range of devices E_GEN_NSUP no PDD exists which can handle the device E_GEN_UNKNOWN Note that the media does not have to be mountable for this service to work. p_locreadpdd Direct read of local SSD INT p_locreadpdd(INT aDevice, LONG *aPos, VOID *aPtr, UINT aLen); This function is only available in EPOC version 3.18 or later. Read, to the buffer pointed to by apt r, aLen bytes starting at an offset of *apos bytes into the SSD from the local SSD (that is, an SSD on the toc: : file system) specified by aDevice. The data is read by means of direct access to the physical device driver (PDD) and the reading is therefore very efficient. The value of aDevice must be one of: e = 'M' (0x4d) e =6'T (0x49) e ='A' (0x41) to 'H' (0x48) inclusive. where 'T' (Internal) is an alias for 'M'. The medium must have been mounted prior to using this service. (To ensure the medium is mounted, just make any normal device access.) The service returns zero or one of the following negative error numbers: E_GEN_OS the medium is not mounted E_FILE_CORRUPT the specified offset is greater than the size of the SSD 11-17 PLIB REFERENCE Operations on directories and files p_open(P_FDIR) Get a directory list INT p_open(VOID **ppfcb, TEXT *name, UINT mode) ; INT p_iow(VOID *pfcb, INT func, TEXT *buf, P_INFO *pinfo); INT p_close(VOID *pfcb) ; To get a list of files in a directory you: e call p_open with a mode of P_FD1R to open a directory list channel e repeatedly call p_iow with a func of P_FREAD to read each directory entry (until it returns E_FILE_EOF) e call p_close to close the directory list channel The name parameter to p_open is a zero terminated file specification. Internally to the call to p_open, this is parsed with a wild card related name (such as "*.*") that will find all files in the directory. It is therefore not necessary to include a file name or file name extension in name unless you wish to restrict the directory search to files with that name and/or extension. If present, the file name and the file name extension in name would normally contain wildcards. Passing a name of "" produces the names of all the files in the current directory. The parameter mode must be P_FDIR. Each successful call to p_iow (P_FREAD) writes the next matching file name (excluding the node, device and directory component) as a zero terminated string to buf (buf should have a capacity of P_FNAMESIZE (128) bytes). The call to p_iow(P_FREAD) returns E_FILE_EOF after all the file names have been read. If the parameter pinfo is not NULL it is taken as the address of a P_INFo struct where p_1nro is defined in p_file.h as: typedef struct { UWORD version; UWORD status; /* status bits */ ULONG size; /* size of the file in bytes */ ULONG modst; /* system time of last modification */ UBYTE spare[4]; } P_INFO;. The file status pinfo->status has the following bit fields: P_FAWRITE set if file is not read-only P_FAMOD set if the file has been modified P_FAHIDDEN set if file is hidden P_FASYSTEM set if file is a system file P_FADIR set if the file is a directory file P_FAVOLUME set if the file is a volume name directory P_FATEXT set if file is a text file Note that a directory read may return with a volume name written to buf and p_FAVOLUME set in pinfo->status. This will only occur if the directory is a root directory of a PC-based device that has a volume name. A directory read only returns with P_FaTEXT set in pinfo->status when the file system can recognise a text file. The toc: : file system cannot recognise text files but a remote file server, running on an operating system (eg VMS) which can recognise text files, will set the p_raTExt bit as appropriate. The field pinfo->size gives the logical file length (the end-of-file position). The field pinfo->modst gives the time the file was last modified, expressed in system time format (the number of seconds since 00:00:00 January 1 1970). See the chapter Time, Timers and Dates for details on how to convert to and from the system time. The p_inro file information may also be obtained when using p_finfo, described below. 11-18 11 FILES Example #include LOCAL_D VOID *dcb=NULL; LOCAL_C VOID panic(TEXT *msg, INT errno) { TEXT bb[E_MAX_ERROR_TEXT_SIZE]; p_close(dcb); dcb=NULL; p_errs (&bb[0],errno) ; p_printf("%Ss: %s",msg,&bb[0]); p_leave (errno) ; } LOCAL_C VOID PrintDirLine (TEXT *name, P_INFO *pinfo) { P_DAYSEC ds; P_DATE dt; TEXT *p,b[40]; p=&b[0]; if (pinfo->status&P_FAVOLUME) p=p_scpy(p,"Vol,"); if (pinfo->status&P_FADIR) p=p_scpy(p,"Dir,"); if (pinfo->status&P_FAMOD) p=p_scpy (p, "Mod, "); if (! (pinfo->status&P_FAWRITE) ) p=p_scpy (p, "Read,"); if (pinfo->status&P_FASYSTEM) p=p_scpy (p,"Sys,"); if (pinfo->status&P_FAHIDDEN) p=p_scpy(p,"Hid,"); if (*(p-1)==',') *-—p=0; p_sttods (&pinfo->modst, &ds) ; p_dstodt (&ds, &dt) ; p_printf("S- 12s S8lu %02u-%02u-%02u %02u:%02u Zs", name, pinfo->size,dt.day+1,dt.month+1,dt.year,dt-.hour,dt.minute, &b[0]); } LOCAL_C VOID CDECL PrintDirList (TEXT *dir) { INT err,NoFiles; P_INFO info; TEXT name [P_FNAMESIZE]; if ((err=p_open (&dcb, dir, P_FDIR) ) !=0) panic("Failed to open directory file",err); NoFiles=TRUE; while (! (err=p_iow(dcb, P_FREAD, &name[0],é&info) ) ) { NoFiles=FALSE; PrintDirLine(&name[0],&info); } p_close(dcb); dcb=NULL; if (err!=E_FILE_EOF) panic("Failed to read directory",ret); if (NoFiles) p_printf("No files found"); } GLDEF_C INT main(VOID) { TEXT name [P_FNAMESIZE] ; while (p_get1l(">", &name[0],P_FNAMESIZE) ) p_enter((VOID *)PrintDirList, éname[0]); return (0); } 11-19 PLIB REFERENCE p_finfo Return file information INT p_finfo(TEXT *name, P_INFO *pinfo); INT p_finfoasync(TEXT *name, P_INFO *pinfo, WORD *stat); Parse name with a NULL related name and write information about the specified file (which may be a directory file) to the p_1NnFo struct at pinfo. The same file information is written to pinfo as is obtained when using p_iow(P_FREAD) on a directory list channel that was opened with p_open(P_FDIR), as described above. The version field pinfo->version is designed to allow future versions of p_finfo (which would write further information to pinfo->spare[]) to be identified by the caller. At the time of writing, pinfo->version Is set to 2. Returns zero if successful or a negative error number. As well as the p_fparse error numbers if the parse fails, p_finfo can return: E_FILE_DEVICE if the device does not exist E_FILE_NOTREADY if the device does not contain a medium E_FILE_DIR if the directory does not exist E_FILE_NXIST if the file does not exist The atomic nature of the p_finfo (unlike p_open) makes it a good choice for checking whether a file exists (it returns E_FILE_NxtsT if the file does not exist). To test for the existence of a directory, you can also use p_testpth, described below. p_testpth Test for the existence of a directory INT p_testpth(TEXT *dname) ; INT p_testpthasync(TEXT *dname, WORD *stat); Return zero if the directory component of the zero terminated file specification dname exists. The file specification dname is parsed with a nuLt related name and any file name component is discarded. If the parse fails, p_testpth returns the error return from p_fparse. It can also fail with: E_FILE_DEVICE if the device does not exist E_FILE_NOTREADY if the device does not contain a medium E_FILE_DIR if the directory does not exist For example, if the current path is Loc: :a:\, then: p_testpth("\\dirl\\dir2\\fred.c"); returns zero if the directory Loc: :A:\DIR1\DIR2\ exists. p_rename Rename a file or directory INT p_rename (TEXT *oldname, TEXT *newname) ; INT p_renameasync(TEXT *oldname, TEXT *newname, WORD *stat); Parse each of newname and oldname with a NULL related name and, if successful, rename oldname to newname. Both buffers should be at least P_rNames1zE bytes in length. The file specified by oldname must exist and newname must not already exist. Neither file name may include wild cards. Both files should be on the same node and device. On Loc: :, files may be renamed across directories (but this may not be supported by some remote systems). Prior to version 3.5 of EPOC, renaming across directories can fail when the target directory is the root of Loc: :m:. Directory files (which do not need to be empty) may be renamed, but not across different directories. 11-20 11 FILES The function returns zero if successful or a negative error number if it fails. As well as a p_fparse error number if either oldname Or newnane fails to parse, p_rename can return the following error numbers: E_FILE_DEVICE oldname and newname are on different devices or the device does not exist E_FILE_NOTREADY the device does not contain a medium E_FILE_DIR the directory does not exist E_FILE_EXIST newname already exists E_FILE_NXIST oldname does not exist E_FILE_LOCKED oldname exists but a process has the file open E_FILE_PROTECT the medium is write protected or is read-only (eg a ROM SSD) E_FILE_FULL not enough room on the medium for the rename (possible on Flash SSDs) For example, if the current path is toc: :a:\, then: p_rename ("\\dirl\\dir2\\fred.c", "\\dirl\\dir2\\jim.c"); renames FRED.C iN LOC::A:\DIR1\DIR2\ to giIm.c while: p_rename ("\\dirl\\dir2\\fred.c","fred.c"); effectively moves FRED.c iN LOC: :A:\DIR1\DIR2\ to the root directory. It is sometimes useful to parse newname With oldname as a related name before calling p_rename as in: GLDEF_C INT RenameFile(TEXT *oldname, TEXT *newname) { TEXT buf [P_FNAMESIZE]; if (ret=p_fparse (newname, oldname, &buf[0],NULL) ) return (ret); return (p_rename (oldname, &buf[0])); } Then, for example, calling: RenameFile("\\dir1l\\dir2\\fred.c","jim.c"); renames FRED.C iN LOC: :A:\DIR1\DIR2\ to gim.c while: RenameFile("\\dir1l\\dir2\\fred.c","\\jim.c"); renames FRED.C IN LOC: :A:\DIR1\DIR2\ to gIm.c and moves Jim.c to the root directory. p_delete Delete a file or directory INT p_delete (TEXT *name) ; INT p_deleteasync(TEXT *name, WORD *stat); Parse name with a nui related name and, if successful, delete the specified file (which may be a directory file). A directory can not be deleted unless it is empty. The function returns zero if successful or a negative error number if it fails. As well as a p_fparse error number if name fails to parse, p_delete can return the following negative error numbers: E_FILE_DEVICE the device does not exist E_FILE_NOTREADY the device does not contain a medium E_FILE_DIR the directory does not exist E_FILE_NXIST the file does not exist E_FILE_LOCKED a process has name open E_FILE_ACCESS name is a read-only file E_FILE_PROTECT the medium is write protected or is read-only (eg a ROM SSD) E_FILE_EXIST name is a directory that contains files 11-21 PLIB REFERENCE For example, if the current path is Loc: :a:\, then: p_delete("\\dir1l\\dir2\\fred.c"); deletes Loc: :A:\DIR1\DIR2\FRED.C. p_mkdir Make a new directory INT p_mkdir (TEXT *name) ; INT p_mkdirasync(TEXT *name, WORD *stat); Parse name with a NULL related name and create the specified directory. The name of the directory to be created is specified by the file name component of name if name contains a file name component. Otherwise it is specified by the directory component of the parsed file specification. Intermediate directories are also created if necessary. Although unusual, there is no reason why a directory should not have an extension, and any extension in name is significant. The function returns zero if successful or a negative error number if it fails. As well as the p_fparse error numbers returned if name fails to parse, p_mkdir can return the following negative error numbers: E_FILE_DEVICE the device does not exist E_FILE_NOTREADY the device does not contain a medium E_FILE_EXIST the directory already exists E_FILE_PROTECT the medium is write protected or is read-only (eg a ROM SSD) E_FILE_FULL there is no more room on the medium E_FILE_DIRFULL there is no more room in the root directory For example, if the current path is Loc: :a:\, then either: p_mkdir("\\dirl\\dir2\\"); or p_mkdir("\\dirl\\dir2") ; makes the directory Loc: :A:\DIR1\DIR2\ and also makes Loc: :A:\DIR1\ if it does not already exist. p_sfstat Set file attributes or label medium INT p_sfstat (TEXT *name, UINT status, UINT mask); INT p_sfstatasync(TEXT *name, UINT status, UINT mask, WORD *stat); Set or clear the attributes of the file specified by the zero terminated file name name (but see also Setting the volume name below). Both status and mask contain a bit field made up of the following bit masks: P_FAWRITE set if file may be written to P_FAMOD set if the file has been modified P_FAHIDDEN set if file is hidden P_FASYSTEM set if file is a system file The function only modifies those bits that are set in mask where attribute is set or cleared depending on the value of the corresponding bit in status. 11-22 11 FILES The function returns zero if successful or a negative error number if it fails. As well as a p_fparse error number if name fails to parse, p_sfstat can return the following negative error numbers: E_FILE_DEVICE the device does not exist E_FILE_NOTREADY the device does not contain a medium E_FILE_DIR the directory does not exist E_FILE_NXIST the file does not exist E_FILE_LOCKED a process has name open E_FILE_PROTECT the medium is write protected or is read-only (eg a ROM SSD) For example: p_sfstat ("joe.doc",0,P_FAWRITE) ; makes the file joe.doc read-only. Setting the volume label If mask has the p_ravouume bit set, status 1s ignored p_sfstat and p_sfstat sets or deletes the volume name. At the time of writing, only the toc: : file system supports setting the volume label (the function returns E_GEN_Nsup if it is not supported). The name parameter should be a zero terminated file specification with a root directory (name is parsed with a nuuu related name) of the form: LOC: :\ to label the medium in , giving it the volume name . If name does not contain a component, the volume label is deleted. The function returns zero if successful or a negative error number if it fails. As well as a p_fparse error number if name fails to parse, p_sfstat can return the following negative error numbers: E_FILE_DEVICE the device does not exist E_FILE_NOTREADY the device does not contain a medium E_FILE_PROTECT the medium is write protected or is read-only (eg a ROM SSD) For example, if the current path is toc: :a:\, then: p_sfstat ("d:\mydisk",0,P_FAVOLUME) ; gives the medium in device Loc: :p: the label myprskx, p_sfstat ("d:\diskname.dsk", 0,P_FAVOLUME) ; gives the medium in device toc: :p: the label prsKName.psx and: p_sfstat ("d:\",0,P_FAVOLUME) ; deletes any volume name from device Loc: :D:. p_fdate Set file creation date INT p_fdate(TEXT *name, ULONG date); INT p_fdateasync(TEXT *name, ULONG date, WORD *stat); Set the file creation date to date where date is in the form of the system time - that is, the number of seconds since January Ist 1970. The date may not be set earlier than January Ist 1980. If p_fdate is called with any earlier system time, the date is forced (without error) to January Ist 1980. The system time used for the creation date is always converted to an even number of seconds (i.e. the least significant bit of date is discarded). 11-23 PLIB REFERENCE The function returns zero if successful or a negative error number if it fails. As well as a p_fparse error number if name fails to parse, p_fdate can return the following negative error numbers: E_FILE_DEVICE the device does not exist E_FILE_NOTREADY the device does not contain a medium E_FILE_DIR the directory does not exist E_FILE_NXIST the file does not exist E_FILE_LOCKED a process has name open E_FILE_ACCESS name is a read-only file E_FILE_PROTECT the medium is write protected or is read-only (eg a ROM SSD) The following example copies the date of fred.doc to fred.txt. P_INFO info; p_finfo("fred.doc", &info) ; p_fdate("fred.txt",info.modst) ; Binary file access To open a file to be manipulated as a flat binary file, you call p_open with the 3rd parameter mode or'ed with P_FSTREAM. For example: p_open (&fcb, "fred.dat",P_FSTREAM|P_FSHARE) opens a file for reading only. p_open (&fcb, "fred.dat",P_FSTREAM|P_FUPDATE |P_FREPLACE|P_FRANDOM) creates a writable binary file. The mode flags (eg P_FREPLACE) are described under p_open, below. There is no practical limit to the number of channels that may be opened in the system or by a particular process. Once you have opened a binary file channel, the possible I/O operations are: p_read to read from the file channel p_ioc (P_FREAD) p_ioa(P_FREAD) p_write to write to the file channel p_ioc (P_FWRITE) p_ioa (P_FWRITE) p_close to close the file channel p_seek to set and sense the channel file position p_iow (P_FSETEOF) to set the logical end of file p_iow (P_FFLUSH) to flush the file buffers p_iow (P_FCANCEL) to cancel all pending asynchronous requests (not actively supported) Bytes can be read or written to the file in any length up to a maximum of P_rMaxss12ZE bytes per read or write. Shared access Any number of processes can open the same file for reading only (but only if all the readers specify P_FSHARE when they open the file). However, the file server does not allow multiple processes to open a channel to the same file for writing. Once a file has been opened for reading, it may not be opened again for writing (but it may be opened again for reading). Once a file has been opened for writing, it may not be opened again for reading or for writing. 11-24 11 FILES If you have an multi-process application design which needs to update shared data, consider one of the following: e put the shared data in a named segment (see the chapter Memory Allocation) e access the shared file data via a server process (see the chapter Processes and Inter-Process Messaging) Example of binary file access The following example, which compares the contents of two files, uses many of the functions described in this chapter. #include typedef struct INT ret; VOID *chan; LONG len; TEXT name [P_FNAMESIZE]; UBYTE buf [P_FBLKSIZE]; } FILE_DATA; LOCAL_D FILE_DATA f1={0,NULL}; LOCAL_D FILE_DATA f£2={0,NULL}; LOCAL_C VOID CleanUp(TEXT *msg, FILE_DATA *pf) { TEXT bb[E_MAX_ERROR_TEXT_SIZE]; p_close(pf->chan) ; pf->chan=NULL; if (pf->ret<0) { p_errs (&bb[0],pf->ret); p_printf("Failed to %s %s (%s)",msg, &pf->name[0],&bb[0]); pf->ret=0; } } LOCAL_C VOID Exit (TEXT *msg) { if (fl.ret>=0 && f£2.ret>=0) p_printf (msg); CleanUp (msg, &f1); CleanUp (msg, &f£2); p_leave (0); } LOCAL_C VOID OpenFile(FILE_DATA *pf, TEXT *name, TEXT *related) { LONG pos; if (pf->ret=p_fparse (name, related, &pf->name[0],NULL) ) Exit ("parse"); if (pf->ret=p_open (&pf->chan, &pf->name [0], P_FSTREAM|P_FSHARE|P_FRANDOM) ) Exit ("open") ; pf->len=0L; p_seek (pf->chan, P_FEND, &pf->len) ; pos=0L; p_seek (pf-—>chan, P_FABS, &pos) ; } LOCAL_C VOID ReadFile(FILE_DATA *pf) { pf->ret=p_read(pf->chan, &pf->buf[0],sizeof (pf->buf) ); if (pf->ret!=E_FILE_EOF && pf->ret<0) Exit ("read"); 11-25 PLIB REFERENCE LOCAL_C VOID CDECL CompareFiles (TEXT *filel, TEXT *file2) { OpenFile(&f1,filel,NULL) ; OpenFile(&f2,file2,&f1.name[0]); p_printf ("Compare %s (%1d)",&f1.name[0],f1.1len); p_printf(" with %s (%ld)",&f2.name[0],f£2.len); if (fl.len!=f2.1len) Exit ("Files are of different length"); FOREVER { ReadFile(&f1); ReadFile(&f2) ; if (fl.ret==E_FILE_EOF && f2.ret==E_FILE_EOF) { fl.ret=f2.ret=0; Exit ("Files are identical"); } if (p_bcmp(&f1l.buf[0],fl.ret,&f2.buf[0],f£2.ret) ) Exit ("Files are different"); } GLDEF_C INT main(VOID) { TEXT *p; TEXT bb[P_FNAMESIZE]; while (p_getl("Enter ? ",&bb[0],P_FNAMESIZE) ) { p=p_skipch(&bb[0]); if (*p) *p++=0; p_enter((VOID *)CompareFiles, &bb[0],p_skipwh (p)); } return (0); } The program solicits two file names to compare (where the second name is parsed with the first name as a related name in CompareFiles) and reports whether they are the same or different. As well as illustrating binary file access, the example gives a realistic illustration of the use of p_enter and p_leave. Note also that the function cleanup takes advantage of the fact that p_close (NULL) is harmless. p_open(P_FSTREAM) Open a binary file INT p_open(VOID **ppfcb, TEXT *name, UINT mode); Open a channel to the file specified by the zero terminated file specification name and, if successful, return zero and write the channel to *ppfcb (ppfcb is not written to if the open fails). To open a file to be manipulated as a flat binary file, mode should be or'ed with P_FsTREAM. The file specification name is parsed with a nutt related file name (see p_fparse). If this fails, the open fails and returns the return value from p_fparse. The mode in which the file is opened is selected by oring in one (and only one) of the following bit fields into mode: P_FOPEN Open an existing file. If the file does not exist, the error E_LFILE_NXIST is returned. This option would normally only be used to open a file for read access. P_FCREATE Create a file which must not already exist. If the file does exist, the error E_FILE_EXIST is returned. To enable write access to the file you must specify P_FUPDATE (described below). P_FREPLACE If the file exists, open it and truncate it to zero length. If the file does not exist, then create a file. To enable write access to the file you must specify P_FUPDATE (described below). P_FAPPEND This is the same as for P_FOPEN except that the initial current position is set to the end of file such that the next write will append to the file. It is not necessary to specify P_FRaNnpDom (described below) but to enable write access to the file you must specify P_FUPDATE (described below). 11-26 11 FILES P_FUNIQUE Create a unique file using the passed path as the related path name in which to create the file. The unique file name is written back to name (there should be room for p_FNAMESIzE bytes). It is not necessary to specify p_FUPDATE (described below). The access which is subsequently permitted is formed by oring together a combination of the following flags in mode: P_FUPDATE specifies that write access as well as read access is required for the file. If an attempt is made to write to a file when this flag has not been set, the error E_FILE_RDONLY is returned. P_FRANDOM specifies that random access (as opposed to sequential access) is required for the file. If a call is made to p_seek with a file that has not been opened with this option, the error E_FILE_INv is returned. You should not specify P_FRANDoM unless you do intend to use p_seek because the file system may be able to optimise the device access if it knows that only sequential access is required. P_FSHARE specifies that this open should not block the file from being opened again for read access. If this flag is not set, a subsequent request to open a file (by the same or another process) will fail with z_FILE_LOCKED. Note that shared write access is not supported and p_rsHare can not be combined with p_ruppaTE. Returns zero if successful or a negative error number if it fails. As well as the p_fparse error numbers which may be returned if name fails to parse, p_open (P_FSTREAM) can return the following negative error numbers: E_GEN_NOMEMORY failed to allocate memory for the control block E_GEN_ARG mode contains an illegal combination of flags E_FILE_DEVICE the device does not exist E_FILE_NOTREADY the device does not contain a medium E_FILE_EXIST attempt to create a file which already exists E_FILE_NXIST attempt to open a file which does not exist E_FILE_ACCESS access to the file in the requested mode is not available E_FILE_DIRFULL there is no more room in the root directory E_FILE_PROTECT attempted to create/replace when the medium is write protected or is read-only (eg a ROM SSD) E_FILE_FULL there is no room on the device to create/replace this file E_FILE_LOCKED the file is already open (possibly by another process) E_FILE_DEVICE the device in name does not exist E_FILE_DIR the directory in name does not exist p_close Close a binary file channel INT p_close(VOID *pfcb) ; Close the binary file channel pfcb and return zero if successful. If pcb is NULL, just return zero. Although p_close can return an error, it will always succeed in closing the channel (and pfcb should not be used subsequently). If the file system buffers written data, the close operation may need to perform one or more write operations on closing. Even if written data is not buffered, if data has been written to the file, the close will update the modification date on the file. Because of this, p_close can return the some of the errors numbers which can be returned from p_write and p_fdate. However, the failure to flush the data or to write the new date will not cause the close operation to be aborted although the failure will be reported by an error return. 11-27 PLIB REFERENCE Carefully written applications avoid this problem by using p_iow(P_FFLUSH) to flush the data and to apply the date (and taking appropriate action if this fails) before closing the channel without risk of failure. The toc: : file system on SIBO machines does not buffer written data. When EPOC is running on a PC, the Loc:: file system is built over the MSDOS filing system which does buffer written data. p_read Read from a binary file channel INT p_read(VOID *pfcb, VOID *buf, UINT len); Reads 1en bytes or the number of bytes remaining before the end of file, whichever is smaller, from the current position of binary file channel pfcb and writes the data to buf. The current position is incremented by the number of bytes read. If the current position is already at the end of file, zero bytes are read and the negative error number E_FILE_EOF is returned. The parameter len must not be greater than p_rmaxss1zE (16K bytes). The most efficient way of processing files is to read in multiples of P_rFBLKs1zE (512) while ensuring that the file position remains on P_FBLKS1zE boundaries. The function returns the number of bytes written to buf if successful or one of the following negative error numbers: E_FILE_EOF end of file encountered E_FILE_ABORT the SSD containing the file is or was previously not present and the channel is now in the abort state E_FILE_READ failed to read from the file (eg because of a CRC failure on a floppy disk) p_write Write to a binary file channel INT p_write(VOID *pfcb, VOID *buf, UINT len); Write len bytes from buf to file channel pfcb at the current file position. The current position is incremented by the number of bytes written. The parameter len must not be greater than p_rMaxss1zE (16K bytes). The most efficient way of processing files is to write in multiples of P_FBLKs1z=z (512) while ensuring that the file position remains on P_FBLKS1ZE boundaries. When writing to Flash SSDs, you can physically overwrite a single byte (where len is one) provided that the new byte can be written by just clearing bits in the old byte. In general, overwriting data on a Flash SSD-based file will consume space and reduce the remaining capacity of the SSD. The function returns zero if successful or one of the following negative error numbers: E_FILE_FULL not enough room on the device for the data E_FILE_RDONLY the file channel was opened without the Pp_rupDaTE access bit set E_FILE_ABORT the SSD containing the file is or was previously not present and the channel is now in the abort state E_FILE_WRITE failed to write to the file (eg because of a CRC failure on a floppy disk) E_GEN_BATFLASH failed to write to the file because the batteries are too low to write to a Flash SSD 11-28 11 FILES p_seek (or f_seek) Position a binary file channel INT p_seek (VOID *pfcb, INT sense, LONG *ppos) ; INT f_seek(VOID *pfcb, INT sense, LONG *pos); Set the current file position of file channel pfcb to a new file position which depends on sense and *ppos, where sense is one Of: P_FABS to set the current file position to *ppos P_FEND to set the current file position to *ppos relative to the current end-of-file position P_FCUR to set the current file position to *ppos relative to the current file position If successful, the new file position (which may be the same as the old) is written to *ppos and p_seek returns zero. Otherwise, it returns one of the following negative error numbers: E_FILE_INV the channel was not opened with p_rranpom E_FILE_ABORT the SSD containing the file is or was previously not present and the channel is now in the abort state If the channel was opened with p_rsTREAM_TEXT on a remote file system, the full p_rszEx functionality may not be supported. When this is the case, p_seek fails with r_F1LE_1nv. However, the following is always supported on Pp_rSTREAM_TEXxT channels: ¢ using p_rass to set the current position to zero ¢ using p_FEND with a zero relative position to set the current position to the end of the file (although with p_rsTREAM_TExT you should not use the value which is written to *ppos) If calling p_seek results in an absolute file position which is negative then the file is positioned at the beginning of the file. If the absolute file position is greater than the end of file then the new position is set to the end of file. See p_iow(P_FSETEOF) for setting a new end of file. The function £_seek is identical to p_seek except that, if there is an error, it calls p_leave (err) rather than return the negative error number err. For example, if the current file position is 0x2000 then LONG pos; pos=256L; p_seek (pfcb, P_FCUR, &pos) ; sets the current position to 0x2100 and pos=-256L; p_seek (pfcb, P_FCUR, &pos) ; sets the current position to 0x1f£00 and pos=0L; p_seek (pfcb, P_FCUR, &pos) ; does not change the current position but could be used to sense the current position (pos contains 0x2000 after p_seek has returned). If the current end of file is 0x4000 then pos=0L; p_seek (pfcb, P_FEND, &pos) ; sets the current position to the end of the file and senses the length of the file (pos contains 0x4000 after p_seek has returned). To set the current position to the beginning of the file, use: pos=0L; p_seek (pfcb, P_FABS, &pos) ; 11-29 PLIB REFERENCE p_iow(P_FFLUSH) Flush internal file buffers INT p_iow(VOID *pfcb, P_FFLUSH) ; Flush all buffered written data to binary file channel pfcb and write the file's modification date. Returns zero if successful (the negative error number returns are as for p_write). The amount of information which is flushed depends on the file system and on the medium in the drive. The RAM and Flash SSD PDDs do not buffer any file data (in this case, calling p_iow(P_FFLUSH) just writes the file date). On Flash SSDs the last record written is held open until the file is closed or a write occurs to another file on the same SSD (see the section Flash SSDs at the beginning of this chapter). When EPOC is running on a PC, written file data is buffered to improve performance. Calling p_iow(P_FFLUSH) will ensure that any buffered data is written to the file but repeated flushing is likely to degrade performance. Although harmless, there is absolutely no benefit in calling p_iow(P_FFrLusH) on file channels which were opened without the p_ruppateE flag. p_iow(P_FSETEOF) Set end of file INT p_iow(VOID *pfcb, P_FSETEOF, ULONG *peof); Set the logical end of file on channel pfcb to position *peof and return zero if successful (the negative error number returns are as for p_write). The file channel must have been opened with p_FuPDATE. If *peof is greater than the current end of file, the file size is extended to *peof. On block-structured devices (ie excluding Flash SSDs), this is equivalent to appending indeterminate data to the end of the file to pre-allocate storage. (It is not useful to extend a file on a Flash SSD.) The current position is not affected when the file is extended with p_iow(P_FSETEOF). If *peof is less than the current end of file, the file is truncated to *peof (the current position is also reduced if necessary to the new end of file). p_iow(P_FCANCEL) Cancel an asynchronous file channel request INT p_iow(VOID *pfcb, P_FCANCEL) ; Cancel all asynchronous I/O requests on channel pfcb and return zero. The operation is harmless if no request is pending. In fact, for reasons which were described at the beginning of this chapter (under the heading Asynchronous file operations), a call to p_iow (P_FCANCEL) has no effect even when a request is pending. However, if you are performing asynchronous file access (which is not at all common) you may (for the sake of consistency and provided the status word is currently E_LFILE_PENDING) Use p_iow(P_FCANCEL) - normally followed by a call to p_waitstat - to effect cancellation as you would do for any other asynchronous request. It is always possible that a future implementation of the file server may process P_FCANCEL operations. At the time of writing, a preferable alternative is to simulate a cancel service, as illustrated in the earlier example, under the heading Asynchronous file operations. Stream text file access Different file systems vary in their support of a text file type and (if such support is provided) implement text files in different ways. Some systems (for example, DEC's VMS operating system which runs on a Vax) support text file types that are not implemented as crLF terminated records. Other systems (such as MSDOS, UNIX and the EPOC toc: : file system on a SIBO machine or a PC) do not support a text file type, and on such systems the following convention predominates: e text records are terminated by a cRLF sequence - a carriage return (code 13) followed by a line feed (code 10) e a file may optionally be terminated by a sus character (code 26) The terminating sus is not necessary on systems that store a logical file length and it is falling out of use. 11-30 11 FILES Applications running under EPOC that read or write text files can choose either to process the records in a text file themselves, or to take advantage of the system's text file handling. The system support for text files, using p_open (P_FTEXT) , is described in the next section of this chapter. Applications that do their own text file processing, for performance purposes or otherwise, should use p_open (P_FSTREAM_TEXT) in preference to p_open (P_FSTREAM) . Using p_FsTREAM_TEXT when opening such a text file on the rem: : file system declares the intention that the file is to be considered as a text file. It causes the remote file server to present the data from the file as if it were implemented with criF terminated records. In more detail, this presentation layer does the following: when reading the text record content on the remote system is converted to that content followed by a cRLF when writing the data is parsed for crLF record terminators and converted into text records on the remote system (the parser will also recognise cr, LF Or LFCR as a record terminator) Although in many cases (and certainly when opening a local file) opening with p_rsTREAM_TEXT has an identical effect to opening with p_rsTReaw, there is no guarantee that the effect will be the same on a remote file. There is no penalty to using p_rsTREAM_TExT - only a potential gain when accessing a remote file. The upshot of all this is... All this sounds very complicated (and it is - especially for the implementor of the remote file server) but you should trust the system and just remember this: To open a text file to be manipulated as a flat binary file, you should call p_open with mode or'ed with p_rsTREAM_TEXT and not p_FSTREAM. With the exception of p_open, all functions are exactly as for flat binary files, described in the previous section. p_open(P_FSTREAM_TEXT) Open a stream text file INT p_open(VOID **ppfcb, TEXT *name, UINT mode); Open a channel to the file specified by the zero terminated file specification name and, if successful, return zero and write the channel to *ppfcb (ppfcb is not written to if the open fails). To open a stream text file (a text file to be manipulated as a flat binary file) mode should be or'ed with P_FSTREAM_TEXT. For all other details, see the description p_open (P_FSTREAM) in the previous section. Text file access To open a file to be manipulated as a record oriented text file, you call p_open with the 3rd parameter mode or'ed with p_rtext. For example: p_open (&fcb, "fred. lis",P_FTEXT|P_FSHARE) opens a text file for reading only. p_open (&fcb, "fred. lis", P_FTEXT|P_FUPDATE|P_FREPLACE) creates a text file which may be written to. Once you have opened a text file channel, the possible I/O operations are: p_read to read the next text record from the file channel p_ioc (P_FREAD) p_ioa(P_FREAD) p_write to append a text record to the file channel p_ioc (P_FWRITE) p_ioa (P_FWRITE) p_close to close the text file channel 11-31 PLIB REFERENCE p_seek to set and sense the channel file position p_iow (P_FSETEOF) to set the logical end of file p_iow (P_FFLUSH) to flush the file buffers p_iow (P_FCANCEL) to cancel all pending asynchronous requests (not actively supported) The text file handling is not part of the file server but is implemented as a layer of code over the P_FSTREAM_TEXT binary file access. The text file handling code runs in the caller's context ("on the client side") in the same way as regular function calls. This layer of code is inserted between the application and the file server by the F1L: device driver p_open code when it sees the P_FTEXT bit set in the mode parameter. The data is, however, buffered in this layer, so flushing is necessary to ensure that any buffered data is written to the file. Implementing the code as a layer over P_FSTREAM_TEXT binary file access makes it independent of the file system node which is providing the services. What text file handling does The text file handling assumes that text files obey the following conventions: e text records are terminated by a cRLF sequence - a carriage return (code 13) followed by a line feed (code 10) e a file may optionally be terminated by a sus character (code 26) The text file handling also assumes that the record content (excluding the record termination) does not exceed a length of P_rmMaxRs1zE (256) bytes. A text record may not contain cr, LF or suB characters, but there is no other restriction on the contents. When reading a file, the text file handling parses a stream of bytes for cRLF record terminators such that the application gets the data a record at a time (excluding the terminator). The parser will also recognise CR, LF, LFCR or a SUB as a record terminator. If it sees a sus (either as a record terminator or immediately after a CRLF, CR, LF Of LFCR record terminator), it will behave as if the end of file had been reached. When writing to a file, the text file handling simply adds the record terminator (which is always CRLF) to the end of the record data. A terminating svuB is not written. For enthusiasts ... This is for interest only and may certainly be skipped. In case you were wondering what the Txt: device is, text file handling is implemented as an I/O device driver (txT:) which layers over the FIL: P_FSTREAM_TEXT mode binary file access (which was described in the previous section). The Fru: device redirects the p_open to TxT: when P_FTEXT is set in mode. This means that the following calls to p_open: p_open(&fcb, "fred.dat", P_FTEXT|P_FUPDATE|P_FREPLACE) ; p_open (&fcb, "FIL: fred.dat", P_FTEXT|P_FUPDATE|P_FREPLACE) ; p_open (&fcb, "TXT: fred.dat", P_FUPDATE |P_FREPLACE) ; are all equivalent. (The above examples are provided to help de-mystify the I/O system and the FIL: device and it would be obscure to use TxT: without good cause.) p_open(P_FTEXT) Open a text file INT p_open(VOID **ppfcb, TEXT *name, UINT mode); Open a file to be manipulated as a record oriented text file where mode should be or'ed with P_FTExT. Except for the use of P_FTEXT in place of P_FSTREAM or P_FSTREAM_TEXT, the parameters and returns are as for opening a binary file, described in the previous section. p_close Close a text file channel INT p_close(VOID *pfcb); Close the text file channel pfcb and return zero if successful. Since text written to the file is buffered in the device driver, p_close may give rise to an E_FILE_WRITE error. It is therefore advisable to call p_iow(pP_FFLusH) before calling p_close. Otherwise, the behaviour and returns are as for closing a binary file, described in the previous section. 11-32 11 FILES p_read Read from a text file channel INT p_read(VOID *pfcb, VOID *buf, UINT len); Read the contents of the current record (ie excluding any record terminators) from text file channel pfcb, writing up to 1en bytes to buf and, if successful, return the length of the record read (which is also the number of bytes written to buf). The channel is positioned to the next record. If 1en is less than the length of the current record, the first 1en bytes from the record are read and p_read returns the negative E_FILE_RECoRD. The channel is still positioned to the next record. Note that p_read returns zero if it encounters a record of zero length. After the last record is read, the channel is positioned to the end of the file. When the channel is positioned at the end of the file, p_ read returns the negative =_F1ILE_koF (nothing is written to buf). Other negative error returns are: E_FILE_ABORT the SSD containing the file is or was previously not present and the channel is now in the abort state E_FILE_READ failed to read from the file (eg because of a CRC failure on a floppy disk) Example LOCAL_D VOID *fcb=NULL; LOCAL_C INT CDECL SearchFile(TEXT *file, TEXT *pattern) { INT ret; TEXT line [P_FMAXRSIZE+2]; if (ret=p_open(&fcb, file, P_FTEXT) ) panic("Failed to open file",ret); while ((ret=p_read(fcb, &line[0],P_FMAXRSIZE) ) >=0) { line [ret]=0; if (ret && p_smatchi(é&line[0],pattern) ) p_printf(&line[0]); } p_close(fcb); if (ret!=E_FILE_EOF) panic("Failed to read file",ret); return (0); } p_write Write to a text file channel INT p_write(VOID *pfcb, VOID *buf, UINT len); Write a record of length 1en bytes (where 1en is zero to p_FMAxRS1ZE inclusive) from buf to the text file channel pfcb. Returns zero if successful or one of the following negative error numbers: E_FILE_FULL not enough room on the medium for the data E_FILE_RECORD the record size exceeds P_FMAXRSIZE E_FILE_RDONLY the file channel is opened without p_ruppatE being set in mode E_FILE_ABORT the SSD containing the file is or was previously not present and the channel is now in the abort state E_FILE_WRITE failed to write to the file (eg because of a CRC failure on a floppy disk) Records are always written to the end of file. The data between buf and buf+1en should not include any record delimiters. 11-33 PLIB REFERENCE p_seek (or f_seek) Position a text file channel INT p_seek (VOID *pfcb, INT sense, LONG *ppos) ; INT f_seek(VOID *pfcb, INT sense, LONG *pos); Set the current record position of text file channel pfcb to a position which depends on sense and *ppos, where sense is one Of: P_FREWIND to position to the first record (the value of ppos is ignored) P_FRSENSE to get the position of the last record read or written P_FRSET to set the record position which was previously got with a call to p_seek (P_FRSENSE) Returns zero if successful or the negative E_FILE_1nv if the file was not opened with P_FRANDoM. Some remote file systems may not be capable of supporting p_seek (P_FRSET) and p_seek (P_FRSENSE) and in this case p_seek will return E_FILE_INV. The function f_seek is identical to p_seek except that, if there is an error, it calls p_leave (err) rather than return the negative error number err. It is important to note that this function sets the current record position for read operations only. Records are always written at the end of file. Example while ((len=p_read(fcb, &buf[0],P_FMAXRSIZE) )>0) { buf [len]=0; if (buf[0]==':') { p_seek (fcb, P_FRSENSE, &pos) ; StoreLabel (&buf[1],pos); } p_iow(P_FFLUSH) Flush internal file buffers INT p_iow(VOID *pfcb, P_FFLUSH) ; Flush all buffered written data to text file channel pfcb and write the file's modification date. The behaviour and returns are as for flushing a binary file, described in the previous section. Unlike the binary file, however, data is buffered for all file systems and media. p_iow(P_FSETEOF) Set end of text file INT p_iow(VOID *pfcb, P_FSETEOF, ULONG *peof); Set the logical end of file on channel pfcb to position *peof. Behaviour and returns are as for flushing a binary file, described in the previous section. p_iow(P_FCANCEL) Cancel an asynchronous file channel request INT p_iow(VOID *pfcb, P_FCANCEL) ; Cancel all asynchronous I/O requests on channel pfcb and return zero. The behaviour and return values are as for cancelling a binary file request, described in the previous section. At the time of writing, a preferable alternative is to simulate a cancel service, as illustrated in the earlier example, under the heading Asynchronous file operations. 11-34 CHAPTER 12 PROCESSES AND INTER-PROCESS MESSAGING Processes A process is a running program. It is normally created by loading an image file (also called an executable) using p_execc. After loading the program, the process creator normally calls p_presume to start the process running. EPOC is a single-user multi-tasking operating system that rapidly switches contexts between a number of independent processes - creating, at times, the illusion of multiple processes running in parallel. The maximum number of processes that may exist is E_MAX_PROCESSES (24). At any particular time, one process is actually running. On a reschedule, EPOC runs the process with the highest priority that is ready to run. If there is only one ready process at the highest priority it will continue to run indefinitely (and any lower priority ready processes will wait indefinitely). Preemptive multi-tasking means that the running process may be replaced at any time - it does not have to make a system call to yield the processor. A process which becomes ready will immediately run if it has the highest priority. If there is more than one ready process with the highest priority then each process is made current for a fixed time period (4 system ticks) after which the operating system makes the next process of that priority current in what is called a "round robin" fashion. On a SIBO machine, the system "ticks" 32 times a second. Because EPOC is a single-user system, the current process is comparatively rarely switched out by the system tick. It is more likely to stop running because an event occurred that made a higher priority process become ready or because the current process voluntarily gave up its ready status to wait for an event to occur. In EPOC, a process consists of at least the following: e aprocess control block (described below) e adata segment, containing the processor stack, static variables and the heap (as described in the Memory Allocation chapter) ¢ aprimary code segment (which is shared if there are one or more other processes of the same program) e an I/O semaphore (as described in the chapter Asynchronous Requests and Semaphores) On SIBO machines, a process that tries to write to a memory segment other than its own data segment (except via functions that explicitly allow such activity) is panicked with panic number 60. A code segment may exist in ROM or it may be loaded into a RAM memory segment from an executable. A process may acquire other resources during its lifetime - for example, I/O channels or additional code segments. The system automatically releases owned resources such as memory segments, I/O channels and semaphores when it terminates. Server processes are also designed to clean up client resources when a client process terminates. 12-1 PLIB REFERENCE System processes When the operating system initialises itself, it creates a number of system processes some of which are essential to the operation of EPOC. After a system reset on a typical machine running EPOC, the system processes (by process name) are: SYSSNULL.$01 the zero priority null process runs when no other process is ready to run and switches the machine off (to reduce power consumption) after a period of inactivity SYSSMANG.$02 the supervisor has a higher priority than any other process and performs many critical system functions including memory segment moving and resource clean-up when a process terminates SYSSFSRV.$03 the file server (described in the Files chapter) has the second highest priority and performs all file related operations, including loading an image to create a process SYSSWSRV.$04 the window server (described in the Window Server manual) provides shared access to the screen, keyboard and, if present, the digitiser (or mouse on a PC) SYSS$SHLL.$05 the shell process provides a user interface that allows other processes to be started (on a custom system it might provide a "turnkey" environment) The extension of the process name gives the process number (assigned by the operating system). The structure of process names is described later in this chapter. The functions (described in this chapter) which change the process name, change the process priority and suspend a process will fail when applied to syssnuLL, sySSMANG and SYSSFSRV. On systems with a digitiser (or mouse), the window server creates a subsidiary process, sharing its data segment, to draw the mouse icon. In EPOC, a subsidiary process that shares the data segment of its owner is called a task and the window server task has a name such as sys$wsRV.@05. The notifier service p_notify (described in the chapter Error Handling) may be provided by the window server itself or it may be provided by a client of the window server. If a separate notifier exists, it has a process name such as SYS$NTIFY.$07. Process ID and process control block Each process is identified by its process ID - a positive 16-bit number containing two bit fields: e = The least significant 12 bits is the offset of the process control block in the operating system data segment (also called the process slot). e The most significant 4 bits contains a value in the range 0-7 (note that a valid process ID is positive). This value is incremented modulo 8 each time a process slot is used so that a process ID may be rejected after a process has terminated. The function p_getpid returns the process ID of the caller. If a system function is passed a process ID that is zero or negative, or in which the least significant 12 bits is outside the range of the process table, the caller is panicked with panic number 7 (invalid process ID). 12-2 12 PROCESSES AND INTER-PROCESS MESSAGING The structure of the process control block is defined by the &_proc struct, defined in epoc.h as: typedef struct e_proc { struct e_proc *next; struct e_proc *prev; WORD queKey; WORD queData; deltaType; addressTrap; UBYT UBYT UBYT UBYT UBYT UBYT UBYT UBYT UBYT UBYT UWOR UBYT UBYT UWOR UBYT UWORD E E E E E E E E E E D E E D E status; sstatus; priority; priorityH; ramOrRom; isTask; name [E_MAX_NAME+1]; active; semaphore; *semHead; *memBasePtr; memGrowBy; *mCtrlPtr; minHeap; HANDLE fServer; HANDLE dataSeg; HANDLE codeSeg; UBYTE UBYTE UBYTE UBYTE UWORD UWORD UWORD *saveSP; *saveBP; notify; sndSem; magic; checkSum; terminate; } E_PROC;. A copy of a process control block may be obtained by calling p_getosd (where zE_p1pMaskx is used to mask out the address portion from the process ID) as follows: E_PROC pcb; p_getosd(&pcb, (VOID *) (pid&E_PIDMASK), sizeof (pcb) ); Many of the fields of z_pRoc are described in the course of this chapter. The remaining fields are reserved for system use. As an aside, and for the reader's interest, some miscellaneous fields (which would not otherwise be described) are described below. It must be emphasised that none of these fields should be modified directly by any application code. sstatus ramOrRom isTask active semaphore memBasePtr memGrowBy mCtrlPtr TRUE if the process is waiting to be suspended (for example, if suspended while on the time delta queue - on leaving the queue it will be suspended rather than entering the ready queue) TRUE if the process code segment is in RAM, ratss if the code segment is in ROM TRUE if the process is a task (a subsidiary process that shares the data segment of its creator) TRUE if the running of this process will stop the machine from switching off - as set by p_marka and p_unmarka the handle of the process I/O semaphore the address of the start of the heap (4 bytes before that obtained from p_allspace) the heap granularity in paragraphs as set by p_hgran contains the address of the message control block as set up by p_minit or zero if p_minit has not been called 12-3 PLIB REFERENCE minHeap the minimum heap size in paragraphs fServer the client ID as given by the file server or zero if the process has not connected to the file server dataSeg the handle of the process data segment codeSeg the handle of the process code segment if the code is in RAM or the paragraph address of the code segment if the code is in ROM notify the notifier state as set by p_sentnotify (and sensed by p_getnotify) sndSem TRUE if the process is waiting on the sound semaphore, to indicate the need to call p_signal on termination of the process magic the top 4 bits of the process ID, used to reject a process ID of a process that no longer exists - even when another process has been created and re-uses the same process slot terminate contains the termination message type as set by p_onterminate or zero if p_onterminate has not been called Process states Each process is in one of the following states (as stored in pcb. status): E_PROC_CURRENT the process that is currently running (at any time, one process is in this state) E_PROC_READY the process is waiting for a chance to run E_PROC_SEMAPHORE the process is waiting on a semaphore, most likely its I/O semaphore pcb. semaphore (described in the chapter Asynchronous Requests and Semaphores) E_PROC_DELTA the process is waiting in the timer delta queue (described in the chapter Time, Timers and Dates) E_PROC_SUSPENDED the process exists but will not run until it is resumed by calling p_presume Process queues Processes that are in the READY, SEMAPHORE Or DELTA State are in a doubly-linked queue (using pcb. next and pcb. prev - see queues in the chapter Characters, Strings, Buffers and Queues). The READY queue is ordered by the process priority as stored in pcb. priority. Each semaphore heads a SEMAPHORE queue. This queue may be empty, or may contain one or more processes in a first-in, first-out order. The DELTA queue is a special kind of doubly-linked queue (called a delta queue, as described in Characters, Strings, Buffers and Queues). It stores the time interval in system ticks between timer entries. These queues are described in more detail in the chapter Asynchronous Requests and Semaphores. Process priorities When there is more than one process that is ready to run, the operating system runs the process with the highest priority (an unsigned byte value in the range 1 to 255). Lower priority processes are blocked indefinitely. If there is more than one ready process with the same highest priority, they take it in turns to run every four system ticks. Applications should set their priority in the range E_MIN_PRIORITY (64) to E_MAX_PRIORITY (192) inclusive (the operating system reserves the values outside this range). The initial process priority is normally taken from a value stored in the program file or image (and is generated by the tool used to build the image) but it may subsequently be changed using p_setpri (p_getpri returns the priority of a process). 12-4 12 PROCESSES AND INTER-PROCESS MESSAGING Interactive application processes that are clients of the window server are normally created at the priority E_PRIORITY_FORE (128) and subsequently leave it to the window server to change their priority depending on whether the process is receiving user input or not. See the Window Server manual. The supervisor runs at priority 248 and the file server at priority 240. A hardware interrupt runs at the same priority as the process it interrupts. Critical sections of interrupt code are protected by switching off pre-emption. Preemptive scheduling It is possible for a process to run for as long as it wants to and, in practice, this often happens. In this case, the current process eventually gives up its cuRRENT state by changing its state to: SEMAPHORE by calling p_iowait to wait on the I/O semaphore (or p_wait to wait on any semaphore) DELTA by calling p_sleep, p_sleept OF p_sleepa SUSPENDED by calling p_psuspend on its own process ID However, many events can cause a reschedule in which the current process is preempted by a higher priority process without completing its course. Such events include: e the fourth consecutive system tick e asemaphore being signalled (eg as a result of user input) which releases a higher priority process e the expiry of a higher priority process in the timer DELTA queue The current process may, by its own action, cause itself to be preempted by: e signalling the I/O semaphore of a higher priority process (by calling p_iosignalbypia or, for example, by sending it an inter-process message) e calling p_presume to release a higher priority process from the susPENDED state (especially after loading a process from an image) e raising the priority of a another process by calling p_setpri e lowering its own priority by calling p_setpri Process names A process name takes the following form: where the component contains between one and eight characters and the component consists of a period, a s and a two digit number of the form 01, 02, 03 .... This number gives the index (starting from 1) of the process slot. If the process is a task (a subsidiary process that shares the same data segment), the s is replaced by a e@ where, except for the @, the process name is otherwise the same as the creator of the task. The maximum length of a process name is =_max_Name (12), excluding the zero terminator (buffers normally allow &_mMax_NamE+2 bytes to include the zero terminator and to keep following variables on an even address). When a process is created by loading an image using p_execc, the process is created with a taken from the file name of the image. For example, if a program called Loc: :\B:\UTILS\SoRT. IMG is loaded twice the process names might be: SORT .$07 SORT.$11 The component of a process name guarantees that the process name is unique. Note that the process data segments are given the same names (see the chapter Memory Allocation) but that these segment names are held independently of the process name. To convert a process name into a process ID, you use p_pidfind. Since a program cannot anticipate the component, p_pidfind allows wild card characters in a match string. For example, to get the process ID of a database server process that was loaded from db$serv.img, you would use p_pidfind("DBSSERV.*"). When there is the prospect of more than one process of the same (because a program was loaded more than once), p_pfind may be used to get the process IDs of all instances. 12-5 PLIB REFERENCE In the following example, the general purpose function ProcsOfThisProg returns the number of processes having the same process name (excluding the extension) as this process (that is, it returns the number of processes of the calling program). GLDEF_C INT ProcsOfThisProg (VOID) { TEXT *pp; HANDLE h; INT count; TEXT mm[E_MAX_NAME+2]; TEXT bb[E_MAX_NAME+2]; p_pname (p_getpid(), &mm[0] pp=(&mm[p_slocr(&mm[0],'.' *pptt='"*'; *pp=0; for (h=0,count=0; (h=p_pfind(h, &mm[0], &bb[0]))>=0; count++) ; return (count) ; } )+1)); The function gets the name of this process using p_pname and p_getpid and builds a match string in mm[] by replacing the extension with the '*' wildcard. It then uses this match string to count the number of matching processes. This function could be used to stop more than one process of a program (especially a server) from being executed where, for example, if ProcsoOfThisProg returns more than one, the server program could panic. Reserved statics (magic statics) The reserved statics (otherwise known as magic statics) are variables with fixed, known, addresses, existing in the process data space between addresses 0x00 and 0x40. They are 'magic' in the sense that they are accessible from all parts of the process code, even from dynamic library code (which does not have any data space and therefore may not, normally, access statics). Some of these variables are used by the operating system as part of the process context. Others are used by system code such as the window server, and the graphics user interface libraries. Such usage differs from machine to machine in the EPOC range; you will find a description of any such usage in the Programming Guide for the appropriate machine or in user interface library documentation. The remaining reserved statics are freely available for use by the process code. A common use is to provide access from dynamic library code to application-specific data, without the need for it to be passed in function parameters. 0x00 DatWordDead The word at this address contains the value 0xDEapD. This value must not be changed by application code. Many operating system calls check for this value and will panic the process with panic reason code PanicDead0 if it has changed. A change in this value is symptomatic of a common software bug; the unintentional use of a NULL pointer. 0x02 DatHandNext The data at these two addresses are used as pointers to a queue of wait 0x04 DatHandPrev handler function descriptors. This data should not be modified by application code. 0x06 DatCountrySeg This holds the segment handle of the data segment containing the country and language specific fold tables for the process. This data should not be modified by application code. 0x08 DatClassHandle In applications which use any of the object oriented programming calls 0x0a DatClassPtr (this includes use of Hwif programs) these locations hold data required by the operating system to work out how to send a message to an object's superclass. It is recommended that application code does not modify the contents of these locations. 0x0c DatEClassHandle In applications which use any of the object oriented programming calls 0x0e DatEClassPtr (this includes use of Hwif) these locations hold data required by the operating system to work out how to perform a p_exactsend. It is recommended that application code does not modify the contents of these locations. 12-6 0x10 0x12 0x14 0x16 0x18 Oxla Oxlc Oxle 0x20 0x21 Ox22 0x24 0x26 0x28 Ox2a Ox2c Ox2e 0x30 0x32 0x34 DatEnterFramePtr wClientData wserv_channel DatOsFramePtr DatATFlag DatHeapLocked DatProcessNamePtr DatCommandPtr DatTest DatApp1 DatApp2 DatApp3 DatApp4 DatApp5 DatApp6 DatApp7 12 PROCESSES AND INTER-PROCESS MESSAGING The enter and leave mechanism provided by the operating system uses this address to store the pointer to the last enter frame generated on the stack. When a leave occurs this pointer is used to unwind the stack and restore the register set. This location should not be modified by application code. In applications that use system user interface libraries this location is assumed to hold the object handle of an instance of the 'wserv' object. The application is responsible for ensuring that this location is set up correctly, normally by calling system code on application start-up. In applications that use the application manager object (all object oriented programs and Hwif programs) this location is assumed to hold the object handle of an instance of the application manager object. The application is responsible for ensuring that this location is set up correctly, normally by calling system code on application start-up. These locations are used by the window server process to store information about your application. If you use any graphics functions you should not modify the contents of these locations. This location is used by the OPL language translator. If your application does not use OPL then this location is free for use by application code. This location is used by the OPL language runtime code. This location should not be modified by application code. This location contains a pointer to the last member of a linked list of operating system calling frames. Following this linked list will show which functions called which operating system services. This location should not be modified by application code. This byte location contains the current address trap status for the process. Certain operating system calls cause address trapping to be turned on or off and the operating system sets or clears this flag to record the current address trap hardware status. Just setting the flag to zero will not disable address trapping. This location should not be modified by application code. This byte location contains the current state of the heap locked flag. While the heap is being modified (for example, being resized, due to a memory allocation request) the heap is locked. This prevents the operating system from compressing the segment while the data structures used by the operating system to run the heap are in an inconsistent state. This location should not be modified by application code. System user interface library code assumes that this location contains either NULL or a pointer to a zero terminated string that the application wishes to be displayed as its name. Otherwise it is free for use by application code. This location contains a pointer, set up by the operating system. It points to an alloc cell that contains the full path name, as a zero terminated string, of the .img or .app file from which the process was loaded. Immediately following the zero terminator, the alloc cell contains leading byte counted initial command line data. Psion's test system library code assumes that this location contains the object handle of the test code. Otherwise it is free for use by application code. These locations are free for application code to use. One exception is DatApp7, which is used within the ISAM library. Code that accesses the ISAM library, either directly or indirectly, may not use DatApp7, but otherwise it is free for use. 12-7 PLIB REFERENCE 0x36 DatDialogPtr System user interface library code may assume that this location contains a pointer to the current dialog structure. Otherwise it is free for use by application code. 0x38 DatGate System user interface library code may assume that this location contains an object handle. Otherwise it is free for use by application code. 0x3a DatLocked System user interface library code may assume that this location contains a flag indicating whether the application is capable of receiving termination or switch files messages. Otherwise it is free for use by application code. 0x3c DatStatusNamePtr System user interface library code may assume that this location contains a pointer to a zero terminated string that will appear in a status window. Otherwise it is free for use by application code. 0x3e DatUsedPathNamePtr | System user interface library code may assume that this location contains a pointer to a fully parsed file name. Otherwise it is free for use by application code. Shared code segments When a second or subsequent process of the same program is loaded, the existing code segment (which has segment name .$sc) is shared and not loaded from the image file (however, the initialized static data values are still loaded into the created process data segment). The system checks that the contents of the existing code segment matches the code in the image file by comparing pcb. checkSum with the checksum stored in the image header (the checksum in the image header is also used to check the integrity of the code when it is loaded). If the checksums do not match, the load fails. It follows that you cannot run different programs of the same name (or different versions of the same program) at the same time. Image files An image file is a form of executable (program) file that is run by calling p_execc. It normally has a . mc file name extension. Application files (with a .ApP extension) are a particular type of image file. An image file is created by applying the emake.exe tool to a DOS executable. This is normally done automatically as part of the build process that generates an EPOC application program (for an example of the use of emake.exe, see \ts\sys\tsprj.txt). The creation process adds an ImgHeader struct (defined in epoc.h) to the front of the file and may optionally concatenate a number of other files into the image file. The ImgHeader struct is effectively defined as: #define SignatureSize 16 #define MaxAddFiles 4 typedef struct { UINT offset; UINT length; } ADDFILE; typedef struct { Gq 1 tO K HZQZGZZZZ2ZRZ ZZ ZZae TE Signature [SignatureSize]; [ ImageVersion; [ HeaderSizeBytes; [ CodeParas; [ InitialIP; [ StackParas; [ DataParas; [ HeapParas; [ InitializedData; [ CodeCheckSum; [ DataCheckSum; [ CodeVersion; r Priority; ILE Add[MaxAddFiles]; DylCount; NG DylTableOffset; [ Spare; mgHeader;. iw) Jy E i i RB mm i 1 a en et 12-8 12 PROCESSES AND INTER-PROCESS MESSAGING The meanings of the elements of this struct are as follows: Signature ImageVersion HeaderSizeBytes CodeParas InitialIP StackParas DataParas HeapParas InitializedData CodeCheckSum DataCheckSum CodeVersion Priority Add Dy1lCount DylTableOffset Spare contains the string "ImageFileType**". the version number of the software tools used to create the image file. At the time of writing the version is 2.00F (0x200£) the offset of the start of the executable code within the image file the required size, in (16 byte) paragraphs, of the memory to be reserved for the code segment the initial instruction pointer offset in the executable code - determined by the linker, (and normally 0) the size, in (16 byte) paragraphs of the stack - determined by which PLIB C startup module is linked into the program (see the discussion of startup modules in the Introduction chapter) the total size, in (16 byte) paragraphs, of the declared static data (including the initialised data - see below) the required size, in (16 byte) paragraphs, of the initial - and minimum - alloc heap, user definable via a parameter to emake.exe (see below) the size, in bytes, of the initialised static data internally generated and verified checksum on the code internally generated and verified checksum on the data the version number of the executable code, user definable via a parameter to emake.exe (see Dbf£Version in the chapter Database Files for the form of a version number) the start-up priority of the process that will be created from this image file, user definable via a parameter to emake.exe (default value is 0x80) an array of four ADDFILE structs, describing up to four additional files included within the image file (common included files, in .APP files, are an icon graphic and a resource file) the number of object dynamic libraries (DYLs) concatenated into the image file. See also the Object Oriented Programming chapter of this manual. the offset within the file of the start of an array of pyLENTRy structs (defined in epoc.h) with pyicount entries, giving the names and file offsets of the included DYLs reserved The contents of this header may be displayed by applying the edump.exe tool to an image file. For efficiency, the value of Heapparas should be adjusted to be equal to, or slightly larger than, the heap space used by the running process immediately after it has started. Setting a smaller value means that the heap will have to be grown one or more times during the initialisation of the process. Growing the heap may involve moving large amounts of in-memory data and may, therefore, significantly increase the process start-up time (see also The heap allocator in the chapter Memory Allocation). 12-9 PLIB REFERENCE IO Ka ns Tn st Process termination The subject of process termination (with associated functions) is described in detail in the chapter Error Handling. The functions described include those that terminate a process: p_pterminate or to terminate another process (typically in response to a user request) p_pkill p_ppanic to panic another process (normally following unreasonable behaviour from the process being terminated) and those functions that request notification of the termination of other processes: p_logona to be signalled when the specified process terminates p_logon to receive an inter-process message when the specified process terminates (convenient for server processes to keep track of their clients) p_watchall to receive an inter-process message when any process terminates (only one process can call p_watchal1 - normally the Shell to monitor the termination of all processes) When a process terminates, pcb. status in the process control block (which normally holds the process state) is set to E_LPROC_FREE to mark it as available for use by a new process. When a newly created process re-uses the slot, pcb.magic is incremented modulo 8, as described above. SS a ae a a re eT) Creating a process p_execc Load an image HANDLE p_execc(TEXT *pName, VOID *pCommand, INT length); Create a suspended process from the image file described by the zero terminated file specification pNname and, if successful, return the positive process ID of the created process. The created process is suspended so that the creator can perform any initialisation (eg to set the process priority) before allowing the process to run by calling p_presume. If the created process has a higher priority than the creator, the call to p_presume may not return for some time. The file specification pName is parsed with a related name of ". mc". The function allocates a command cell from the heap of the created process. This cell is loaded with the following sequence: ¢ acopy of the zero terminated full file specification resulting from the parse of pName e a byte containing length e acopy of the length bytes from pcommand where length must be between zero and E_MAX_COMMAND_BUFFER (127) inclusive. If pcommand is NULL, length is taken to be zero, whatever is passed. The address of this command cell is written to the reserved static Dat CommandPtr in the created process data segment. This variable is accessed from C by declaring: GLREF_D UBYTE *DatCommandPtr; Note that there is no restriction on the data in pcommang; it may include binary data structures as well as text. For example, it is often useful to pass the process ID of the caller in the command line. The name of the created process is taken from the file name component (excluding any extension) of pName. If a process, loaded from pName, already exists (as determined by a name and checksum match), the created process shares the loaded code segment. In this case, the code is not reloaded from pName (however, the initialized static data values are still loaded into the created process data segment). 12-10 12 PROCESSES AND INTER-PROCESS MESSAGING The function returns a positive process ID if successful or a negative error number if it failed. As well as the p_fparse error numbers that may be returned if the parse of pName fails, p_execc can return the following error numbers: E_GEN_NOMEMORY insufficient free system memory E_FILE_DEVICE the device in pName does not exist E_FILE_NOTREADY the device in pName does not contain a medium E_FILE_DIR the directory in pName does not exist E_FILE_NXIST the file in pName does not exist E_FILE_EXIST a process of a different program (ie with a different checksum) with the same name already exists E_GEN_IMAGE the file is not a valid image or if the image is corrupt E_GEN_NOPROC there are no more free process slots (the maximum process limit has been reached) E_GEN_ARG the total size of the data, stack and heap exceeds oxrrero bytes or length exceeds E_MAX_COMMAND_BUFFER The loading of an image is performed by the file server (see the Files chapter) and the caller must have connected to the file server before calling p_execc (otherwise the caller is panicked with panic number 41). The standard PLIB library connects to the file server before calling main. Example #include LOCAL_C VOID Exec(TEXT *name, TEXT *cmd, INT len) { INT ret; TEXT bb[E_MAX_ERROR_TEXT_SIZE]; ret=p_execc (name, cmd, len); if (ret<0) { p_errs (&bb[0],ret); p_printf ("Failed to start %s (%s)",name, &bb[0]); } else { p_printf("Process ID is %x",ret); p_presume (ret) ; } } GLDEF_C INT main(VOID) { TEXT *pl,*p2; TEXT bb[64]; while (p_getl("Enter ? ", &bb[0],64)) { pl=p_skipwh (&bb[0]); if (!*pl) continue; p2=p_skipch (pl); if (*p2) { *p2tt+="\0'; p2=p_skipwh (p2) ; } Exec (pl,p2,p_slen(p2) +1); } return (0); } 12-11 PLIB REFERENCE The program solicits a line of input and parses out the name of the image to run and a text string to pass to the created process (which is passed with the zero terminator). If this program is used to run dummy.img in the default path (which happens to be Loc: :D: \) by entering: dummy fred and the code for dummy.img is: #include GLREF_D UBYTE *DatCommandPtr; LOCAL_C VOID PrintCommandPtr (VOID) { UBYTE *p, *pe; UINT lcell; if (!DatCommandPtr) p_panic (0); lcell=p_alen(DatCommandPtr) ; p_print ("sd [",lcell); for (p=DatCommandPtr, pe=p+lcell;p", *p) ; p_printf£("]"); } GLDEF_C INT main(VOID) { PrintCommandPtr(); p_getch(); return (0); } then dummy.img prints: 24 [LOC::D:\DUMMY.IMG<00><05>fred<00>] p_execcasync Load an image asynchronously INT p_execcasync(TEXT *pName, VOID *pCommand, INT length, WORD *pStatus, HANDLE *pPid)j; This is the asynchronous version of p_execc, which may return before the operation is complete. See the chapter Asynchronous Requests and Semaphores for an explanation of asynchronous requests. See p_execc above for the meaning of the parameters pName, pCommand and length, the behaviour of the operation and its possible error returns. The function p_execcasync returns zero if the asynchronous request was successful or a negative error number if the operation failed to start (eg E_FILE_NAME if pName failed to parse). When the load completes, the completion status is written to *pStatus and the process I/O semaphore is signalled. If the load completes successfully, *pstatus contains zero and *pPid contains the process ID of the created process (this corresponds to the value returned by p_execc). If the load completes unsuccessfully, *pstatus contains a negative error number (eg E_GEN_NOMEMORY). If pName refers to a file on the Loc: : file system, the load will actually have completed by the time p_execcasync has returned because the file server runs at a higher priority than any of its clients. However, if the image is on a REm: : file system where the connection is via an RS232 cable, p_execcasync will return well before the load is complete. p_pcreate Create a process HANDLE p_pcreate(E_CPB *pBlock) ; Create a process from the information in the E_cpB struct pointed to by pBlock and, if successful, return the positive process ID of the created process. This is the primitive process creation service that does not involve the file server. In the vast majority of cases, it is more convenient to use p_execc, which will eventually call this service. 12-12 12 PROCESSES AND INTER-PROCESS MESSAGING The &_pcs struct is defined in epoc.h as: typedef struct { UWOR: UWOR: D codeParagraphs; D UWORD stackParagraphs; D D initiallp; UWOR UWORD heapParagraphs; UBYTE *commandLine; UWORD checkSum; UWORD minHeap; UBYTE priority; UBYTE ramOrRom; UBYTE name [E_MAX_NAME]; } E_CPB;. dataParagraphs; The function creates a process of initial priority pBlock->priority where the process name is taken from the zero terminated (up to 8 characters) in pBlock->name. If pBlock->ramOrRom is TRUE and a process of the same name already exists, its checksum is compared with pBlock->checkSum and, if they match, the existing code segment is shared (in which case pBlock->codeParagraphs is ignored). If pBlock->ramOrRom IS TRUE and a process of the same name does not already exist, p_pcreate creates a code segment of length pBlock->codeParagraphs paragraphs (and pBlock->checkSum is stored as the code segment checksum). In this case, the caller must deposit the code into the created code segment before resuming the process. If pBlock->ramOrRom IS FALSE, pBlock->codeParagraphs is taken to be the paragraph address of the code segment in the ROM. When the process is resumed, the initial instruction pointer is set to pBlock->initiallIp. A data segment is created, of sufficient size to include the stack (of length pplock->stackParagraphs) a static data space (of length pplock->dataParagraphs) and a heap (of length pplock->heapParagraphs). The total size of the data, stack and heap must not exceed oxrre paragraphs. The pBlock->dataParagraphs area in the data segment is zero filled. The minimum heap size subsequently allowed for the created process is pBlock->minHeap paragraphs. If pBlock->commandLine 1S not nuuL it should point to a zero terminated string, immediately followed by a leading byte count buffer where the leading byte is less than or equal to E_Max_COMMAND_BUFFER (as described in p_execc, above). The whole pBlock->commandLine data structure is copied into a heap cell that is allocated in the created process. The function returns a positive process ID if successful or on of the following negative error numbers: E_GEN_NOMEMORY insufficient free system memory E_FILE_EXIST a process of a different program (ie with a different checksum) with the same name already exists E_GEN_NOPROC there are no more free process slots (the maximum process limit has been reached) E_GEN_ARG the total size of the data, stack and heap exceeds oxrreo bytes or the leading byte count length in the pBlock->commandLine data structure exceeds E_MAX_COMMAND_BUFFER E_FILE_NAME pBlock->name is invalid 12-13 PLIB REFERENCE Operations on the current process See the chapter Error Handling for the functions (p_exit and p_panic) that terminate the current process. p_getpid Get this process ID HANDLE p_getpid(VOID) ; Return the process ID of the caller. For example: TEXT ProcessName [E_MAX_NAME+2]; p_pname (p_getpid(), &ProcessName[0]); writes the name of this process as a zero terminated string to ProcessName. p_unmarka Mark this process as non-active VOID p_unmarka (VOID) ; Mark the calling process as non-active such that the running of the process will not keep the machine switched on. The zero priority null process switches the machine off (to reduce power consumption) after a period of inactivity. A context switch to a process marked as non-active does not count as activity. When a process is created it is initially marked as active. General purpose server processes should mark themselves as non-active since not to do so would disable their clients from effectively calling p_unmarka. For example, if the file server was marked as active, a file request by a non-active client would cause the file server to run and reset the inactivity timer. As it is, the file server is marked as non-active and it is left to the clients of the file server to reset the inactivity timer or otherwise. Applications that respond to a continuously restarted short interval relative timer (as in, for example, a clock program) should also call p_unmarka. Otherwise, the machine would never switch off while the program is running. Interactive programs that do call p_unmarka do not need to take any measure to reset the inactivity timer when responding to user input (a key press or the use of a pointing device if there is one) since the system (by one means or another) guarantees to reset the inactivity timer on user input. To reset the inactivity timer in response to an event other than user input, call p_tickle, described next. Note that a compute-bound process (such as a game program that is computing its best next move) will not allow the machine to switch off, simply because the null process never gets an opportunity to run. Such a process should assume responsibility for allowing the machine to switch off by calling p_allowoff from time to time. p_tickle Register activity VOID p_tickle (VOID) ; Reset the auto-switch-off inactivity timer. It is used by a process that is marked as inactive, but wishes to stop the system switching off. An example would be to keep the machine going if serial data is received. You don't need to call p_tickle in response to user input since the system automatically registers activity in this case. 12-14 12 PROCESSES AND INTER-PROCESS MESSAGING p_marka Mark this process as active VOID p_marka (VOID) ; Mark the calling process as active such that the running of the process will stop the machine from switching off. The zero priority null process switches the machine off (to reduce power consumption) after a period of inactivity. A context switch to a process marked as active resets the inactivity timer. When a process is created it is initially marked as active and p_marka does not need to be called unless p_unmarka has previously been called. —EeEEeE—E—————— ey Operations on any process See the chapter Error Handling for functions (p_pterminate, p_pkill and p_ppanic) that terminate a process. p_getpri Get a process priority INT p_getpri(HANDLE pid); Return the positive priority of process pia, or the negative k_FILE_Nxtst if the process does not exist. p_setpri Set a process priority INT p_setpri(HANDLE pid, INT nPriority); Set the priority of process pid to nPriority (between E_MIN_PRIORITy and E_MAX_PRIORITY inclusive) and return zero if successful or one of the following negative error numbers: E_GEN_RANGE nPriority 1s outside the range E_MIN_PRIORITY tO E_MAX_PRIORITY E_FILE_NXIST process pid does not exist E_GEN_FAIL attempted to change the priority of the null process, the supervisor, or the file server Calling p_setpri causes a reschedule. p_presume Resume a process INT p_presume (HANDLE pid); Resume process pia and return zero if successful or one of the following negative error numbers: E_GEN_ARG pid 1s not suspended E_FILE_NXIST process pid does not exist If process pid has a higher priority than the caller, the call to p_presume may not return for some time. If you actually want to wait for the resumed process to terminate before continuing, you can use p_logona (described in the chapter Error Handling) as follows: p_logona (pid, &stat) ; p_presume (pid) ; p_waitstat (&stat) ; 12-15 PLIB REFERENCE p_psuspend Suspend a process INT p_psuspend (HANDLE pid); Suspend process pid and return zero if successful or one of the following negative error numbers: E_FILE_NXIST process pid does not exist E_GEN_FAIL attempted to suspend the null process, the supervisor, or the file server A process that is in the READY queue or is currently running (ie the calling process) is suspended immediately. A process that is waiting on the SEMAPHORE queue or the DELTA queue is marked (in pcb. sstatus) as requiring suspension and is subsequently placed in the susPENDED state when it would otherwise have been transferred to the READY queue (unless the process is resumed before this happens). p_pname Get a process name by ID INT p_pname (HANDLE pid, TEXT *pName) ; Write the name of process pid as a zero terminated string to pName (which should be big enough to receive E_MAX_NAME+2 bytes) and return zero if successful or E_FILE_Nx1sT if the process does not exist. The process name written to pName includes the process slot extension as in, for example, syS$NULL.$01. p_prename Rename a process INT p_prename (HANDLE pid, TEXT *pNewName) ; Rename process pid to the zero terminated pNewName and return zero if successful. The process name pNewName should not include a process slot extension (this is supplied by the system) and should be between | and 8 characters long. No check is made that the new name is unique, but an invalid name will return the error E_FILE_NamE. The possible error returns are: E_GEN_ARG process pid is not suspended E_FILE_NXIST process pid does not exist E_FILE_NAME the new name is invalid p_pidfind Get a process ID by name HANDLE p_pidfind(TEXT *pName) ; Return the positive process ID of the first process having a name matching the zero terminated pName (which may contain wild card characters) or return E_FILE_Nxt1sT if there is no matching process. For example, to find the process ID of a process created by loading db$serv.img, use: pid=p_pidfind("DBSSERV.*") ; p_pfind Find all processes HANDLE p_pfind(HANDLE pid, TEXT *pMatch, TEXT *pName) ; Called repeatedly to find all the processes that match the wild card string pointed to by pMatch, writing the process name as a zero terminated string into pName (which should be big enough to receive E_MAX_NAME+2 bytes). The first call should pass a zero pid. Returns the positive process ID of the process found (which is also passed to the next find) or the negative E_FILE_NXIST if no more matching processes can be found. The wild card string pMatch should remain the same between successive calls. No memory is used by this service and it can be abandoned at any time without taking any further action. 12-16 12 PROCESSES AND INTER-PROCESS MESSAGING For example, to print the names of all processes: GLDEF_C INT main (VOID) { HANDLE h; TEXT b[E_MAX_NAME+2]; for (h=0; (h=p_pfind(h,"*",&b[0]))>=0;p_printf(&b[0])); p_getch(); return (0); } p_getowner Determine the owner of a process HANDLE p_getowner (HANDLE pid); This function is only available in EPOC version 2.17 or later. Returns the process ID of the process which last resumed process pia, that is, the last process to call p_presume (pid). This process is defined to be the owner of process pid. This mechanism will fail in the case where the process being resumed has not attempted to run between a call to p_psuspend and a subsequent call to p_presume. This is because a process is only truly suspended at the time that it first attempts to run following a call to p_psuspend. Note that there is no guarantee that the process whose ID is returned by p_getowner still exists. Accessing a process data segment The functions p_pcpyfr, p_piscpyfr and p_pcpyto copy data between a (normally different) process data segment and the caller's data segment (cf p_sgcopyfr and p_sgcopyto which copy data between any segment and the caller's data segment). p_pcpyfr Copy data from a process INT p_pcpyfr(HANDLE pid, VOID *pSource, VOID *pTarget, UINT nBytes); Copy nBytes bytes at offset psource from the data segment of process pid to address ptarget (in the current process) and return zero if successful. If psourcet+nBytes exceeds the size of the processes data segment, the data up to the end of the segment is copied and zero is returned. The system ensures that the copy is not interrupted by another process. The function returns &_GEN_arc if the process pid does not exist. p_piscpyfr Indirected string copy from a process INT p_piscpyfr(HANDLE pid, VOID *pSourceAddr, VOID *pTarget, UINT nBytes); This function is only available in EPOC version 2.14 or later. Read the pointer at offset psourceAddr in the data segment of process pid. Then copy the zero terminated string indicated by this pointer to address ptarget (in the current process) and return zero if successful. wie If the pointer is nuuu, a null string (""") is copied to ptarget. If the string is longer than nBytes, only nBytes of data (plus a terminating zero) are copied and zero is returned. If the implied length of the source string extends beyond the end of the processes data segment, the data up to the end of the segment (plus a terminating zero) is copied and zero is returned. The system ensures that the copy is not interrupted by another process. The function returns z_cEn_arc if the process pid does not exist. 12-17 PLIB REFERENCE For example: GLREF_D TEXT *DatProcessNamePtr; LOCAL_C INT GetCalcName (VOID) { HANDLE h; TEXT buf [0x40]; h=p_pidfind("calc.*"); if (h<0) return (h); p_piscpyfr(h, &DatProcessNamePtr, &buf[0],0x40); return (0); } fetches the process name of the calculator. p_pcpyto Copy data to a process INT p_pcpyto(HANDLE pid, VOID *pTarget, VOID *pSource, UINT nBytes); Copy nBytes bytes from pSource in the current process data segment to pTarget in the data segment of process pid and return zero if successful. The system ensures that the copy is not interrupted by another process. Address trapping is automatically switched off for the duration of the copy. Returns the negative E_GEN_arc if the process does not exist or if pTarget+nBytes exceeds the size of the target process data segment (in which case the data is not copied). 5 ——————_________________s_;;; Inter-process messaging Inter-process messaging in EPOC is designed for the efficient implementation of client-server relationships where a particular server may have multiple clients. A server process is a commonly provided to share a resource amongst multiple client processes. The EPOC operating system starts up with three multi-client servers: e the supervisor, which performs critical system functions and provides shared access to memory via the memory segment allocator e the file server, which provides shared access to file storage devices e the window server, which provides shared access to the screen, keyboard and, if present, a pointing device A server may also be a client of another server. For example, the window server is a client of the file server (since, for example, it loads bitmap files) and all processes are implicitly clients of the supervisor. In order that the relative priorities of client processes have their intended effect, a multi-client server process should run at a higher priority than any of its clients. A client's priority is effectively lowered to that of the server while waiting for completion of a service provided by a low priority server. Message slots A process that wishes to receive messages must first call p_minit to initialise the message system. Calling p_minit (nMess, 1Mess) allocates nMess message slots (from the heap) where each message slot contains an E_MESSAGE struct header followed by a buffer of length 1Mess. The E_MESsAGE struct is defined in epoc.h as: typedef struct message { struct message *next; UBYTE *status; UINT type; HANDLE pid; } E_MESSAGE;. 12-18 12 PROCESSES AND INTER-PROCESS MESSAGING The structure of the 1mess bytes of data following the z_messacz header is defined by the receiver of the message and is normally limited to a few words. For example, the file server and the supervisor both use an iMess of 8. Note that the sender has no control over the length of the message sent (a later version of a server could increase the message length to provide additional services while maintaining upward compatibility). When the sender calls p_msend (or a variant) the arriving message is copied into a previously free message slot, which is also placed in the receiver's message queue. From this time the message slot is allocated, in the sense that it cannot be overwritten by another incoming message. It contains the message type (as specified by the sender) in type and the process ID of the sender in pia, followed by 1mess bytes of data from the sender. (The fields next and status in the E_MESSAGE header are used internally by the message system.) The message slot is removed from the queue when the receiver calls, for example, p_mreceivew (which returns the address of the message slot). The content remains safe from being overwritten until the receiver calls p_mfree to indicate that it has completed processing the message. Calling p_mfree returns the message slot to its free state, available to receive another incoming message. What the server does A server is always a passive process that waits for messages to arrive from a client process. It is not expected that a server will ever initiate a transaction by sending a message to a client. As described earlier, the server first calls p_minit to initialise the message system. The server then waits for a message to arrive by calling p_mreceivew (an asynchronous version, p_mreceive, also exists). When the message arrives, the function returns the address of the message slot. The server always removes messages from the front of the queue. Arriving messages are normally inserted at the end of the queue but if the client's priority is 0x80 or more, the arriving message is inserted at the front, overtaking any existing messages, regardless of their sender's priority. When it is necessary to send more than the amount of information allowed for by the (typically short) message length, the message contains the data by reference (ie by address and length). The server then uses p_pcpyfr to copy the data from the client's data segment. The message may also contain the address or addresses of buffers to receive data from the server when the service has been completed. Here, the server uses p_pcpyto to copy the data to the client's data segment. When the message has been processed, the server frees the message slot using p_mfree. If the message was sent in such a way that the sender is expecting an acknowledgment of some kind (ie the message was sent using p_msendreceivew Of p_msendreceivea rather than p_msena), the call to p_mfree also writes back a completion status value and signals the sender's I/O semaphore. Servers request to be informed of the termination of a client by calling p_logon (pid, nType) to receive a message of type nType when client pid terminates. Process termination and p_logon are described in the chapter Error Handling. What the client does The client process requests a service of the server by sending it typed fixed length messages (where the message length is determined by the server) using: p_msend to send the message "blind", returning when the message has been deposited (but not necessarily processed) p_msendreceivew to send the message and wait for the server to reply by calling p_mfree p_msendreceivea to send the message, returning when the message has been deposited (as for p_msenda) having set up an asynchronous request for a reply If the server does not have any empty message slots, the message sending function waits on a mutual exclusion semaphore until a message slot becomes free. It can therefore be blocked indefinitely, even if using the asynchronous p_msendreceivea. Multi-client servers avoid this prospect by allocating a slot for each potential client (by allocating say E_max_PROCESSES minus the number of known processes). 12-19 PLIB REFERENCE The only preparation required by the client is to obtain the process ID of the server. Examples of ways in which this is done are: the client gets the ID via the process name using p_pidfind (this is normally what is done for multi-client servers) the client knows the ID because it created the server using p_execc the server created the client and passed its process ID as a command parameter to p_execc (as in the above example) For multi-client servers, the client typically sends an opening message to connect to the server. An example of a server typedef struct { E_MESSAGE mess; TEXT *bofs; UWORD len; } MESS; LOCAL_C VOID RunServer (VOID) { MESS *pmsg; TEXT buf[256]; FOREVER { p_mreceivew(&pmsg) ; if (pmsg->mess.type) { p_mfree(pmsg, 0); break; } p_pcpyfr(pmsg->mess.pid, pmsg->bofs, &buf[0],pmsg—>len) ; p_printf("%*s",pmsg-—>len, &buf[0]); p_mfree(pmsg, 0); } } LOCAL_C VOID Exec(TEXT *name) { HANDLE pid; TEXT bb[E_MAX _ERROR_TEXT_SIZE]; pid=p_getpid(); pid=p_execc (name, (UBYTE *) &pid, sizeof (pid) ); if (pid<0) { p_errs (&bb[0],pid); p_printf ("Failed to start %s (%s)",name, &bb[0]); } else { p_printf("Process ID is %xd",pid); p_logon (pid, TRUE) ; p_presume (pid) ; RunServer (); } } GLDEF_C INT main(VOID) 12-20 { TEXT bb[P_FNAMESIZE]; p_minit (1, sizeof (MESS) -sizeof (E_MESSAGE) ) ; while (p_getl("Enter ? ", &bb[0],P_FNAMESIZE) ) Exec (p_skipwh(&bb[0])); return (0); } 12 PROCESSES AND INTER-PROCESS MESSAGING In the above example, main solicits a file specification of an image to run. This image is then (in Exec) loaded using p_execc (passing the created process the process ID of its creator) and then resumed using p_presume. While the image is running, the program acts as a server to it where messages of type FALSE are taken to contain a buffer by reference. The data from this buffer is copied from the process using p_pcpyfr and printed using p_printé. Before resuming the process in Exec, the server logs on to the process by calling p_1ogon. When the process terminates, the server receives a type TRUE message. This causes the program to return from RunServer and solicit another image file specification. Corresponding client code example The following example shows how to write that part of the client side code that corresponds to the above example of server code. GLREF_D UBYTE *DatCommandPtr; LOCAL_D HANDLE pid=0; LOCAL_D TEXT bb[256]; GLDEF_C VOID printf(TEXT *pfmt,...) { struct { TEXT *pbuf; UINT len; } msg; msg.len=p_atob(msg.pbuf=&bb[0],pfmt, &pfmt+1); if (!pid) pid=* (HANDLE *) (DatCommandPtr+p_slen(DatCommandPtr) +2) ; p_msendreceivew (pid, FALSE, &msg) ; } The printf function behaves in the same way as p_printf except that the printing is performed by the creator of the process in its console window. Note that the call to p_msendreceivew does not return until the server calls p_mfree. This example does not show the sending of the message with a type TRUE on termination of the client process. Asynchronous messaging The server-client example described above uses synchronous messaging in both the server and the client. While this may be sufficient in simple cases, there are situations where such an implementation will prove inadequate. A client that sends messages synchronously is effectively suspended from the moment it calls p_msendreceivew until the server completes processing the message and calls p_mfree. If completion is dependent on an external event, such as the expiry of a timer or the arrival of serial data, the client may be suspended indefinitely. This will, in general, be unacceptable behaviour (particularly if the client must remain responsive to other events, such as user input) and in such a case the client should use asynchronous messaging. A server does not, in general, have a user interface. If its only task is to receive and process messages from its clients, then synchronous server code may be perfectly acceptable. As in the example server code given earlier, the server is effectively suspended until a message is received and returns to the suspended state as soon as it has finished processing the message. If the server also has to respond to other events, such as the expiry of a timer, then the receipt of messages must be handled asynchronously. Suppose a client needs to use asynchronous messaging because completion of the processing of the message may be delayed indefinitely by an external event. This implies that the server itself must respond to at least two events (the receipt of a message and the external event) and so must also handle events asynchronously. 12-21 PLIB REFERENCE The sequence of events that occur during messaging between asynchronous client and server is as follows: e the server makes a call to p_minit e the server then calls p_mreceive, passing the address of its messaging status word, and later makes a call to p_iowait (usually in its main event-handling loop) e the client calls p_msendreceiva, passing the address of a status word, and later calls p_iowait (usually in its main event-handling loop) e the server is signalled that a message has been received, by noting that its messaging status word is no longer E_FILE_PENDING on areturn from p_iowait, and commences processing the message ¢ on completion of the processing the server calls p_mfree which notifies the client of the completion e the client is signalled that processing is complete, by noting that the relevant status word is no longer E_FILE_PENDING on areturn from p_iowait If the processing of the message itself involves an asynchronous request, the server may process any number of synchronous messages from any of its clients (including the one sending the asynchronous message) while the request is pending. It is therefore quite possible for a synchronous message to complete before an earlier asynchronous message from the same client. On the assumption that clients may send messages asynchronously because they do not want to wait, the server will generally need more than one message slot to avoid the client having to wait for a message slot to become free. How many slots to provide depends on many factors, such as: e the maximum expected number of clients e the maximum number of outstanding messages allowed per client (rarely more than two) e whether it is acceptable for any client ever to be suspended while waiting for a free slot Message processing order A message sent by a client to a server is placed in a message queue, normally at the end of the queue. The server receives a message by removing the one at the front of the queue (into one of its message slots). Thus, in normal circumstances, messages are processed in the order in which they are sent. As mentioned in the previous section, it is possible for synchronous messages to 'overtake' asynchronous messages from the same client. The window server has a requirement, particularly in an overlapping window environment, to give priority to messages from the foreground task. For example, when an area of the screen covering all or part of a number of task windows needs redrawing (after, say, the disappearance of a dialog) the foreground task should be redrawn first. The following scheme has been adopted to satisfy the window server's requirement without causing an unacceptable performance penalty. In all cases, messages from a client process with a priority of 0x80 or above (the foreground task normally has a priority of 0x80, which is higher than the priority of background tasks) are placed at the front of the queue. They therefore overtake all other messages in the queue (including any earlier messages from the same client) irrespective of the priorities of their sending processes. This has the added advantage of reducing the risk that switching to the shell task (which always runs at a priority higher than the foreground task) can be blocked by a 'rogue' task. There is, however, one problem, which can be illustrated as follows. Suppose a client with a priority of 0x80 sends an asynchronous message to a server. This message is inserted at the front of the queue. Before the server removes any messages from the queue, the same client sends a cancel message, to cancel its previous request. This second message is again inserted at the front of the queue, overtaking the message it is supposed to be cancelling. For this situation to arise, all the following conditions must be satisfied: e the client must have a priority of 0x80 or above e the client must use asynchronous messaging e the client must be sensitive to the order in which the server processes its messages e the server must not have removed the first message from the queue by the time the second message is queued 12-22 12 PROCESSES AND INTER-PROCESS MESSAGING The last of these conditions is rarely satisfied, since a server normally runs at a higher priority than its clients. As soon as the server is signalled that a message has been queued the server pre-emptively suspends the client. In most cases the client will not resume until the server has completed processing the message and is waiting for another message. It is rare that a server has more than one message in the message queue. The main exception is when the processing of the message requires the server to call (directly or indirectly) p_iowait, for example, to perform file I/O. In such a case you should consider whether the client really needs to use asynchronous messaging. Alternatively you can set the client's priority to be less than oxso. In this case the mechanism by which the window server adjusts the priority of background and foreground tasks should be disabled (see the description of wconnect in the Window Server manual). Pa a a Ng eee Server functions All the server functions except p_minit call p_panic if messages have not been initialised and p_minit itself calls p_panic if it is called a second time. p_minit Initialise for message reception INT p_minit (INT nMess, INT 1Mess) ; Initialise a queue of nMess message reception slots of length 1Mess and return zero if successful or one of the following negative error numbers: E_GEN_NOMEMORY there is insufficient free memory to allocate the message queue E_GEN_NOSEM there are no more free semaphores (p_minit creates a mutual exclusion semaphore, initialised with nmess) The message length imess excludes the &_messacg structure that is at the front of all messages. The message queue is allocated as a single cell from the heap. Since this cell remains allocated for the lifetime of the process, programs calling this service should do so early in their initialisation (before any cell has been freed) to avoid heap fragmentation. All message slots are of the same size and the total size of the message queue is nMess* (1Mess+sizeof (E_MESSAGE) ) bytes. To avoid senders being blocked by message sending (quite different from waiting for a reply that can be handled asynchronously), multi-client servers should allocate a message slot for each potential client. The constant &_MAX_PROCESSES contains the total number of processes that can be supported by the system. In practice this can be reduced by at least four, for the null process, supervisor, file server and window server. Only the least significant byte of nmess is significant, limiting the number of message slots to 255. Calls p_panic if p_minit has already been called or if the least significant byte of nmess is zero. p_mreceivew Wait for message reception VOID p_mreceivew(VOID *pMess) ; Return when a message has been received, where the address of the message slot containing the message is written to *pMess. The data in the message slot is protected from being overwritten by the receipt of another message until the message slot is freed from the queue using p_mfree. Calls p_panic if messages have not been initialised or if an asynchronous message receive request is pending. Example typedef struct { E_MESSAGE mess; TEXT *bofs; UWORD len; } MESS; MESS *pmsg; p_mreceivew (&pmsg) ; 12-23 PLIB REFERENCE p_mreceive Asynchronous message reception VOID p_mreceive (WORD *pStatus, VOID *pMess) ; Make an asynchronous request to receive a message and return immediately without waiting for a message to be received. While the request is pending (and the message queue is empty), *pStatus contains E_FILE_PENDING. When a message is received (or if there is already a received message in the queue) *pStatus is set to zero, the address of the message slot containing the message is written to *pMess and the process I/O semaphore is signalled. The data in the message slot is protected from being overwritten by the receipt of another message until the message slot is freed from the queue using p_mfree. Calls p_panic if messages have not been initialised or if an asynchronous message receive request is already pending. Example typedef struct { E_MESSAGE mess; TEXT *bofs; UWORD len; } MESS; MESS *pmsg; WORD status; p_mreceive (&status, &pmsg) ; p_mcancel Cancel a message receive request VOID p_mcancel (VOID) ; Cancel any pending asynchronous request to receive a message (which was previously requested using the p_mreceive service). It is not considered an error to call this service if no request is pending (since the request may complete at any time). If the cancel is processed before a message is received, the associated status word *pStatus is set to E_LFILE_CANCEL. Note that this service does not cancel a pending p_msendreceivea (which is a client function). A server may support a cancel service but this is invoked, like any other service, by sending the server a message. p_mfree Free a message VOID p_mfree(VOID *pMess, INT nReply); .Free the message slot with address pMess (as returned from p_mreceivew or p_mreceive) and, if the message was sent using p_msendreceivew Of p_msendreceivea, complete the request by signalling the sending client's I/O semaphore and copying nRep1y to the client's status word (which is returned by p_msendreceivew). A negative value of nReply normally indicates an error. If the message was sent with p_msend, the message slot is just freed and nRep1ly is ignored. The server should not read the contents of pMess after calling p_mfree. If messages are not freed from the message queue then in due course sending processes will be blocked waiting on the message queue mutual exclusion semaphore. 12-24 12 PROCESSES AND INTER-PROCESS MESSAGING Se ooo | Ht“fo#ooavouwHurNvwaavaH07U7TT84A«I0 °I™ 7v“T“>Yn=Nazsw—" Client functions p_msend Send a message INT p_msend(HANDLE pid, UINT mType, VOID *pMessage) ; Send message pMessage Of type mType to process pid and return zero when the message has been deposited or the negative E_GEN_RECEIVER if pid does not exist or if process pid has not called p_minit. The message is deposited into a free message slot in the server where the member type in the E_MESSAGE header is set to mtype and the message body is copied from pMessage (where the length of the message was specified by the server as a parameter to p_minit). If pia does not have a free message slot, p_msend waits (on a mutual exclusion semaphore) until it does. This function is only really suitable for sending messages where the whole information fits in the message. That is, the data at pMessage should not contain the address of further data to be copied (using p_pcpyfr) because there is no way of knowing when the server will copy the data so that pMessage may be re-used. Where the message does reference further data, p_msendreceivew Of p_msendreceivea should be used. A client should certainly use p_msendreceivew Of p_msendreceivea if the service is to return information (eg success or failure). p_msendreceivew Send a message and wait for a reply INT p_msendreceivew(HANDLE pid, UINT mType, VOID *pMessage) ; Send message pMessage Of type mType to process pid and wait for the server to reply (which it does by calling p_mfree (pid, nReply) ) and return the reply nreply. Return immediately with the negative &_GEN_RECEIVER if pid does not exist or if process pia has not called p_minit. The message is deposited into a free message slot in the server where the member type in the E_MESSAGE header is set to mtype and the message body is copied from pMessage (where the length of the message was specified by the server as a parameter to p_minit). If pia does not have a free message slot, p_msendreceivew waits (on a mutual exclusion semaphore) until it does. p_msendreceivea Asynchronous send message and get reply INT p_msendreceivea (HANDLE pid, UINT mType, VOID *pMessage, WORD *pStatus) ; Send message pMessage of type mType to process pia and asynchronously request a reply from the server. Return zero if the message was successfully sent or the negative E_GEN_RECEIVER if pid does not exist or if process pid has not called p_minit. When the server completes the request and calls p_mfree (pid, nReply), *pStatus 1S Set to nReply and the client's process I/O semaphore is signalled. While the reply is pending, *pstatus contains E_FILE_PENDING. The message is deposited into a free message slot in the server where the member type in the E_MESSAGE header is set to mtype and the message body is copied from pMessage (where the length of the message was specified by the server as a parameter to p_minit). If pia does not have a free message slot, p_msendreceivea Waits (on a mutual exclusion semaphore) until it does. 12-25 CHAPTER 13 GENERAL SYSTEM SERVICES System information p_version Get the operating system version UINT p_version(VOID) ; Return the operating system version number. The returned version number should be interpreted as a 4-digit hexadecimal number of the form: X.YYZ where x is the major release number, yy is the minor release number and z is normally the hexadecimal digit F (internal releases use a and B to designate alpha and beta releases, respectively). For example, a return of 0x123F is interpreted as 1.23F. p_romversion Get the ROM version UINT p_romversion (VOID) ; Return the ROM version number. The ROM contains the operating system and other system components such as, for example, the window server. The tool used to build the ROM requires a version number to be specified and this is the version number that is retrieved by this function. See p_version above for the interpretation of the version number. p_getres Get the cause of the last system shut-down INT p_getres (VOID); Return a number indicating the cause of the last system shut-down, as follows: E_IS_A_COLD_START The system started up for the first time (or after a period during which all power had been removed, including the Lithium back-up). E_IS_A_POWERFAIL_START The hardware forced a shut-down because the voltage got too low. This should not happen because the system software gets a non-maskable interrupt if the voltage drops below a certain threshold (but not low enough for the hardware to force a shut-down) and this interrupt code automatically switches the hardware off. However, if the clean-up takes too long because of a poorly designed device driver, the hardware will force the machine off before the interrupt completes - in which case p_getres returns E_IS_A_POWERFAIL_START. The environment variables and the contents of m: are preserved. PLIB REFERENCE E_IS_A_RESET_START The machine has been reset by pressing the recessed reset button. The environment variables and the contents of m: will have been preserved (a soft reset). If the Esc key is held down while pressing the reset button, this causes a hard reset. In this case both the environment variables and the contents of m: will have been cleared. Note that this value will not occur on a Workabout, since this machine does not have a reset button. E_IS_A_KERNEL_FAULT The system was reset because a serious fault occurred while executing in the operating system kernel. This could result from: (1) a bug in the operating system or a system process; (2) a program bug that managed to overcome the operating system's defences or (3) a hardware problem such as a RAM fault. The environment variables and the contents of mM: will be preserved (unless the system detects a memory corruption). On the Workabout, this value is also returned if the system is reset by pressing Psion-Ctrl-Del. This is the equivalent of a soft reset on other machine types and preserves both the environment variables and the contents of M:. E_IS_A_NEW_OS_START The system was reset after programming a new operating system into the Flash ROM (normally after running the repro program). The environment variables and the contents of m: have been cleared. On the Workabout, this value is also returned if the system is reset by pressing Shift-Psion-Ctrl-Del. This is the equivalent of a hard reset on other machine types and clears both the environment variables and the contents of M:. p_getosd Get operating system data VOID p_getosd(VOID *pTarget, VOID *pSource, UINT length); Copy length bytes from offset psource in the operating system data space to pTarget. Operating system handles (such as memory segment handles and semaphore handles) are actually addresses in the operating system data space of the appropriate control entry. If you AND a process ID with E_PIDMASK you get the address in operating system space of the corresponding process control entry - as described in the chapter Processes and Inter-Process Messaging. p_getpsu Get power supply type INT p_getpsu (VOID); -Return one of the following values, defined in epoc.h, to indicate the power supply type on a SIBO machine: E_PSU_OLD E_PSU_MAXIM E_PSU_S3 E_PSU_S3_A9 Apart from this service EPOC hides the differences between the various power supplies. Machines in the MC GI range of SIBO computers, for example, may use either the E_Psu_oLD or the E_PSU_MAXIM power supply variants, each of which requires a slightly different variant of the EPOC operating system. The REPRO program that blows a new operating system into the Flash ROM of the MC GI range uses this service to blow the appropriate variant of EPOC. 13-2 13 GENERAL SYSTEM SERVICES Language and country SIBO machines are produced in a number of language variants, differing in the following respects: e the language code e the language of the text used by the ROM-based software (for example, the error messages returned by p_errs and the month names returned by p_nmmon) e character type and conversion tables as described in the chapter Characters, Strings, Buffers and Queues e = the keyboard layout e = the default country and country-dependent data A particular language variant will always have a different language code and text but may not differ in all the above. The system was designed to be produced in a variety of languages and, as far as the system services are concerned, all the above variations are encapsulated in a single configuration file in the ROM called ROM: :SYS$CTRY.CFO. Depending on the machine, there may be further files (for example, "resource files") containing language-dependent data for higher level system components. Unlike the language-dependent data, the country-dependent data may be altered from the language- dependent defaults. p_getlanguage Get the language code INT p_getlanguage (VOID) ; -Return the language code from the ROM configuration file. The language code can be used by applications that contain the text for more than one language to determine which language to present. The language codes, defined in p_config.h, are as follows: English French German Spanish Italian Swedish Danish Norwegian OMANI HDOBWNHE hobo’ to bt tb obo tot Finnish USA Swiss french Swiss German Portuguese Turkish Icelandic Russian Hungarian Dutch Belgian Flemish Australian New Zealand Austrian NNN N WNHrRFROWOOAANAT AO PWN EF OO Belgian french p_gettext Get operating system text INT p_gettext (INT n, TEXT *pBuffer); Get the nth string from the ROM configuration file and write it to pBuffer. Returns zero if successful or the negative z_cEN_arc if n 1s outside the range of the text strings in the configuration file. PLIB REFERENCE This function is called by specific text retrieval functions such as p_errs and p_nmmon. Applications only need to use p_gettext when retrieving text associated with a higher level of system software (in which case the documentation of the higher level software will list appropriate values of n). p_getctd Get country-dependent data VOID p_getctd(E_CONFIG *pcfg); Write a copy of the system E_conFiIc struct to pcfg where the E_conFIc struct is defined in p_config.h as: typedef struct { UWORD countryCode; WORD gmtOffset; UBYTE dateType; UBYTE timeType; UBYTE currencySymbolPosition; UBYTE currencySpaceRequired; UBYTE currencyDecimalPlaces; UBYTE currencyNegativelInBrackets; UBYTE currencyTriadsAllowed; UBYTE thousandsSeparator; UBYTE decimalSeparator; UBYTE dateSeparator; UBYTE timeSeparator; UBYTE currencySymbol [9]; UBYTE startOfWeek; UBYTE summerTime; UBYTE clockType; UBYTE dayAbbreviation; UBYTE monthAbbreviation; UBYTE workDays; UBYTE units; UBYTE spare[9]; } E_CONFIG; . The count ryCode specifies a country by its international dialling code, and units is O for imperial units or 1 for metric units See the description of p_getctd in the chapter Time, Timers and Dates for a description of the time- related fields: gmtOffset, dateType, timeType, dateSeparator, timeSeparator, startOfWeek, summerTime, clockType, dayAbbreviation, monthAbbreviation and workDays See the description of p_getctd in the chapter Floating Point for a description of the fields that are related to the display of floating point numbers and currency: currencySymbol, currencySymbolPosition, currencySpaceRequired, currencyDecimalPlaces, currencyNegativelInBrackets, currencyTriadsAllowed, thousandsSeparator and decimalSeparator. p_setctd Set country-dependent data VOID p_setctd(E_CONFIG *pcfg) ; Sets the country-dependent data from the E_conrté structure pointed to by pcfg. When changing a particular field or fields you would normally: @ use p_getctd to get a copy of the E_conrie struct e modify the field or fields, as required @ use p_setctd to write back the modified E_conFIe struct 13-4 13 GENERAL SYSTEM SERVICES Switching on and off By default, the auto-switch-off period is set to 300 seconds. The auto-switch-off period may be sensed and set by calling p_getauto and p_setauto respectively. Auto-switch-off when a mains adaptor is connected may be disabled by calling p_setautomains, and the corresponding state sensed by p_getautomains. The zero priority null process automatically switches the machine off to reduce power consumption after the system has been inactive for the auto-switch-off period. In situations where the null process does not get an opportunity to run, a process can assume responsibility for allowing the machine to switch off by calling p_allowoff. Some processes are marked as not being significant when it comes to determining what constitutes activity. For example, a continuously running clock program should not stop the system from automatically switching off in the absence of any significant activity. See p_unmarka and p_marka in the chapter Processes and Inter-Process Messaging for additional details on how to avoid keeping the machine switched on by continuously running applications. p_off Switch off VOID p_off(UINT uTime) ; Switch off the machine indefinitely or for any time up to approximately 4.5 hours and return when the machine switches on. If uTime is oxf££f the machine switches off indefinitely and will stay off until an outstanding absolute timer expires or the user switches on the machine. Equivalent to the machine automatically switching off or being switched off by the user. If uTime is greater than 8, the machine switches off for up to uTime 1/4ths of a second (although it will still switch on if an outstanding absolute timer expires or the user switches on the machine). (If uTime is less than or equal to 8, calling p_ofr has no effect.) On the IBM PC version of EPOC, calling p_or¢ has no effect. p_getauto Get the auto-switch-off period INT p_getauto(VOID); Return the current auto-switch-off period in seconds. If the auto-switch-off period is oxf££4, the system does not automatically switch off. p_setauto Set the auto-switch-off period VOID p_setauto(INT n); Set the auto-switch-off period to n seconds. Passing an n of -1 (oxf££4£) stops the system from automatically switching off. Calling p_setauto with n less than 15 is equivalent to calling p_setauto(15). p_getautomains Get switch-off state when mains is present INT p_getautomains (VOID) ; This function is only available in EPOC version 3.18 or later. Return true if auto-switch-off is disabled when mains is present, otherwise return FALSE. PLIB REFERENCE p_setautomains Disable/enable switch-off if mains is present VOID p_setautomains (INT flag) This function is only available in EPOC version 3.18 or later. Disable or enable auto-switch-off if mains is present. If flag is TRUE, auto-switch-off is disabled while mains is present and if flag is FALSE, auto-switch-off is enabled. Even if enabled, the machine will not switch off when mains is absent if switch-off has been stopped by use of p_setauto. p_allowoff Allow auto switch off VOID p_allowoff (VOID) ; Allow the machine to switch off if the auto-switch-off period has expired. A compute-bound process which has marked itself as non-active (by calling p_unmarka) does not allow the machine to switch off since the null process will never get an opportunity to run. Such a process should call p_allowoff from time to time. There is no particular advantage in calling p_allowoff more frequently than at intervals of 15 seconds - the shortest auto-switch-off period. On the IBM PC version of EPOC, calling p_allowoff has no effect. p_setonevent Enable/disable the ON key event VOID p_setonevent (INT state) This function is only available in EPOC version 2.28 or later. Enables or disables the event that is sent to the window server when the ON key is pressed. If state is FALSE, the event is disabled, any other value enables the event. Pe i a __________________________iy Power supply This section describes functions to: e determine the presence or absence of the main battery, the Lithium backup battery or the mains adaptor (p_supplyinfo) e get the voltage level of the main battery (or mains adaptor, if present) and the Lithium backup battery (p_supply) e determine whether the mains adaptor is connected (p_supp1ly) e get the nominal maximum voltages of the main battery and the Lithium backup battery (p_wsupply) e¢ get the recommended low voltage warning levels for the main battery and the Lithium backup battery (p_wsupply) e get the time and date of insertion of the main battery, and information about main battery usage (p_supplyinfo) e sense and set the main battery type (p_getbat and p_setbat respectively) The main battery type is only significant on machines that can take more than one battery type (such as the MC GI range) and affects the voltages returned by p_wsupply. 13-6 13 GENERAL SYSTEM SERVICES The main battery types are as follows: E_BATTERY_UNKNOWN the battery type is initially set to this value when EPOC starts up (equivalent in its effect to E_BATTERY_ALKALINE) E_BATTERY_ALKALINE the battery is an Alkaline E_BATTERY_NICAD_600 __ the battery is a 600 mA hour NiCd rechargeable E_BATTERY_NICAD_1000 _ the battery is a 1000 mA hour NiCd rechargeable If the hardware is unable to identify automatically the battery type, it is the responsibility of the user to set the battery type to match that actually fitted. The r_BaTTERY_UNKNowN value is intended to trigger the system into prompting the user to identify the battery type. p_supply Get power supply status VOID p_supply(E_SUPPLY *pValue) ; Write the status of the various supplies to the z_supp.y struct at pvalue where E_supp.y is defined as: typedef struct { UWORD mainBatteryReading; UWORD lithiumBatteryReading; WORD mainsPresent; } E_SUPPLY;. where: mainBatteryReading is the main battery voltage in millivolts (or the mains adaptor voltage if the mains adaptor is present) lithiumBatteryReading _ is the Lithium backup battery voltage in millivolts mainsPresent is negative if the mains adaptor status cannot be determined (because the SSD pack doors are open); zero if the mains adaptor is not present; one if the mains adaptor is present p_supplyinfo Get additional power supply data VOID p_supplyinfo(E_SUPPLY_INFO *pValue) ; This function is only available in EPOC version 3.18 or later. Write information concerning the various power supplies to the E_suPPLY_INFo Struct at pvalue. The &_suppLy_1nFo is defined in epoc.h as: typedef struct { UBYTE mainBatteryLevel; UBYTE mainBatteryStatus; UBYTE backupBatteryLevel; UBYTE dcLevel; UWORD warningFlags; ULONG insertionDate; ULONG ticksInUseBattery; ULONG ticksInUseDc; ULONG maTicks; } E_SUPPLY_INFO;. 13-7 PLIB REFERENCE where: mainBatteryLevel mainBatteryStatus backupBatteryLevel dcLevel warningFlags insertionDate ticksInUseBattery ticksInUseDc maTicks is the present status of the main battery. It describes the voltage level as being in one of four discrete states: a) E_MBAT_GOOD battery voltage is good b) E_MBAT_LOW battery voltage is low C) E_MBAT_VERY_LOW battery voltage is very low d) E_MBAT_ZERO there is no battery present! No precise figures for the actual voltage levels corresponding to these states are given. is the same as mainBatteryLevel described above except that it records the lowest level that the battery has reached. It is reset when the main batteries are removed from their housing. is TRUE if the Lithium backup battery is present and ratse if the backup battery level is low or the battery is not present is TRUE if the mains adaptor is present and powered up is a set of flags which can be one of: a) E_SUPPLY_SYSTEM_TIME_CHANGED this flag only has meaning when the battery insertion date changes. If set, it means that the system time has changed; if not set, it means that the main battery has been changed. b) E_SUPPLY_SOUND_WARNING this flag is set if the battery power level is too low to operate sound. Its setting implies that the user has been warned at least once before of the failure of an attempt to generate sound. C) E_SUPPLY_FLASH_WARNING this flag is set if the battery power level is too low to operate a flash SSD. Its setting implies that the user has been warned at least once before of the failure of an attempt to write to flash. These warning flags are intended to be used only for supplying information to a user. The last two, in particular, do not necessarily imply that an attempt to generate sound or to write to flash will definitely fail. Even if either or both of these warning flags are set, the attempt may succeed - for example, either because the battery voltage has recovered since an earlier failure, or because the machine is now connected to mains power. An application is not expected to test either of these flags before attempting the corresponding operation - failures should simply be handled by standard error-recovery techniques. is the system time when the present main battery was inserted is the total length of time in 'ticks' (1/32 second) for which the machine has been switched on and powered by the main battery. Any period during this time when the machine has been powered by the mains adaptor is excluded from the total. is the length of time in 'ticks' (1/32 second) for which the machine has been switched on and powered using the mains adaptor. is the cumulative current delivered by the present main battery; it is a product of current x time and is measured in units of milliamps x 'ticks' where a 'tick' is 1/32 second On machines that use the ASIC1 chip (that is, the Series 3 classic and HC) this information is not available, even if the machines contain version 3.18 or later of EPOC. In such a case, p_supplyinfo writes zero values to all members of the E_SUPPLY_INFOo struct. 13-8 13 GENERAL SYSTEM SERVICES p_wsupply Get battery warning and maximum levels VOID p_wsupply (E_SUPPLY_WARNINGS *pValue) ; Write the recommended low voltage warning level and the nominal maximum voltage of the main battery and the Lithium backup battery to pvalue. The &_SUPPLY_WARNINGS Struct is defined as: typedef struct { UWORD mainBatteryWarning; UWORD lithiumBatteryWarning; UWORD mainBatteryMax; UWORD lithiumBatteryMax; } E_SUPPLY_WARNINGS; . where: mainBatteryWarning is the recommended voltage at which to warn the user of a low main battery lithiumBatteryWarning is the recommended voltage at which to warn the user of a low Lithium backup battery mainBatteryMax is the nominal maximum voltage of the main battery lithiumBatteryMax is the nominal maximum voltage of the Lithium backup battery All voltages are provided in millivolts. If the host machine can take more than one battery type, the voltages will depend on the battery type set with p_setbat. p_getbat Get the battery type INT p_getbat (VOID); Return the current battery type. The battery types of the form =_BATTERY_xxx are described at the beginning of this section. On machines whose hardware does not support detection of the battery type, a call to this function should be preceded by a call to p_setbat. p_setbat Set the battery type INT p_setbat (INT nType); Set the battery type to nType and return zero if successful or E_GEN_nsup if battery type nType is not supported by the hardware. The battery types of the form &_BATTERY_xxx are described at the beginning of this section. This function has no practical effect on machines, such as the Workabout, whose hardware supports the detection of the battery type. Keyboard p_getscancodes Get the state of all keys INT p_getscancodes (UWORD *pScan) ; This function is only available in EPOC version 3.18 or later. Write the current state of all the keys on the keyboard to the array of ten words pointed to by pscan. Each key corresponds to a bit in the word array. In the case of the Series 3a, the lowest eleven bits in each of the first eight words are used. All other machines in the SIBO range use the lowest eight bits in all ten words. If a key is depressed the corresponding bit will be set, otherwise it is clear. 13-9 PLIB REFERENCE The mapping between keys and bits in the array varies from machine to machine. In all cases, however, if no key is depressed all ten words in the array will contain zero. The following example, to detect if any key is pressed, is suitable for use on all SIBO machines: GLDEF_C IsKeyDown (VOID) { UWORD *p; UWORD scans[10]; p=&scans [0]; p_getscancodes (p) ; for (;p<=&scans[9];p++) { if (*p) return (TRUE) ; } return (FALSE) ; } The mapping between keys and the bits within the array for the Series 3a keyboard is given in the description of the EPOC HwGetScancodes service, in the Hardware Management chapter of the EPOC O/S System Services manual. Display p_geticd Get the system display type INT p_getlcd(VOID); -Return the (positive) system display type, where the display types are defined in epoc.h. The function returns -1 if the host is a PC that has an unknown display type. On a SIBO machine the display type is a good way of identifying the model. The appropriate constants are defined in epoc.h. Examples are: E_LCD_640_400 a 640x400 pixel display as on the MC 400 E_LCD_640_200_SMALL a 640x200 pixel display as on the MC 200 E_LCD_160_80 a 160x80 pixel display as on the HC E_LCD_240_80 a 240x80 pixel display as on the Series 3 E_LCD_480_160 a 480x160 pixel display as on the Series 3a E_LCD_240_100 a 240x100 pixel display as on the Workabout On a PC, the display types are: E_PC_HERC Hercules graphics adaptor E_PC_CGA CGA graphics adaptor E_PC_MDA MDA display adaptor E_PC_EGA_MONO EGA monochrome graphics adaptor E_PC_EGA_COLOUR EGA colour graphics adaptor E_PC_VGA_MONO VGA monochrome graphics adaptor E_PC_VGA_COLOUR VGA colour graphics adaptor If p_get1cd returns -1 (which can only happen on a PC), and you choose not to fail, it is recommended that you proceed as if it had returned E_Pc_vGA_mMoNno. p_Icdcontrastdelta Change the LCD contrast VOID p_lcdcontrastdelta(INT nDelta); Step the LCD contrast up or down depending on whether nbde1ta is positive or negative respectively (the magnitude of nDelta is ignored. The LCD contrast is changed through a sequence of values in a circular fashion. That is, stepping the LCD contrast up when it is already at its maximum value sets it to its minimum value and vice versa. The sequence of values that may be set varies from model to model. On a particular model, the values can be ascertained by using p_get1cdcont rast, described below. 13-10 13 GENERAL SYSTEM SERVICES p_geticdcontrast Get the current LCD contrast INT p_getlcdcontrast (VOID) Return the current LCD contrast setting. p_backlight Switch the backlight on or off INT p_backlight (INT mode) ; Switch the backlight on or off, depending on mode as follows: E_BACKLIGHT_OFF switch the backlight off (if it is not already off) E_BACKLIGHT_ON switch the backlight on (if it is not already on) and reset the auto-switch-off timer E_BACKLIGHT_TOGGLE switch the backlight on and reset the auto-switch-off timer if it is currently off or switch the backlight off if it is currently on E_BACKLIGHT_QUERY does nothing and is used just to get the current backlight on/off state For EPOC version 3.57 and above, provided a backlight is fitted, the function returns the on/off state (&_BACKLIGHT_ON if on, E_BACKLIGHT_oFF if off) as it was as the function was entered. If no backlight is fitted, the function returns E_GEN_NSUP. For earlier versions of EPOC, the return value is unreliable. p_setbacklight Set the backlight control value VOID p_setbacklight (UINT flag) ; Enable or disable the backlight key and set the backlight auto-switch-off interval in ticks. If the most significant bit of f1ag is set (as given by the bit mask &_BACKLIGHT_DISABLE), the operating system will not respond to the backlight key. (However, this does not stop the backlight from being switched by a program calling p_backlight.) The lower 15 bits of £1ag specifies the backlight auto-switch-off interval in ticks (1/32ths of a second). If this value is zero, the backlight is not automatically switched off by a backlight timer and it will remain on until the machine switches off. For example: p_setbacklight (96); sets the backlight auto-switch-off interval to 3 seconds and: p_setbacklight (E_BACKLIGHT_DISABLE | (32*5)); sets the backlight auto-switch-off interval to 5 seconds and disables the backlight key. p_getbacklight Get the backlight enablement UINT p_getbacklight (VOID) ; Return the backlight control value as set by p_setbacklight, described above. 13-11 PLIB REFERENCE Sound Most SIBO machines contain a piezo-electric buzzer in addition to a loudspeaker. This section describes how to use the piezo to make a sound, and how to manipulate the set of flags used to control the sound produced by the system. The piezo uses very little power and is an easy way of generating sound, although it is fairly quiet. Consider using the snp: device driver, which drives the loudspeaker, if greater sound complexity or a louder sound is required. The snp: device driver is described in the I/O Devices Reference manual. The bit masks for the flags controlling sound output are: E_SOUND_KEYBOARD keyboard clicks are silenced if clear E_SOUND_BUZZER the piezo sound system is silenced (except for keyclicks) if clear E_SOUND_DEVICE the snp: device driver is silenced if clear E_SOUND_LOUD the piezo will sound louder if set E_SOUND_DISABLE all sound in the system is silenced if set p_sound Make a sound with the piezo VOID p_sound(UINT nDuration, UINT nPitch); Make a sound through the piezo for nDuration system ticks and at pitch nPitch (where the pitch has frequency 512/nPitch KHz). Shared access to this piezo service is controlled by first waiting on a mutual exclusion semaphore that has been pre-counted with 1 (mutual exclusion semaphores are described in the chapter Asynchronous Requests and Semaphores). The effect of the mutual exclusion semaphore, assuming the piezo is not already in use, is that the first call to p_souna will complete immediately but subsequent calls will wait until the current operation completes. The result is that there could be an indefinite wait before the sound is made. If nDuration is passed as a negative value the call will always complete immediately, but will fail to make a sound if the piezo is currently in use. For example: p_sound (5,320); makes a short beep, suitable for accompanying an error notification. The call: p_sound (-5, 320); will make the same sound, provided the piezo is not in use. p_getsnd Get the sound flags INT p_getsnd(VOID); Return the current setting of the sound flags. The flags are described at the beginning of this section. p_setsnd Set the sound flags VOID p_setsnd(INT nFlag); Set the sound flags to nFlag. The flags are described at the beginning of this section. 13-12 13 GENERAL SYSTEM SERVICES Sound on the Series 3a The Series 3a machine does not contain a piezo-electric buzzer, so all sounds are made via the loudspeaker. The Series 3a operating system does, however, contain a buzzer emulator (using the snp: device driver) that supports the sound services described in the previous section. This does, of course, mean that the p_sound service uses more power than in the case of machines that contain a piezo-electric buzzer. Since all sounds are produced via the snp: device driver, sounds that, in other machines, use the buzzer (keyclicks, for example) are disabled while the Series 3a is playing a sound, such as an alarm. Sound files Series 3a sound files are files that contain a 32-byte header and a byte stream of A-Law encoded digital sound. Such files normally have a .wve extension. During recording 13-bit (a sign bit plus 12 magnitude bits) sound samples is converted to an 8-bit data stream at 8000 bytes per second by CODEC hardware, using A-Law encoding. During playback the byte stream is sampled at 8000 bytes per second and decoded to 13-bit sound by the CODEC. In C, the file header is represented by the following struct (defined in epoc.h): #define SignatureSize 16 #define ALawSignature "ALawSoundFile**" typedef struct { TEXT Signature[SignatureSize]; UWORD Version; ULONG Samples; UWORD SilenceInTicks; UWORD Repeats; UWORD Spare[3]; } SndFile;. This header is written and read by the Series 3a sound services described below. The meanings of the items in the sndFile struct are: Signature the 16-byte (including the zero terminator) string "ALawSoundFile**". Version the Series 3a sound file version number as a 4-digit hexadecimal number of the form xvyz, where x is the major release number, yy is the minor release number and z is normally the hexadecimal digit r. See, for example, the description of p_version. Samples the number of bytes following the header. This must always be size of file less the 32 bytes for the header. Dividing this by 8000 gives the duration of the sound in seconds. SilenceInTicks the number of system ticks of silence appended to each repeat on playback (in practice, you get at least 2 ticks between repeats). Repeats the number of times to repeat the sound on playback (0 and | are the same). Spare reserved for future use. A system tick is a 1/32th of a second, equivalent to 250 samples. The program wav2wve.exe, supplied with the SDK (it is installed to the \sibosdk\sys directory) converts .wav sound files to the .wve format. The A-Law encoding scheme The encoding scheme compresses signed, 12-bit magnitude (13 bits in all) samples into an 8-bit representation. A-Law encoding uses a logarithmic compression to reduce the size of sound files whilst preserving the overall sound quality. The use of a logarithmic scale ensures that the low amplitude signals (which contain most of the information in speech signals) are stored and reproduced with a minimal loss of fidelity. The A-Law encoding and decoding schemes are illustrated in the following sections. 13-13 PLIB REFERENCE Encoding If the input data is represented by a normal two's complement signed value, it must first be converted into a 12-bit magnitude, plus a 13th sign bit. For example, a value of -1 (represented by the 16-bit two's complement value of 0xff£££) must be converted to the 13-bit binary representation (1) 000000000001, where brackets indicate the sign bit. The following table represents the range of possible 13-bit inputs. In this table an x character indicates either a one or a zero (a "don't care" state) and an s character represents the sign bit: 0 0 0 0 0 0 0 a VrFOCCCCCO TMrFDdAO0OCO Qa7MmrFDVDGACO faeces! eucaeeeas HANNNNHDADADN xx AaATWrO xxx AAOTM mM xx xXKM ATS xxx MM AAA xx xxM MK OO xxx MMM MM A-Law compression of the above 13-bit inputs leads to the following range of 8-bit output values: PRRERPODOO PROOFRFOO FPOrFPOFOFRSO TOC OCC OO aaaaaaa a Ss Ss Ss Ss Ss Ss Ss Ss oo ooo mw ow w aaaqaaaqaaaa The compressed data is computed according to the following rules: e the most significant bit preserves the sign bit of the original data item e the following three bits represent the magnitude of the original data item, by recording the position of the most significant non-zero bit (but note that the smallest magnitude group is treated exceptionally) e the remaining four bits record the next four most significant bits of the original data (in all but the first group, these are the four bits that follow the most significant non-zero bit) The compressed data is then manipulated to invert bits 0, 2, 4 and 6. This manipulation brings the ratio of ones to zeroes closer to 50:50 and thus improves analog transmission of the data. Examples of A-Law encoded data are given in the following table, where brackets identify the sign bit. signed input 13-bit data compressed data encoded output -1 (1)000000000001 (1)0000000 (1)1010101 +2 (0)000000000010 (0)0000001 (0)1010100 -371 (1)000101110001 (1)1000111 (1)0010010 +2074 (0)100000011010 (0)1110000 (0)0100101 A-Law encoding can be performed most simply and rapidly by means of a look-up table. Note that a 2048- element table is sufficient, since the least significant bit of the input data has no effect on the encoded value. 13-14 13 GENERAL SYSTEM SERVICES Where speed is not of the essence, an algorithmic method may be used, such as that illustrated by the following code: #define MAGICXOR 0x2a #define ONE 1 #define BIT8 0x80 #define MASK2 0x03 #define MASK3 0x70 #define MASK4 Ox0f #define MASK8 Oxff GLDEF_C INT Compress(INT x) /* Compress input 16-bit 2's complement integer 8-bit signed compressed number using A-law. ee: { INT p,S,y; /* convert 2's complement to sign bit and magnitude */ p=BIT8; /* p is the (inverted) if (x&0x1000) { X= -X; X&=OxFFF; p=0; } if (x& (MASK4<<8)) /* Find leading { if (x& (MASK2<<10) ) s=(x& (ONE<<11)) ? else s=(x& (ONE<<9)) ? 5 } else { if (x& (MASK2<<6) ) s=(x& (ONE<<7)) ? 3 else s=(x& (ONE<<5)) ? 1 } if (s==0) y=x>>1; else y=((x>>s) &MASK4) | (s<<4); return ((~((y|p) *MAGICXOR) ) &MASK8) ; } sign bit */ (13-bit magnitude) to using binary search */ Note that the final bit manipulation is done by adding in the (inverted) sign bit, performing an exclusive or with maGIcxor (0x2a - inverting bits 1, 3 and 5) and then complementing the result. This exactly corresponds with the formal definition of A-Law compression and also has the effect of restoring the sign bit. It would be marginally more efficient to replace the definition of macicxor with: #define ALTXOR 0xD5 and replace the last line of the function with: return (((y|p) “ALTXOR) &MASK8) ; 13-15 PLIB REFERENCE Decoding Decoding an A-Law compressed data value broadly consists of reversing the process that is described in the previous section. The bit manipulation is first reversed, by re-inverting bits 0, 2, 4 and 6. This results in the range of possible 8 bit values as follows 0 0 0 0 1 1 1 1 ANDNHNANHADNA PROORROO FPOrROGCrROrROA oo ooo ow w® [on ON ON OE ON OME OO} qgqaaqaaqaaqaaa aaaaaaaa Decompressing these 8 bit inputs using A-Law decoding leads to the following 13 bit outputs: FOOCOCOO CrFODCCCACO ToMrFWCAOA0CO QavTMrFDCOCSO aanowraaonao rPaATWMrFrAO OrFaATMrFO cooraQaqnoga yw oooraq ogo ooooraaqa coooorga COCO COOFRF Ss Ss Ss Ss Ss Ss Ss Ss The decompressed data is computed according to the following rules: e the most significant bit preserves the sign bit of the compressed data item e the following three bits of the compressed data are used to determine the position of the most significant non-zero bit of the decompressed data (but note that the smallest magnitude group is treated exceptionally) e the remaining four bits of the compressed data are used as the next four most significant bits of the decompressed data (in all but the first group, these are the four bits that follow the most significant non-zero bit) e the next most significant bit of the decompressed data is set to one and any remaining trailing bits are set to zero. The resultant value is thus set to the mean of all the possible values which, on compression, give the value that is being decoded If the output data is required in 16-bit two's complement format, then the sign bit must be extracted from the data and, if it is set, the decoded value should be negated. Examples of decoding are given in the following table, where brackets identify the sign bit. encoded data compressed data 13-bit data signed output (1)1010101 (1)0000000 (1)000000000001 -1 (0)1010100 (0)0000001 (0)00000000001 1 +3 (1)0010010 (1)1000111 (1)000101111000 -376 (0)0100101 (0)1110000 (0)100001000000 +2112 The starting values in this table are the results from the encoding examples given in the previous section. Notice that compression followed by decompression loses some of the information, as is expected. 13-16 13 GENERAL SYSTEM SERVICES As for encoding, A-Law decoding can be performed by means of a look-up table. In this case, a 256- element table is sufficient. It is, however, simple to perform the decoding algorithmically, as illustrated in the following code: #define XORMASK 0x55 #define BIT8 0x80 #define MASK3 0x70 #define MASK4 Ox0f #define MASK8 Oxff GLDEF_C INT ALawDecode (INT x) /* Expand input 8-bit signed compressed number to 16-bit 2's complement integer (13-bit magnitude) using A-law. Si), { INT s,y; x= (x*XORMASK) &MASK8; S=(x&MASK3) >>4; y=0; if (s) { y=0x10; s-=1; } y=(( (y+ (x&MASK4) ) <<1) +1) <