This document works as draft for the OpenPosix standard, version 1.0. It is a standard for operating systems for the Minecraft mod OpenComputers. The aim is to archieve maximum compatibility while providing a Unix style theme.
The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED", "MAY", and "OPTIONAL" in this document are to be interpreted as described in RFC 2119.
Operating systems following that standard MUST support multiple processes running at the same time. A process is accociated with an environment, an id, a current working directory, 3 user ids (effective, real and saved) and 3 group ids (also effective, real and saved). Processes are seperated for the most part even though there are some ways for processes to interact
The PID (Process IDentifier) is a system wide unique handle for the process. The first process (init) gets the ID 1. After that the id gets increased for each process. If a process exits its id will not get reused to avoid confusion.
The environment is basicly the memory a process can access, also known as _G
. If the OS supports multithreading, there is also a _T
, providing thread-specific memory (a metatable can be used to archieve this). A metatable on _G
and _ENV
ensures that only the right process can access data, even if they share the same environment (for example after a use of the spawn()
syscall)
The working directory is where the process resides. Any relative file names used will be applied relative to this directory.
The user and group ids specify the processes owner. You can change any of them, but only to values that are already one of them. For example a process has these uids: 2, 3, 4 (e, r, s). It can now change any of them to 2, 3 or 4. Exception to this rule: a process can arbritarily set any UIDs/GIDs if the process has the capability for it. Note that, if the system does not enforce user/group policies, it always has to assume UID/GID 0 (root)
This is the UID/GID used to check for permissions. Will be same as real UID/GID unless manually set or the executable has the setuid/setgid bit set
This is the UID/GID of the actual person executing
This one defaults to the real UID/GID. Its only purpose is to being able to temporarily change your own effective UID/GID
Syscalls are the way of a process to send messages to the kernel. The RECOMMENDED way for doing so is by internally making syscall()
call coroutine.yield()
. syscall
is a table with __call
metamethod, making syscall("mycall")
and syscall.mycall()
equivalent. The metatable SHOULD be added as wrapper in the usermode initializer (a script that runs in a usermode _G
before a process is executed).
The *
syscall returns a list of all valid syscalls. It is mainly to be used internally.
Invokes a call for a file descriptor. Normal files have write(data: string)
, read(count: number)
, seek(pos: number)
and close()
. read will read at least 1 char, unless it reaches the end of the file; If count is nil it reads until the end of the file descriptor (if supported. A socket for example which does not support this should return nil and and error message). It is a blocking call. all of them return false
, error code, human readable message on error, all but read
true on success. read
returns a string (possibly empty) on success
changes current working directory. Does take relative directory names into account.
gets current working directory
Lists all files in a directory. If this fails, it returns nil, followed by an error code and a human readable error message
opens a file, returning a file descriptor
Creates a new socket fd. It initially does nothing.
Following methods are available through invoke()
:
Connects to a remote host. This can be both another OC computer or a real world machine.
Makes this socket a listen socket. If no interface list is provided it defaults to all supporting interfaces. If a not supporting interfact (for example an Internet Card) is in the interface list, an error will be returned
Blocks until a new incoming request is served. Then returns a file descriptor to it
Send a Datagram (UDP style) packet
after this, the socket can accept UDP packets (to be specified)
Creates a Unix pipe, where all data written to the first FD can be read from second
Returns a list of all running processes
Returns a list of all logged in users, including their tty. a user can be listed multiple times.
Jails the current process into a certain directory
checks if a user has a capability. User id must always be the one of current process unless user has capability to bypass (both effective and real work)
checks if a process has a capability. The euid of selected process must be same as either ruid or euid of current proces or bypass capability must be granted
Sets a processes capability. Needs writeAllCapab capability. If value is not nil it overrides the value for the user
Sets a users capabilities. Needs writeAllCapab capability.
mount(driver: string, device: (number|usernil), mountpoint: string, options: table): boolean[, number, string]
Mounts [device] using [driver] in [mountpoint]
driver must be a valid kernel mode driver. device a file descriptor to the device file. if the fd is opened in read mode, the device can only be mounted with the 'ro' option. it can be nil if there is no device (like for a tempfs). mountpoint has to be an existing empty directory with rwx permissions all set. Options can be arbitrary: the following ones exist by default:
issues a shutdown by sending the 'shutdown' ipc call to init
issues a reboot by sending the 'reboot' ipc call to init
rw: boolean - default: true, conflicts with ro - device mounted read-write
ro: boolean - default: false, conflicts with rw - device mounted readonly
user: boolean - (fstab only) default: false - mounting can be done by a user without need for the mount capability
Checks a file permission. permission is a string consisting of any combination of r w and x (read, write and execute)
Creates a symbolic link. A symbolic link is basicly an alias for a file or directory. they act transparent, so if you open a symbolic link it behaves exactly as if you had opened the target. If the target does not exist it should instead error as if the link itself did not exist. The system may however replace the human readable error message with something like "<source>: invalid symbolic link to <target>". This feature is not mandatory but it SHOULD be implemented by all main file systems. Symlinks can go across different mount points. They must be stored to the physical file system.
Creates a hard link. A hard link nothing but a second file name for a file or directory. Once created it makes no difference if you access the source or target. They behave exactly same. In fact, after deletion of the source you can still access the target. This is archieved by each file having a reference count. This starts out with 1. Each creation of a hard link increments it. Each deletion of a hard link decrements it (the original file name is just a hard link as well). When the counter reaches 0 the actual file gets removed. Hard links do not work cross device. Creation of directory hard links requires the linkDirectory capability. implementation of hard links is OPTIONAL. If implemented, they must be stored to the physical file system.
Retrieves information on a file or directory. Table entries:
- type: string - one of
file
,directory
,device
,fifo
,mount
andlink
- target: string|nil - symbolic link only: the link target
- mode: number - the file mode. a 3 digit number. See file permissions section
- owner: number - the UID of the owner
- group: number - the GID of owning group
- size: number - file only: the size in bytes
- refcount: number - the reference counter used for hard links
makes a new fifo (named pipe). A named pipe can be opened by one process for reading and for writing by another. all data written to it can then be read again.
Each file has a permission mask. 000 means no access, 777 means full access for all users. The first digit represents owner, second means owner group and last means everything else. The digit is a bitmask with the following values: execute: 1, write: 2, execute: 4. Execute on directories means that you can chdir() to that directory
Capabilities are general purpose permissions. In fact a kernel module can define custom ones. By default root has all capabilities and every other user has none (this can be changed though). A user with all capabilities should not behave any different than root and a if you take all capabilities from root it should not be have any different than a regular user
readAllFiles - allows to read all files on the system bypassing permission checks
writeAllFiles - allows to change all files on the system (dangerous)
chmodAllFiles - allows to change owner and mode of any file in the system (dangerous)
readAllCapab - allows to read capabilities of all processes/users
writeAllCapab - allows to change any users capabilities (dangerous)
changeToAnyUID - allows to change current processes user ids to any valid user id (dangerous)
changeToAnyGID - allows to change current processes group ids to any valid group id (dangerous)
shutdown - allows to shut down or reboot the system
linkDirectory - allows to create hard links to directories
A program that needs extended privileges should never check if it is run as root. instead it should check if it has the needed capabilities