(* Copyright (C) 1990, Digital Equipment Corporation. *) (* All rights reserved. *) (* See the file COPYRIGHT for a full description. *) (* Last modified on Fri Jun 15 23:06:20 1990 by muller *) (* modified on Thu Apr 19 13:42:15 PDT 1990 by swart *) INTERFACE POS; IMPORT Text, Rd, Time; FROM Thread IMPORT Alerted; (* This interface is a native Modula 3 interface to the facilities of IEEE POSIX 1003.1-1988 and some of the facilities of the, as yet unballoted, POSIX 1003.4a-1990 (thread extensions). This interface is designed to be modified for each POSIX implementation, but in a way so that applications using this interface in a portable way will still compile and run. The permissible changes that may be made to this interface and the rules for writing portable code are given here and in the relevant POSIX standards. While it is possible to define a compatible C style binding for the POSIX interface, it is deemed preferable to define a native Modula 3 interface. This interface should work more smoothly with the Modula 3 programming style. Changes were made to take advantage of the following Modula 3 features: objects, threads, enumerated types, sets, TEXTs, plus we have used Modula 3 spelling conventions. Pervasive changes from the C binding include: Changing each procedure that may set the errno variable to instead raise the Error exception. Changing each procedure that may set errno to EINTR to raise the Thread.Alerted exception. Replacing all bit masks with sets. Attempts were made to allow the Modula 3 structures to be binary compatible with the C types, and where that was not possible, a note was made. Replacing those facilities of POSIX that are already present in M3. These include the Thread, Time and Params interfaces. All operations of open files have been added to various methods on subtypes of the type POS.File. The various fcntl operations have been split into separate methods for better use of the type system. Note that setting the process state in one thread may adversely affect other threads in the process using that state. This can be a problem for the signal state as well as the current working directory and the effective user IDs. The related interfaces POSTerminal and POSPath give operations for dealing with POSIX terminals and pathnames. This particular version of the POS interface has been adjusted to support the Ultrix 3.1 version of POSIX. To distinguish all names that are not part of the Modula 3 binding of the POSIX standard, they have been given the suffix "_NP", meaning "Not POSIX". In all enumerated types, the order of the elements may be changed, nonportable names removed, and new nonportable names added. BITS FOR may be added or removed from any type. Wherever a record appears, the orders of the fields may be changed, nonportable fields may be removed and new nonportable fields added. Optional parameters to procedures may be added after all the portable parameters and the parameter names should end with _NP. Arguments may be added to exceptions that have no standard arguments. The argument type must end with _NP. Note that this interface does not describe all of the differences between POSIX and Ultrix 3.1. This interface is limited to only those parts of Ultrix that are needed to completely define the POSIX types. Other Ultrix specific functions are in other interfaces. This interface is rather sparingly commented. It is expected that the programmer has a copy of IEEE 1003.1-1988 available for reference. *) (* Differences between POS and the Modula 2+ OS interface. The OS interface was developed by Paul McJones and Garret Swart to support multithreaded access to the facilities of the Ultrix and Taos operating systems. The OS interface goes farther than this interface in a number of ways. Directory handles. The OS interface allows a process to open a number of directory handles simultaneously, rather than the single working directory allowed by POSIX. Each operation accepting a path also accepts a directory handle that the path is evaluated relative to. This facility is used rather infrequently. Typically in single process/multi-window shells and in servers. However many user programs don't need this facility and are happier with the late binding semantics of keeping directories textually. User handles. The OS interface allows a process to act on behalf of several users simultaneously by allowing an optional User authorization parameter on each call that potentially does something that needs to be authenticated. This is primarily useful for servers. Names instead of IDs. The OS interface deals only with user and group names rather than IDs. This allows scaling to large systems that are not in one administrative domain. The new OS1 interface goes still farther: Defines a super root for access to files in a global name space. Defines new operations useful for managing information stored in a name service. Defines a more flexible access control scheme using access control lists. *) (* Primitive system data types. *) (* The following types may differ on an implementation to implementation basis. The rules regarding permissible bindings for these types is given in the POSIX standards. Those types that are opaque in the POSIX sense have been given the suffix "_T" *) TYPE Short_NP = [-16_8000 .. 16_7FFF]; TYPE Device_T = [0 .. 16_FFFF]; GroupID_T = Short_NP; UserID_T = Short_NP; INode_T = INTEGER; NLink_T = Short_NP; Offset_T = INTEGER; PID_T = INTEGER; Clock_T = INTEGER; (* Units of time. Each unit represents 1/ GetSystemConfigurationParameter(SystemConfigurationParameter.ClockTick) seconds. *) (* Environment description *) (* See Params.i3 *) (* Limits. *) (* Minimum values. These should change only when the POSIX standard changes. *) CONST POSIX_ArgumentMax = 4096; POSIX_ChildMax = 6; POSIX_LinkMax = 8; POSIX_MaxCanonical = 255; POSIX_MaxInput = 255; POSIX_NameMax = 14; POSIX_NGroupsMax = 0; POSIX_OpenMax = 16; POSIX_PathMax = 255; POSIX_PipeBuf = 512; (* Implementation defined maximum values. These are the Ultrix 3.1 values *) CONST ArgumentMax = 10240; ChildMax = 25; LinkMax = 1000; MaxCanonical = 256; MaxInput = 256; NameMax = 255; NGroupsMax = 32; OpenMax = 64; PathMax = 1024; PipeBuf = 4096; (* Configurable Pathname Variables *) TYPE PathnameConfigurationParameter = {bad0_NP, LinkMax, MaxCanonical, MaxInput, NameMax, PathMax, PipeBuf, ChangeOwnerRestricted, NoTruncation, VDisable}; (* Configurable System Variables *) TYPE SystemConfigurationParameter = { bad0_NP, ArgumentMax, ChildMax, ClockTick, NGroupsMax, OpenMax, JobControl, SavedIDs, Version}; (* Constants for portability specifications *) (* These values of these constants are those for Ultrix 3.1 *) CONST POSIX_JobControl = TRUE; POSIX_SavedIDs = TRUE; POSIX_Version = 198808; CONST POSIX_ChangeOwnerRestricted = TRUE; POSIX_NoTruncation = TRUE; POSIX_VDisable = FALSE; (* Errors *) TYPE EC = {ok_NP, Permission, NoEntry, Search, Interrupt, IO, NoXIO, TooBig, NoExec, BadFile, Child, Again, NoMemory, Access, Fault, NotBlock_NP, Busy, Exist, XDevice, NoDevice, NotDirectory, IsDirectory, Invalid, NFile, MFile, NoTty, TextBusy_NP, FileBig_NP, NoSpace, SeekPipe, ReadOnlyFileSystem, MaxLink, Pipe, Domain_NP, Range, WouldBlock_NP, InProgress_NP, Already_NP, NotSocket_NP, DestinationAddressRequired_NP, MessageSize_NP, ProtocolType_NP, NoProtocolOption_NP, ProtocolNotSupported_NP, SocketNotSupported_NP, OperationNotSupported_NP, ProtocolFamilyNotSupported, AddressFamilyNotSupported_NP, AddressInUse_NP, AddressNotAvailable_NP, NetworkDown_NP, NetworkUnreachable_NP, NetworkReset_NP, ConnectionAborted_NP, ConnectionReset_NP, NoBuffers_NP, IsConnected_NP, NotConnected_NP, Shutdown_NP, TooManyReferences_NP, TimedOut_NP, ConnectionRefused_NP, Loop_NP, NameTooLong, HostDown_NP, HostUnreachable_NP, NotEmpty, ProcessLimit_NP, Users_NP, DiskQuota_NP, Stale_NP, Remote_NP, NoMessage_NP, IdentifierRemoved_NP, Align_NP, NoLock, NoSystem}; (* Ultrix 3.1 note: ENOSYS is not defined in errno.h *) EXCEPTION Error(EC); PROCEDURE ErrorMessage(ec: EC): TEXT RAISES {}; (* Returns an error message relating to ec in the appropriate language given the value of the LANG environment variable. *) (* Types for use with access and stat *) TYPE FileType = {Pipe, FIFO, Character, bad3_NP, Directory, bad5_NP, Block, bad7_NP, Regular, bad9_NP, Link_NP, bad11_NP, Socket_NP, bad13_NP, bad14_NP, bad15_NP}; AccessMode = {Read, Write, Execute}; AccessModes = SET OF AccessMode; AccessClass = {Others, Group, Owner}; AccessRights = ARRAY AccessClass OF BITS 3 FOR AccessModes; FileAttribute = {SaveTextAfterUse_NP, SetGroupID, SetUserID}; FileAttributes = SET OF FileAttribute; FileMode = RECORD access: BITS 9 FOR AccessRights; attributes: BITS 3 FOR FileAttributes; type: BITS 4 FOR FileType; END; (* Types for use with the seek *) TYPE SeekCommand = {Set, Current, End}; (* Types for file locking. *) TYPE FileLockOp = {ReadLock, Unlock, WriteLock}; FileLock = RECORD type: BITS 16 FOR FileLockOp; whence: BITS 16 FOR SeekCommand; start: Offset_T; length: Offset_T; pid: PID_T; END; FileLockMode_NP = {Shared, Exclusive, NoBlock, Unlock}; FileLockModes_NP = SET OF FileLockMode_NP; TYPE OpenFileMode = {Read, Write, NoDelay_NP, Append, Mark_NP, Defer_NP, Async_NP, SharedLock_NP, ExclusiveLock_NP, Create, Truncate, Exclusive, BlockInUse_NP, SetInUse_NP, NonBlock, Termio_NP, NoControlTty}; OpenFileModes = SET OF OpenFileMode; TYPE FileStatus = RECORD dev: Device_T; inode: INode_T; mode: (* BITS 16 FOR *) FileMode; nLink: BITS 16 FOR NLink_T; userID: BITS 16 FOR UserID_T; groupID: BITS 16 FOR GroupID_T; rdev_NP: Device_T; size: Offset_T; atime: Time.T; mtime: Time.T; ctime: Time.T; blockSize_NP: INTEGER; blocks_NP: INTEGER; generationNumber_NP: INTEGER; spare4_NP: INTEGER; END; TYPE File = OBJECT METHODS close() RAISES {Error}; (* Some implementations may automatically close a file if that File is garbage collected. *) read(VAR (* out *) buf: ARRAY OF CHAR): CARDINAL RAISES {Error, Alerted}; write(READONLY buf: ARRAY OF CHAR): CARDINAL RAISES {Error, Alerted}; dup(): File RAISES {Error}; getFileMode(): OpenFileModes RAISES {Error}; setFileMode(flags: OpenFileModes) RAISES {Error}; getLock(VAR (* in/out *) flock: FileLock) RAISES {Error}; setLock(READONLY flock: FileLock) RAISES {Error}; setLockWait(READONLY flock: FileLock) RAISES {Error, Alerted}; seek(offset: Offset_T; whence: SeekCommand := SeekCommand.Set ): Offset_T RAISES {Error}; getConfigurationParameter( name: PathnameConfigurationParameter): [-1..LAST(CARDINAL)] RAISES {Error}; (* Returns -1 if there is no limit for the given configuration parameter, otherwise it returns the limit. *) status(VAR (* out *) buf: FileStatus) RAISES {Error}; setOwner_NP(owner: PID_T) RAISES {Error}; getOwner_NP(): PID_T RAISES {Error}; setSynchronous_NP(state: BOOLEAN): BOOLEAN RAISES {Error}; lock_NP(lockMode: FileLockModes_NP) RAISES {Error, Alerted}; changeMode_NP(mode: FileMode) RAISES {Error}; changeOwner_NP(owner: UserID_T; group: GroupID_T) RAISES {Error}; sync_NP() RAISES {Error}; truncate_NP(length: Offset_T := 0) RAISES {Error}; ioctl_NP(operation: INTEGER; VAR (* in/out *) operand: ARRAY OF CHAR) RAISES {Error, Alerted} END; FileImpl <: File; (* FileImpl is the kind of file that is produced by this interface. *) PROCEDURE Pipe(VAR (* out *) readFile, writeFile: FileImpl) RAISES {Error}; (* Files and Directories *) TYPE DirEnumerator = Rd.T OBJECT METHODS next(): BOOLEAN RAISES {Error}; (* Calling next resets the enumerator to read the next directory entry. Returns TRUE if there is an entry following the current entry, FALSE otherwise. *) rewind() RAISES {Error}; (* Resets the reader to be reading the first directory entry again. *) inode_NP(): INode_T RAISES {Error}; (* Returns the inode of the current directory entry. *) END; PROCEDURE OpenDir(dirName: TEXT): DirEnumerator RAISES {Error}; (* Returns a reader that reads the first entry in the directory. Closing this reader terminates the entire enumeration. *) PROCEDURE ChangeDir(dirName: TEXT) RAISES {Error}; PROCEDURE GetPath(path: TEXT; dir: TEXT := NIL): TEXT RAISES {Error}; (* Returns an absolute path name for the given path relative to dir if it is given. If dir is not given, the path is evaluated relative to the working directory. dir must be the absolute path name of a directory. This procedure has no effect on the process's working directory. If the file is has more than one link its absolute path name may not be unique. Replaces getcwd from the C binding. *) CONST DefaultCreateAccess = AccessRights{ AccessModes{AccessMode.Read, AccessMode.Write}, AccessModes{AccessMode.Read, AccessMode.Write}, AccessModes{AccessMode.Read, AccessMode.Write}}; PROCEDURE Open(path: TEXT; flags: OpenFileModes; mode: FileMode := FileMode {access := DefaultCreateAccess, attributes := FileAttributes{}, type := FileType.Regular} ): FileImpl RAISES {Error, Alerted}; PROCEDURE Create(path: TEXT; mode: FileMode := FileMode {access := DefaultCreateAccess, attributes := FileAttributes{}, type := FileType.Regular} ): FileImpl RAISES {Error}; PROCEDURE SetUMask(mask: AccessRights): AccessRights RAISES {}; (* These rights are subtracted from any rights presented to Open, Create, MakeDir or MakeFIFO. *) PROCEDURE Link(path1, path2: TEXT) RAISES {Error}; CONST DefaultMakeDirAccess = AccessRights{ AccessModes{AccessMode.Read, AccessMode.Write, AccessMode.Execute}, AccessModes{AccessMode.Read, AccessMode.Write, AccessMode.Execute}, AccessModes{AccessMode.Read, AccessMode.Write, AccessMode.Execute}}; PROCEDURE MakeDir(path: TEXT; mode: FileMode := FileMode {access := DefaultMakeDirAccess, attributes := FileAttributes{}, type := FileType.Directory} ) RAISES {Error}; PROCEDURE MakeFIFO(path: TEXT; mode: FileMode := FileMode {access := DefaultCreateAccess, attributes := FileAttributes{}, type := FileType.FIFO} ) RAISES {Error}; PROCEDURE Unlink(path: TEXT) RAISES {Error}; PROCEDURE RemoveDir(path: TEXT) RAISES {Error}; PROCEDURE Rename(old, new: TEXT) RAISES {Error}; PROCEDURE Status(path: TEXT; VAR (* out *) status: FileStatus; follow_NP: BOOLEAN := TRUE) RAISES {Error}; (* If follow_NP is FALSE this call will return information about a symbolic link. *) PROCEDURE Access(path: TEXT; amode: AccessModes) RAISES {Error}; (* To check only for existence only pass in AccessType{}. *) PROCEDURE ChangeMode(path: TEXT; mode: FileMode) RAISES {Error}; (* mode.type is ignored. *) PROCEDURE ChangeOwner(path: TEXT; owner: UserID_T; group: GroupID_T) RAISES {Error}; TYPE UpdateTimeBuf = RECORD atime: Time.T; mtime: Time.T; END; PROCEDURE UpdateTime(path: TEXT; times: UpdateTimeBuf) RAISES {Error}; PROCEDURE GetConfigurationParameter( path: TEXT; name: PathnameConfigurationParameter ): [-1..LAST(CARDINAL)] RAISES {Error}; (* Returns -1 if this parameter value is not applicable for the given file. *) (* Process Environment *) PROCEDURE GetPID(): PID_T RAISES {}; PROCEDURE GetParentPID(): PID_T RAISES {}; PROCEDURE GetUserID(): UserID_T RAISES {}; PROCEDURE GetEffectiveUserID(): UserID_T RAISES {}; PROCEDURE GetGroupID(): GroupID_T RAISES {}; PROCEDURE GetEffectiveGroupID(): GroupID_T RAISES {}; PROCEDURE SetUserID(user: UserID_T) RAISES {Error}; PROCEDURE SetGroupID(groupID: GroupID_T) RAISES {Error}; PROCEDURE GetGroups(): REF ARRAY OF GroupID_T RAISES {}; PROCEDURE GetLogin(): TEXT RAISES {Error}; PROCEDURE CurrentUser(): TEXT RAISES {Error}; PROCEDURE GetProcessGroup(): PID_T RAISES {}; PROCEDURE SetSessionID(): PID_T RAISES {Error}; TYPE UName = RECORD systemName: TEXT; nodeName: TEXT; release: TEXT; version: TEXT; machine: TEXT; END; PROCEDURE GetUName(): UName RAISES {Error}; TYPE Times = RECORD userTime: Clock_T; systemTime: Clock_T; childUserTime: Clock_T; childSystemTime: Clock_T; elapsedRealTime: Clock_T; END; PROCEDURE GetTimes(): Times RAISES {Error}; PROCEDURE CurrentTerminal(): TEXT RAISES {Error}; PROCEDURE GetSystemConfigurationParameter( name: SystemConfigurationParameter ): [-1..LAST(CARDINAL)] RAISES {Error}; (* Synchronous hardware detected errors. *) (* Hardware detected faults are always seen as exceptions to the programmer. Note that these exceptions may have been the result of a language detected runtime error, so catching one of these exceptions may mask such an error. *) TYPE IllegalInstructionArgument_NP = {ReservedAddress, PrivilegedInstruction, ReservedOperand}; EXCEPTION IllegalInstruction(IllegalInstructionArgument_NP); TYPE FloatingPointExceptionArgument_NP = { IntegerOverflow, IntegerDivideByZero, FloatingOverflow, FloatingDivideByZero, FloatingUnderflow, DecimalOverflow, SubscriptOutOfRange, FloatingOverflowFault, FloatingDivideByZeroFault, FloatingUnderflowFault}; EXCEPTION FloatingPointException(FloatingPointExceptionArgument_NP); TYPE SegmentationViolationArgument_NP = ADDRESS; EXCEPTION SegmentationViolation(SegmentationViolationArgument_NP); (* Signals *) TYPE SignalOrNone = {Null, HangUp_NP, Interactive, Quit, IllegalInstruction, Trap_NP, Abort, EMT_NP, FloatingPoint, Kill, Bus_NP, SegmentViolation, BadSystemCall_NP, Pipe, Alarm, Terminate, Urgent_NP, Stop, TerminalStop, Continue, Child, TtyIn, TtyOut, IOReady_NP, ExceededCPUTime_NP, ExceededFileSize_NP, VirtualAlarm_NP, ProfilingAlarm_NP, WindowChange_NP, bad29_NP, User1, User2}; Signal = [SignalOrNone.HangUp_NP .. SignalOrNone.User2]; (* Signals are split into four categories by the Modula 3 POSIX. Hardware detected errors are converted into exceptions in the appropriate thread. These signals should not be sent or manipulated by the programmer. These signals include: IllegalInstruction, Trap_NP, SigAbort, EMT_NP, FloatingPoint, Bus_NP, SegmentViolation, BadSystemCall_NP. Asynchronous signals used for multiplexing a single thread are reserved for use inside the Modula 3 runtime and should not be sent or manipulated by clients. This signals include: Alarm, Urgent_NP, Child, IOReady_NP, VirtualAlarm_NP, ProfilingAlarm_NP. Clients should instead use separate threads making blocking calls. Synchronous serious system detected error exceptions may be either ignored or defaulted by the client. Ignoring them causes the appropriate Error exception to be raised by the thread making the serious error. Defaulting the signal causes the normal default action. These signals should not be sent or set handled state. These signals include: Pipe, TtyIn, TtyOut, ExceededFileSize_NP. The related error codes that are used if the error occurs and the signal is ignored are EC.Pipe, EC.Again, EC.Again and EC.DiskQuota. The remaining signals may be sent to other processes and, except for Signal.Kill and Signal.Stop, they may be either defaulted, ignored or handled by the client. Signals that are handled are logically blocked at all times. They are handled by creating a new thread that blocks waiting for the signal to become pending. See WaitForSignal below. *) TYPE SignalAction = {Default, Ignore, Handle}; PROCEDURE SetSignalAction(signal: Signal; action: SignalAction): SignalAction RAISES {Error}; (* Note that some implementations may restrict this call so that it can only be made in the initial thread. Some implementations may restrict the use of this call as outlined above. Error(EC.Invalid) is raised if the action is inappropriate for the signal. *) TYPE Signals = SET OF Signal; PROCEDURE WaitForSignal(allowed: Signals): Signal RAISES {Error, Alerted}; (* Waits for one of the given signals to become pending. When one does become pending, it is atomically cleared and its identity is returned. *) PROCEDURE GetPending(): Signals RAISES {}; (* Returns the set of pending signals. *) (* Process operations. *) TYPE ProcessWaitOption = {NoHang, Untraced}; ProcessWaitOptions = SET OF ProcessWaitOption; TYPE TerminationReason = {Stopped, Killed, Exited}; ExitStatus = [0 .. 255]; TerminationStatus = RECORD reason: TerminationReason; signal: Signal; (* if reason is Stopped or Killed *) coreDump: BOOLEAN; (* if reason is Killed *) retCode: ExitStatus; (* if reason is Exited *) END; TYPE Process = OBJECT METHODS wait(options: ProcessWaitOptions := ProcessWaitOptions {} ): TerminationStatus RAISES {Error, Alerted}; kill(signal: Signal) RAISES {Error}; setProcessGroup(pgrp: PID_T) RAISES {Error}; getPID(): PID_T RAISES {Error} END; ProcessImpl <: Process; PROCEDURE OpenProcess(pid: PID_T := 0): ProcessImpl RAISES {Error}; (* OpenProcess(0) returns the calling process. Raises Error(EC.Search) if pid is not found. *) (* Process creation and execution. *) (* To allow easy use with threads, we define a new set of procedures for process creation. By default, a new process inherits everything as it would following a POSIX fork and exec except it inherits no file descriptors. Changes made to the template change those defaults. *) TYPE Relationship = {Orphan, Child, Self}; (* Starting a process with relationship set to Self, replaces the calling process's address space and process state with that specified by the process template. All the threads in the parent process are killed (as in Signal.Kill), and the new process is started with a single thread of control. *) TYPE ProcessTemplate = OBJECT METHODS (* Any of these methods may raise EC.Invalid if the process template has been closed or has already been used to start a process. *) setDescriptor(d: CARDINAL; f: File) RAISES {Error}; (* Causes the child to be given the given file as descriptor d. The file may still be used by the parent or it may be closed with no effect on the child. *) setUMask(umask: FileMode) RAISES {}; setSessionID() RAISES {Error}; setWD(path: TEXT) RAISES {Error}; setControlTerminal(f: File; setGroup: BOOLEAN := TRUE) RAISES {Error}; (* If setGroup is TRUE the process group of the terminal is set to that of the new process once that child is created *) unsetControlTerminal() RAISES {Error}; (* The control terminal of the child is unset. *) setPGRP() RAISES {Error}; (* Sets the process group of the new process to its be the same as its PID. *) setUserID(userID: UserID_T) RAISES {Error}; setGroupID(groupID: GroupID_T) RAISES {Error}; setSignalAction( signal: Signal; state: [SignalAction.Default..SignalAction.Ignore] ) RAISES {Error}; (* There are no restrictions on the signal action on a child process. *) setSignalMask(signalMask: Signals) RAISES {Error}; start( path: TEXT; READONLY argv: ARRAY OF TEXT; relationship: Relationship := Relationship.Child; environment: REF ARRAY OF TEXT := NIL ): Process RAISES {Error}; (* Starts a process with data as set in this template. If environment is NIL, the child inherits the environment of this process. Each text in environment is of the form =. Returns the process ID of the created process. If relationship = Orphan then the new process's parent process is set to 0. Once a template has been used to successfully start one process, it may not be used again. There is no need to call close for such a template. The errors on this operation are the sum of those from fork and exec. *) close() RAISES {Error}; (* Used to deallocate all the resources associated with a template that has not been used to start a process. *) END; ProcessTemplateImpl <: ProcessTemplate; PROCEDURE NewTemplate(): ProcessTemplateImpl RAISES {Error}; (* System Databases *) TYPE GroupInfo = RECORD name: TEXT; groupID: GroupID_T; memberNames: REF ARRAY OF TEXT; END; PROCEDURE GetGroupByID(groupID: GroupID_T; VAR (* out *) info: GroupInfo) RAISES {Error}; PROCEDURE GetGroupByName(name: TEXT; VAR (* out *) info: GroupInfo) RAISES {Error}; TYPE UserInfo = RECORD name: TEXT; userID: UserID_T; groupID: GroupID_T; dir: TEXT; shell: TEXT; END; PROCEDURE GetUserByID(userID: UserID_T; VAR (* out *) info: UserInfo) RAISES {Error}; PROCEDURE GetUserByName(name: TEXT; VAR (* out *) info: UserInfo) RAISES {Error}; END POS.