Last revised: 09Apr2002 GLGTable of Contents
THIS SOFTWARE IS OBSOLETE The newer Suite/P Toolkit provides most of its features.
These files are available mainly for historical purposes.
Imagine if there were Java classes representing all the fun things you can do with Posix (Unix) system-calls and library functions? OK, not all the fun things, because you can already do many fun things with java.io.File and java.io.Socket, and why bother reinventing those classes when there's plenty of fun to be found in other areas. Yes, I know, some of this fun stuff is Unix-specific. Or at least Posix-specific. Or maybe it's specific to certain OSes and platforms. Still, wouldn't it be fun to have a Java Posix API?That's what the Easy Posix Toolkit is: an API for Posix functions, written in Java. I didn't provide every system-call and every library function, so you are limited in your opportunities for fun. But the toolkit's architecture is easily extensible into other areas, so you can expand your fun horizons with relative ease.
If you're on Mac OS X, the Easy Posix Toolkit also gives you a concrete implementation. So maximal fun on Mac OS X is now possible. OK, maybe not maximal fun, since I didn't do every system-call and every library function possible under the Mac OS X APIs. Hey, one person can only provide so much fun.
In designing the Easy Posix Toolkit, I concentrated on things I think matter most, and where there is currently nothing available in Java or in JSR's. For example, I didn't do sockets, because Java already has network sockets. I didn't do high-performance async I/O, either, because there's already a JSR for that. Instead, I focused on simple everyday things like file permissions and symlinks, real and effective user ID, signals and signal-handling, process resource limits, and so on.
Java is currently unusable for some tasks because it doesn't have any representations for many of these simple, mundane things. For example, some system-admin tasks require setuid-root permission on the program. But Java knows nothing about this, nor about user IDs in general, so it may not be a good choice for writing such a program. As another example, some server programs written in Java suck because they hit limits like the number of open file-descriptors, or they die ungracefully when certain signals arrive. Many facilities of the Easy Posix Toolkit are even useful to ordinary user programs, like the amount of free space on a disk volume, or whether a disk is read-write or read-only, or creating a hard-link or a symlink, or even just changing the permission bits (access modes) of files.
It's the Factory pattern. Nested over and over.There are Abstract Factory patterns and Factory Method patterns. Nested over and over.
Why so many factories? Because the individual functional areas interconnect better that way. Factories makes extensibility easier, because there are fewer interdependencies and less overlap, making it easier to add new facilities later. Factories makes implementations easier to do, because you can do some parts without doing other parts, and the parts you provide will still work. Factories also makes subsetting easier, so you can eliminate the pieces you don't want but the parts you keep will still work.
Why don't the Toolkit's classes and methods correspond directly to the POSIX system-calls and library functions? Because it sucks that way. Trust me. I tried a few things that way, and it was amazingly awful. If you think you have a better design than this API, go for it. If you're on Mac OS X, you can even rip out the entire API except for the Ten class, and make a completely new API.
I'm not saying this is the best API for Posix-like capabilities, though I think it's pretty good. There are many ways to provide these capabilities (I went through several design variations), and there is plenty of room for reasonable designs (and designers) to disagree. You may have your own ideas. Maybe there's a particular object-oriented design for Posix calls that you'd like to try. Go for it. You can build whatever you want as Adapters or Facades or whatever on top of the Easy Posix Toolkit classes. You can even make the whole thing look almost exactly like the C functions in one big static class, if that's what you hunger for.
Despite being called the Easy Posix Toolkit, you can implement many of these facilities on platforms that are not POSIX-compliant. If some facility, say GroupID, is meaningless on your desired platform, you can either omit it or provide a do-nothing implementation.
These are the packages in the Easy Posix Toolkit:If your program doesn't use any of the classes from app.posix.test or glguerin.xio, you can safely omit these packages from your redistributions.
- glguerin.posix -- The Toolkit classes and interfaces proper, centered on the Posix factory class.
- glguerin.posix.imp.* -- Concrete implementations of the Toolkit classes. An imp is provided for Mac OS X (TenPosix), and a non-imp for all other platforms (UnPosix).
- glguerin.util -- Utility classes used by glguerin.posix classes and implementations.
- app.posix.test -- The test classes, which are also examples of use.
- glguerin.xio -- Experimental I/O and pathname classes. These were copied and modified from the glguerin.io package of the MacBinary Toolkit for Java, and placed here under a non-conflicting package name.
In the beta release of the Easy Posix Toolkit, the classes in glguerin.xio are modified versions of some classes from my MacBinary Toolkit for Java, but in a different package. This was done so that these two toolkits don't conflict. This is a temporary expedient only. In the final release of the Easy Posix Toolkit, I will revise all the classes shared with the MacBinary Toolkit so they are identical in both toolkits. These classes will then reside once again in the glguerin.io package. The MacBinary Toolkit will also undergo a maintenance release to accomodate these changes, and they will live happily ever after.
Below are the principal Easy Posix Toolkit classes and interfaces, along with the system-calls and library functions they correlate to.
Posix, PosixImps:
no system calls, but provides factory methods for all the other interesting classes.Environs, MountInfo, ResourceLimit, ResourceUsage:
getenv(3), getfsstat(2), getpid(2), getppid(2), getpriority(2), getrlimit(2), getrusage(2), setpriority(2) setrlimit(2), umask(2)FileRef, FileState, MountInfo:
chflags(2), chmod(2), chown(2), link(2), lstat(2), mkfifo(2), stat(2), statfs(2), symlink(2)GroupID:
getegid(2), getgid(2), getgrgid(3), getgroups(2), setegid(2), setgid(2)Signals, SignalQueue:
kill(2), sigaction(2), signal(3), sigprocmask(2)SysTime:
adjtime(2), gettimeofday(2), settimeofday(2),UserID, UserInfo:
geteuid(2), getlogin(2), getpwent(3), getpwuid(3), getuid(2), seteuid(2), setuid(2),
The central factory class is glguerin.posix.Posix. It has a configurable static factory method: GetPosix(). You tell it which concrete Posix subclass to instantiate using SetFactory(), and it does the rest. You give SetFactory() a fully qualified class name, typically retrieved from the PosixImps class.The list of known Posix implementations is maintained by glguerin.posix.PosixImps. You usually call its selectImpName() method to retrieve an implementation's class-name, but if you need to manipulate the list of known imps there are additional methods: getImps() and setImps().
The responsibilities for managing the different aspects of various implementations are divided between PosixImps and Posix. Basically, the PosixImps class knows the class-names and platform-identifiers for the various platform-specific Posix factories, while the Posix class runs the chosen factory. This division of responsibility means that the Posix class and the list of known implementations are more loosely coupled. It also means that you don't have to use PosixImps to retrieve the factory name. You can use any scheme you want, as long as it produces the fully qualified class name of a concrete Posix implementation.
The Posix class is abstract. In addition to its static GetPosix() factory method, it has a number of abstract factory methods, which an implementation provides. These subsidiary factory methods return instances of the other interesting classes, such as UserID, Environs, Signals, and FileRef. Thus, we have factories within factories, reaching into each area of functionality. The Posix class thus acts as the central factory or nexus of production for all the other interesting classes that make up the Easy Posix Toolkit.
Some of the interesting subsidiary classes also have factory methods. For example, Signals has makeSignalQueue(). Yet again, we have factories within factories.
The implementation-provided factory methods in the Posix class are:
- getEnvirons() -- returns an Environs representing this process's environs or surroundings. Environs represents much more than just the process's environment strings. It also represents process IDs, process priority, resource limits, resource usage, umask, and mounted file-systems. In short, the process's surroundings or environs.
- getUserID() -- returns a UserID representing the real and effective user ID of this process. UserID also provides the login ID, name-lookup, and access to UserInfo user-account information.
- getGroupID -- returns a GroupID representing the real and effective group ID of this process. GroupID also provides name-lookup and other group-related functions.
- makeFileRef() -- creates a new FileRef for accessing the file-system.
- getSignals() -- returns a Signals instance for ignoring, exiting, or defaulting on signals, or for making a SignalQueue that can receive signals.
- getSysTime() -- returns a SysTime for setting or adjusting the system time, as well as reading a microsecond-resolution time base.
Environs represents the environs or surroundings of the current process. It's a bit of a catchall for things that didn't fit elsewhere, but didn't merit their own class. Environs uses MountInfo to describe a mounted file-system. It uses ResourceLimit to represent settable resource limits, and ResourceUsage to represent cumulative resource usage by the current process or its terminated children. Environs has the following methods:
- getEnvString() -- return an environment string, given its name.
- getProcessID(), getParentProcessID -- return the process ID and parent process ID, respectively.
- getUMask(), setUMask() -- access the umask value, which determines the default permissions of files and directories.
- getLimit(), setLimit -- access the current process's resource-limits, using the ResourceLimit class.
- getUsage() -- return cumulative resource usage statistics in a ResourceUsage instance.
- getNice(), setNice(), getNiceExpedient() -- access the process's OS scheduling priority (it's nice level)
- getMounted(), getMountFlagsDictionary() -- return information about the mounted file-systems, in MountInfo instances, or in a Dictionary.
MountInfo describes a mounted volume or file-system with the following methods:
- getBlockSize() -- return the block size, measured in bytes.
- getBlocksTotal() -- return the total number of blocks.
- getBlocksFree() -- return the overall number of free blocks.
- getBlocksAvailable() -- return the number of free blocks available to this user, which may be less than the overall number of free blocks.
- getNodesTotal() -- return the total number of inodes.
- getNodesFree() -- return the number of free inodes.
- getOwner() -- return the integer user ID of who owns the file-system, i.e. who mounted it.
- getFlagsDictionary() -- return a Dictionary representing the file-system's mount-flags. Among other things, this signifies whether the file-system is read-only, whether it's local or remote, whether it's exported, etc.
- getTypeName() -- return a String representing the file-system type or format (e.g. "hfs", "nfs")..
- getPath() -- return the pathname String of the directory where this file-system is mounted.
- getDeviceName() -- return the pathname String (or other device-name) of the raw block-device holding this file-system.
UserID represents the process's real and effective user ID, and also provides access to other things related to user IDs. The UserInfo interface represents user account information, as returned by UserID. UserID has the following methods:
- getReal(), getEffective(), getOriginal() -- return the process's current real, current effective, and original effective user ID.
- getLogin() -- return the process's login user ID.
- nameForID() -- return a name String for a given user ID.
- asReal(), asOriginal() -- set the process's effective user ID to the real or to the original effective user ID. These are conveniences for commonly used idioms.
- as() -- set the process's effective user ID to an arbitrary given user ID.
- become() -- set the process's real, effective, and original user ID to an arbitrary given user ID.
- getSuperUser() -- return the superuser's user ID.
- hasSuperPowers() -- return a boolean signifying whether this process is able to take on an effective user ID of the superuser.
- getUserInfo(), getAllUserInfo() -- return a UserInfo object for a specific given user ID, or for all local user accounts.
GroupID represents the process's real and effective group ID, and also provides access to other things related to group IDs. GroupID has the following methods:
- getReal(), getEffective(), getOriginal() -- return the process's current real, current effective, and original effective group ID.
- nameForID() -- return a name String for a given group ID.
- asReal(), asOriginal() -- set the process's effective group ID to the real or to the original effective group ID. These are conveniences for commonly used idioms.
- as() -- set the process's effective group ID to an arbitrary given group ID.
- become() -- set the process's real, effective, and original group ID to an arbitrary given group ID.
- getGroups() -- return an array of group IDs, representing the groups of which the current process is a member.
FileRef provides access to the file-system. It only provides features that you can't get from java.io.File. FileRef creates and returns instances of FileState, which represent information like permissions, owner and group ID, node-type, and link-count.FileRef has a simple yet powerful way of representing pathnames. You simply provide an Object whose toString() method returns a pathname. The Object can be any Object at all, including a String, and it can return any pathname at all. For example, if it's a glguerin.xio.Pathname, then the attached FileRef will follow changes you make to the underlying Pathname.
FileRef has the following methods:
- getPath() -- return the current pathname, by calling on the encapsulated pathname Object.
- getFile() -- return a java.io.File for the current pathname.
- getState(), getState(boolean) -- return a FileState describing the current pathname's target, with or without following symlinks.
- getMountInfo() -- return a MountInfo describing the mounted file-system where the current pathname's target resides.
- setPermissions() -- set the permission-bits of the current target.
- setFlags() -- set the supplementary flags of the current target.
- setOwnership() -- set the owner's user ID and/or group ID of the current pathname target.
- createLink() -- create a hard-link or a symlink to the current pathname target.
- createPipe() -- create a named pipe (FIFO) at the current pathname.
FileState has the following methods:
- getPath() -- return the pathname when this FileState was created.
- followedSymLinks() -- return whether this FileState followed symlinks or not when created.
- getNodeID() -- return a device and i-node identifier.
- getNodeType() -- return an integer node-type identifier (file, directory, symlink, char-dev, block-dev, pipe, etc.).
- getPermissions() -- return the permissions at the time this FileState was created.
- getLinkCount() -- return the link count (hard-links) at the time this FileState was created.
- getOwner(), getGroup() -- return the owning user ID and group ID, respectively, at the time this FileState was created.
- getFlags() -- return the supplementary flags.
Signals provides access to signal-handling and signal-sending facilities. It is somewhat more dependent on the platform OS and implementation than some of the other Easy Posix Toolkit classes. Still, any OS that provides signal-sending and signal-handling capabilities that are even remotely Unix-like should be able to implement Signals and SignalQueue.Signals is used to send signals, or to discover the current signal-handling behavior of the process. Signals is also used to arrange for the ignoring, defaulting, or exiting behavior of signals. A SignalQueue is created by Signals, which you then use to receive signals. You can create one SignalQueue or many. You can arrange for each distinct signal number to be delivered to its own SignalQueue, or you can tell one SignalQueue to receive two or more distinct signals.
All signal values are initially referenced by name. This is so platforms and implementations have the necessary flexibility in assigning the meaning of the signals they support. Signal names are defined in the SignalNames interface, which the Signals class then "implements" to provide convenient access to the names. The Signals class returns a platform-dependent signal-name Dictionary listing the sendable and receivable signals. You can also ask Signals to map a signal-name to a signal-number, or vice versa, without retrieving the intermediate Dictionary object.
Signals has the following methods:
- getSignalDictionary() -- return a Dictionary of sendable or receivable signals. The Dictionary maps platform-agnostic signal-name Strings to implementation-specific signal-number Numbers.
- signalForName(), nameForSignal() -- map a signal-name to a signal-number, or vice versa.
- signalProcess() -- send a given signal-number to a given process ID.
- signalOther() -- send a given signal-number to a given process-group ID or other identifier.
- getSignalHandling() -- return an Object signifying the process's current handling of a given signal. The Object is either a String telling that a signal is ignored, defaulted, or exiting, or the Object is the SignalQueue on which the signal is received.
- makeSignalQueue() -- factory method makes a new SignalQueue, which you then use to receive signals.
- setExitingStatus() -- set the exit-status that exiting signals exit with.
- ignore(), normal(), exiting() -- arrange for the given signal to be ignored, defaulted, or exiting, respectively.
- getSignalMask() -- return the process's signal mask.
SignalQueue has the following methods:
- reset() -- clear pending signals and reset the max capacity to a given value.
- receive() -- arrange for a given signal-number to be delivered to this SignalQueue.
- waitForSignal() -- wait for a signal to be delivered. An argument determines whether this method waits for a limited period of time, or indefinitely, or not at all. A received signal-number or 0 is returned.
- isEmpty() -- is the SignalQueue empty?
- getOverruns() -- return any overrun-count of signal deliveries, leaving the counter cleared.
SysTime provides access to time-setting facilities, and to a microsecond-resolution timebase. Time-setting is limited to processes that have an effective user ID of the superuser. All processes can read the microsecond timebase.
SysTime has the following methods:
- getMicroseconds() -- return the current microseconds timebase value.
- isMicrosecondsSinceEpoch() -- is getMicroseconds() grounded in the epoch 1970 Jan 01, or is it relative to some unspecified point in time?
- setSystemTime() -- set the system time.
- adjustSystemTime() -- set a system-time correction factor that's applied gradually.
To Greg's Home Page
To Greg's Software Page