Foreword
Welcome!
Welcome to NightOS' documentation! This book will show you how the system is designed, how it works, and all the little things it can offer to the end user as well as the features availables for application developers.
Before reading this documentation, please note that I'm developing this project on my own and I'm by no mean an operating system expert. I do not even as a professional low-level developer though I have some knowledge on how low-level software and hardware work to some extent.
This means there are surely many flaws in the very low-level aspects, especially in the microkernel, like the way hardware devices are handled and mapped in memory. If you find a flaw, feel free to fill an issue.
The main point of this project is the many middle-level/high-level aspects, not the very low-level ones which will take quite a lot of time to design completely and surely require the knowledge of people more experimented in this field than I am.
On the other hand, specification documents are not like specification documents you may have seen from, let's say, WhatWG or other consortiums. As NightOS is only a prototypal O.S., the documents you will find here are meant to be easily understandable. Specification documents are meant to describe completely a specific concept or part of the system, but without being hard to read. Some will argue it's not close enough to a "real" specification, but I think it's enough for now, regarding the very early state of the project.
Also, this project is far from being finished, so many things are still missing from the documentation. I frequently add new design documents and complete existing ones, but feel free to create a new issue if you think something should be added to the project :)
By the way, you can also find answers to the most frequently-asked questions here.
What is NightOS?
NightOS is an operating system aiming to replace ancient systems like Windows, Linux or MacOS. Well, this is the ideal goal but given how much Windows/Linux/MacOS and other systems are deeply installed in today's computers, NightOS is more of a theorical operating system that shows what we could get if we hadn't to maintain legacy compatibility with ancient architectures.
What? Another O.S.? Why?
The problem with current OSes like Windows, Linux or MacOS is they were built on bad roots. At the time they were created, security wasn't a concern like today, and computers weren't nearly as powerful as they are now. Which means a lot of things we have to deal with today in order to maintain a good level of security or performance isn't met at all with these systems, because they can't just be rewritten from scratch or break compatibility with older pieces of software.
NightOS, on its side, starts from a blank page. There is no compatibility to maintain on the software level, and everything is designed from the start to be future-proof, meaning it will be easy to add new security features for instance, without breaking any software compatibility.
What NightOS isn't
Unlike other operating systems such as Linux which are able to run on a very wide variety of devices, the goal of NightOS is not to be as flexible as this would require to make some big compromises and hugely complexify the system. Here is a list of things NightOS isn't designed for running on:
- Embedded devices
- Multi-CPU computers (although this decision might change in the future)
- Non-64-bit CPU architectures
- Headless devices (such as servers)
What's the project's structure?
The project is composed of three distinct parts: the kernel, the system, and the desktop environment.
The first one is the part that allows the software to access the hardware, like writing files to the disk or making network requests. It's a microkernel, meaning it's easier to maintain and has a reduced attack surface for potential security issues than a monolithic kernel like Linux. In its early stages though, we will use a the Linux kernel as a base, and the microkernel will be last "brick" to the project.
The system is the higher-level component that deals with everything that is not directly related to hardware or graphical interface. For instance, applications, user accounts and permissions are all managed by the system. It's a read-only part the user cannot modify by itself.
The desktop environment, finally, is the part the user sees when launching NightOS. It noteably contains the status bar, the windows manager, the notifications panel, and all graphical-related things.
The goal of NightOS is to provide an operating system that is far more secure, robust and performant than existing systems, and this for all users.
What license is this project distributed under?
The NightOS project uses the Apache 2.0 license.
Special thanks
Thanks to rmRander for providing the image that served as a base for this project's logo. You can find the original picture here.
Frequently-Asked Questions
Welcome to NightOS' FAQ. This document answers to common questions about the project, completing the foreword.
- This project seems too good to be true
- What's the current state of the project?
- Will this project replace Windows/MacOS/Linux/... one day?
- How does this project relates to NightOS v1, v2 and v3?
- Who are you?
- Why did you create NightOS?
- How can I help?
This project seems too good to be true
Well, it is in a way. NightOS can be designed as such a secure and robust O.S. because we chose to not maintain any backward compatibility with existing software - understand that existing applications from Linux, MacOS or Windows will not be compatible with NightOS. This is why we can afford to introduce so many new features and requirements current operating systems cannot.
The problem is that backward compatibility is absolutely mandatory when it comes to creating a general-purpose operating system. All applications would have to be developed again (in fact, they would just have to be adapted for NightOS, but that's still quite a bit of work) in order to run on this system, which is of course unfeasable today.
This is why NightOS will probably never be completed. The current goal of this project is to build a theorical O.S. that demonstrates how good of an O.S. we could make if we hadn't to worry about backward compatibility. When the theorical specifications will be complete, we shall start to develop the O.S. itself but that will require a lot of work beforehand.
Still, we do all we can to maintain as much compatibility as we can. For instance, we are currently investigating how existing Linux applications may be ran on NightOS, with a system call wrapper that would allow them to run correctly on the system, without compromising security, but with lacking features of course.
What's the current state of the project?
The project is currently in its very early stages. You can find more informations about this in the ROADMAP.
Will this project replace Windows/MacOS/Linux/... one day?
No. Absolutely not. Because, as I said in sooner, it's not viable for an operating system today to just get rid of all backward compatibility with existing software, simply because what users are interested in is applications first, and then the system itself - is it secure, stable, performant, and so on.
Imagine someone introducing the perfect operating system, but with almost no application on it. Would you get rid of your Windows/MacOS/Linux/... system for this one? Surely not, at least not as your everyday system.
This is why this project is for now purely theorical. It aims to describe how great an OS could be if we did not have to care about backward compatibility. Changes to see it in a usable state one day are like very low.
How does this project relates to NightOS v1, v2 and v3?
The current NightOS project is very different from its previous versions (v1, v2 and v3). Originally, it only intended to be a robust and secure desktop environment, written in JavaScript. Performances would of course be very bad, but it was more of a challenge than a serious project. Also, the project was meant to be a Linux kernel under the hood to get rid of the "low-level part". I seriously lacked of knowledge about low-level concepts at this point in time.
This project does not use JavaScript anymore, but Rust, which is both performant and very robust to the most common memory-related bugs (unless you use unsafe
code). There is no more "challenge" idea involved in this new version, only the aim to make the best possible (theorical) system.
In terms of similarities:
- The v1 was more of a draft, which had a lot of problems up to its roots. It was a pretty bad version overall, and was created when I did not have a real knowledge about how an operating system actually worked ;
- The v2 was a bit more realistic, but still far from being mature enough ;
- The v3 was more serious and ambitious, but it was still a desktop environment and not designed to be a full operating system, plus it was not designed for optimal performances
Who are you?
My name is Clément Nerma (my last name is a pseudonym). I'm a back-end developer that makes stuff since I'm 10-year old and I love to touch to low-level concepts, like operating systems.
Why did you create NightOS?
I created it as an answer to the frustration modern OSes provided me. All modern systems have many stability and security problems that cannot be resolved easily because they need to maintain backward compatibility with older pieces of software. This is where I had the idea of creating NightOS: an operating system that is both robust (understand stable, with mechanisms to minimize data loss in case something goes wrong) and secure, with many security features available to the average user and some more complex ones for advanced users.
How can I help?
Help is very welcome but there's not many things to help with currently, as the system is still being designed. You can still help by suggesting improvements or fixing an error (whatever it is, a design problem or a simple typo) by submitting an issue.
Project
This folder contains the documentation about the project itself - how is will be developed, maintained, its release schedule, etc. Please note that this part is far from being complete, and many informations will be missing and/or incomplete.
- Roadmap - the project's roadmap
- Development - how the project will be developped
- Hardware requirements - hardware required in order to install and run NightOS
Project's ROADMAP
WARNING: This document is only a draft and as such far from being complete. All informations described here are subject to change anytime.
The project will be cut in the four phases described below.
Conception
The first part is to write conception documents about how the system work, both at a high-level (concepts, features, native applications etc.) and low-level (processes management, kernel design...).
This project is currently in this state.
Validation
Once all conception documents are ready, they will be validated one by one, and frozen. Breaking changes after validation are still possible but should be avoided as much as possible.
Implementation
Once all documents have been validated, the different project's pieces can be implemented.
It may happen that, during implementation, some validated documents get modifications in order to improve or fix some specific points. Each modification will need to be validated in order to preserve the stability of these documents.
Below are described the different phases of the implementation.
Phase 1: Unoptimized kernel
The first piece to be implemented will be the kernel. This is required as the kernel is needed in order to implement higher-level pieces like processes. Still, as not everything is needed right from the start, it will developed with no special matter of performances. In this state, it will be referred to as the name of unoptimized kernel.
Phase 2: Low-level implementation
Once the unoptimized kernel is ready, two implementations will start in parallel:
- Kernel optimization (as the unoptimized kernel will be really, really slow)
- System development
The system development will consist in building the following pieces successively:
- The BIOS/UEFI bootloader
- The system bootloader
- The process manager
- The native process manager
Phase 3: Applications & System optimization
The third phase consists in developing the native applications, which are meant to be used by the end user, as well as optimizing the system in parallel and improving its stability.
Starting from this point, all conception documents are definitively validated, which means they cannot receive breaking changes anymore, only new points/features.
This also coincides with the system's first alpha release, which will allow persons who are external to the project to test it and suggest improvements.
Phase 4: Completion
The last phase's purpose is to ensure the system is in a fully usable state, in other words to stabilize it.
The kernel and system will also need to be properly optimized, and once all these things are done a first beta release will be deployed.
Evolution, Optimization & Maintenance
The Evolution, Optimization & Maintenance (EPM) part is pretty explicit: it's all about improving NightOS' existing features, introducing new ones as needed, improving performances and stability, and fixing new bugs.
This part will last forever, as for all operating systems - or until the project dies.
Project development
WARNING: This document is only a draft and as such far from being complete. All informations described here are subject to change anytime.
Languages
The project will be developped in Rust, with first-class support for this language. API interopability will be assured for TypeScript as well as Python in the future.
Usage of architecture-specific assembly will be reduced as much as possible, being only used in two cases:
- The desired behaviour cannot be reached without assembly (e.g. direct register or stack manipulation)
- Extremely performance-critical pieces (e.g. memory management, context switching, etc.)
Hardware requirements
WARNING: This document is only a draft and as such far from being complete. All informations described here are subject to change anytime.
This document presents the minimal hardware requirements required by NightOS in order to work. This is also means NightOS will work on any computer with these attributes.
- Processor:
x86_64
processor - Memory: at least 256 MB or RAM
- Storage: to be determined
- Graphics card: Required for graphical access (otherwise see BareEnv)
Recommanded configuration
- A modern process with TPM (ideally 2.0 or higher)
Concepts
This folder contains documentation for each high-level concept of NightOS. They are designed to be easily readable and understandable.
- Applications - the way to run software on NightOS
- Libraries - sharing identical behaviours between multiple applications
- Users - sharing a computer between multiple persons
Applications
Applications are the system's way to handle software.
NOTE : This document is only an introduction to how applications work.
How applications work
An application is a set of executable files and resources. They are the only way to execute code, as direct binary programs are not supported.
Any user can install applications, which will only be available from his account. Administrator users can install global applications, which are available to every user.
Installation methods
Applications are installed through an application package via Skyer, the applications manager. There are several installation methods:
- From the store
- Directly from the application's package (sideloading)
- As a volatile application
From the store
Applications can be downloaded from NightOS' official applications store (available via Stellar).
- For closed-source applications, the store only provides pre-compiled programs
- For open-source applications, the store provides both pre-built programs as well as the source code
For the latter, user can choose either to build the program from source, using the appropriated build tools in order to optimize performances, or to simply use the pre-built programs (which is the option by default).
Sideloading
Applications sideloading (installing an application directly from its package) follows strict rules determined by the sideloading mode, which is either "disabled", "store-checking" or "unsecure".
Disabled mode prevents all sideloading ; it's not possible to install applications from their package in this mode. Volatile applications can stil be run.
Store-checking mode allows sideloading but will first make the system check if the application's AID matches an existing application on the Store. If so, it compares the application's signature to the Store application's one. If they don't match, the application is considered malicious and won't be installed.
Note that this mode only works while connected to internet, as the system needs to check the Store to ensure the application is not malicious. If the computer is offline, sideloading will be disabled.
Unsecure mode allows sideloading without any checking, which is highly dangerous as it allows spoofing.
The sideloading mode can be changed in the control center.
Volatile applications
Applications can be also be ran as volatile applications, which means they are not installed on the disk. There are four different methods:
- Fully volatile: the app's data are removed when the application closes
- Session-scoped: the app's data are stored on disk until the system shuts down
- Locally persistent: the app's data are stored within a data file located in the same folder
- Persistent: the app's data are stored in a dedicated folder, also enabling common data between users
By default, volatile applications are ran in locally persistent mode. In this mode, the system first checks if a file with the same name as the application's package but with the .vad (Volatile Application's Data) exists. If so, it opens this file as the application's storage. Then, when the application wants to store some data, it is stored inside this data file.
Note that VAD files are disguised VSF files.
Volatile applications running as persistent do not appear in the applications list and can only be managed through a specific option in the Control Center. Their executable files are not stored anywhere and stay in the application's package, while only their data are stored on the disk. This allows to run the same application several times without losing any data and without worrying about a data file. This also allows to store common data between users.
Note that the store has an option for installing applications as volatile.
Permissions
Permissions allow to finely control what an application can do (or not). See the permissions feature document for more details.
Commands
Application can expose shell commands. Multiple commands can be exposed without any risk of clashing as the command name must be prefixed by the AID first.
For instance, if an application with AID superdev.utils
exposes an get_time
command, the final usable command will be :superdev.utils.get_time
.
This is quite a long name but allows to prevent any clashing between commands. Shell scripts must use imports at the beginning to specify the commands they will use. This also allows to directly spot any missing application required for that script. For more informations on the shell language, check the related specifications document.
Note that, by default, shell prompts (not scripts) will allow to directly use commands such as get_time
in the short form if no other application exposes a command with the same name, for convenience.
Commands work by launching the application with a specific execution context.
System applications
Some native applications are part of the system itself and are called system applications as such. They get a few specific features:
- Access to system-reserved features
- Ability to create system services
- They cannot be uninstalled
System applications cannot be removed in any way, as some of them are critical for the system to function properly.
Native applications which are not system applications can be removed, though.
These applications can be updated independently of the system itself, thus their version may differ from the system itself.
Services
Application can provide services to run at startup. They must be specified in their manifest.
Services run at startup with the usual application's permissions, and services get one process per active user.
Libraries
Libraries allow to share a program between multiple applications.
How libraries work
Libraries allow to share runtime elements to applications. Unlike the latter, they can be installed in multiple versions. This means you can have three versions of the same library installed at the same time on your computer.
When an application needs to use a library, it explicitly indicates the list of versions it is compatible with. The system then gets the related version and provides it to the application.
Libraries cannot perform interactive tasks by themselves, which means no background process, no commands exposure, no permission granting. Applications simply use libraries as helpers to achieve specific tasks.
For more details, check the specifications document.
Users
Concept
Many computers are intended to be shared by multiple persons. In such case, it is useful to separate the data of each user and to prevent other users from accesing another's.
By default, there is two user accounts: the System and the Administrator. They are called virtual users because it's not possible to log in these accounts. Other users (the ones which are created manually) are called custom users.
Each custom user has a dedicated data directory called the homedir, in /home/[username]
, as well as a list of files and directories it can read and/or write. By default, each user gets access to:
- Its homedir in
/home/[username]
; - The mounted periphericals in
/media
; - Its temporary directory in
/tmp/[username]
User permissions level
Each user has an associated permissions level:
- Level 1 (Phantom): the user's data are erased after the computer is turned off ;
- Level 2 (Standard): the default user account type ;
- Level 3 (Administrator): can run programs directly as administrator ;
- Level 4 (Main administrator): administrator that can manage global storage encryption ;
- Level 5 (System): reserved to a specific builtin user that can only be accessed by the main administrator using a special procedure
When a user wants to perform a task it does not have the privileges to, it can (by default) ask to run the task as another user. The other user's credentials are then required.
It's not possible to ask to run a task as system or as administrator as these accounts are virtual and do not have passwords as such, except if an administrator user is tries to run a task as administrator.
Note that there is always one and exactly one main administrator. This user account is created during the installation process, and can be transferred later on to another administrator account.
Dangers of an admin. account
The problem with administrators account is that they can do almost anything (except some very specific things like editing the system's files). They can change system settings, read and change other users' data, and even run background processes at startup. They also have all possible privileges as they can edit their own.
This is why it's extremely discouraged to have two administrator users on the same computer, unless the two accounts are used by really trustworthy persons. As such, a large warning is shown if you try to create a new administrator user.
User Privileges Elevation (UPE)
Users can ask to perform a task with the privileges of another user, such as running a program as administrator. This uses the User Privileges Elevation (UPE) system, builtin the sys::perm
service.
In such case, the program is still run as the current user, but with the privileges of both the running user and the user specified to the UPE.
Running a program with UPE requires to know either the other account's password, or to have an authorization from this user. For instance, admin. users have an authorization to use the Administrator account, without providing any password, although a human confirmation is required.
Each request, successful or not, is logged in the log file at /etc/logs/upe
.
Complexity level
Each user can define a complexity level, which will affect its default settings.
A higher level of complexity will make the system display more informations, give more details about errors that may require some technical knowledge but providing additional features and informations in exchange.
The levels are:
- Beginner: only show basic informations, make the permission popups as simple as possible
- Standard: the default complexity level
- Advanced: shows additional informations, displays some error reports, makes the permission popups more precise and verbose
- Power: shows very detailed informations, displays all error reports, makes the permission popups display exhaustive informations
Users' data encryption
See encryption.
Child and supervised users
Child users are users that are supervised by the parental control. Supervised users are users that are part of a domain.
Groups
Groups are a set of privileges defined by the administrator. Access to some folders or application's permissions (e.g. microphone access) can be restricted to a specific group, for instance. When a user is created, it can be put in a group. It then automatically inherits all of the group's privileges, in addition to his own ones. The administrator can add and remove users from groups with the control panel.
When a user is in a group, the group's privileges cannot be revoked for this user.
User privileges
User privileges indicate the list of actions a user can perform or not. You can find more in the specifications of the sys::perm
system service.
Features
This folder contains explanations about each major feature of NightOS. Each document is designed to be relatively easy to read and understand.
- The balancer - improve performances by balancing processes' priority
- Crash saves - prevent data loss at maximum with crash-proof data saving
- Domains - manage a network of computers
- Encryption - encrypt the whole storage and individual user accounts
- Freeze-prevention system - prevent the system from freezing when all RAM and CPU power are used
- Parental control - manage children access to the computer
- Permissions system - prevent applications and users from doing whatever they want
- Sandboxes - isolate applications to prevent them from harming important data
- Synchronization - synchronize settings between multiple computers
Balancer
The balancer is a kernel feature that allows to get more performances out of most important applications.
How the balancer works
The balancer allows to manage the priority of userland processes - and only them. Here is the list of its features:
- Increase priority of this application: gives a priority of 8 to the process linked to the active window ;
- Give maximum priority to this application: gives a priority of 10 to the process linked to the active window ;
- Set this application with maximum priority: always give a priority of 8 to this application's processes ;
- Suspend/resume this application: see below ;
- Enter performance mode: see below
Application processes suspension
Application processes can be suspended, which is an equivalent of pause where they don't run at all. This is achieved by setting their priority to 0.
A suspended application can then be resumed, and because it was just suspended it will instantly run again, without any data loss.
When a process is suspended, all its child processes are, too.
Performance mode
The performance mode performs the following actions:
- For all userland processes with a non-null priority, set their priority to 1 ;
- For all the processes of the application related to the active window, set a priority of 10.
This makes all other applications running a lot slower, but the current one will run a lot faster. The priority is re-calculated whenever the active window changes.
When a fullscreen application uses more than 50% of CPU in fullscreen, or when it asks for it, an overlay suggesting to enter performance mode is shown.
Performance mode is automatically exited when the related process exits.
Crash saves
Crash saves prevent most data loss caused by a crash, in all applications supporting it.
How crash saves work
Every minute (this delay can be changed in the registry), a notification is sent to all running applications. Applications can then answer with their state data, which should contain every data required for the application in order to be restored to its exact current state later. They may join a title message, which is expected to be their main window's title - if they have ones. They can also answer with a specific message telling they won't give a crash save data during the current collect. The signal will still be sent on the next collect process (e.g. a minute later). A last answer method is with another message telling they won't give any crash save for the running instance. The signal won't be sent again for this instance of the application. This message is most of the time encountered when the application doesn't implement the crash save process.
If the application didn't answer when the next collect occurs, the signal is aborted, but the next one will be sent.
When a crash save has been collected for a given application, it is stored in /home/[user]/appdata/[appname]/crashsaves/[timestamp]_[pid].csf
.
NOTE: Crash saves' intregrity is controlled during the boot process.
All native applications support crash saves.
Domains
Domains are a set of computers running NightOS which are linked to each other. The way a domain's computers behave and interact are defined in the Domain Supervisor, which is only available to the domain administrator user.
It behaves like an extension of the parental control, though they are two completely distinct features and domains offer a lot more features, while being made not to manage a child's access to a computer but to manage thousands of comupters at once.
The concept
Domains enable all features of parental control (without the dedicated application though), as well as the following ones:
- Mount a common storage between computers
- Use remote user accounts for log in
- Restrict access to applications
- Restrict available permissions to users
- Restrict installation and update of new applications
- Manage how applications and the system are updated
- Monitor CPU, RAM and storage usage on all computers
- Get access to every user's storage (unless per-user encryption has been explicitly allowed)
- Get remote terminal access to every running computer
- Get virtual desktop access to every running computer
- Put computers to sleep, hibernation, log out current user, power them off or reboot them
- Start any computer remotely (if the computer does support it, e.g. through PoE)
- Limit disk usage per user (in disk usage percentage or absolute value)
- Limit the session duration per user
- Limit the number of physical and virtual cores per user
- Limit the amount of memory per user
Domain supervisor
The Domain Supervisor is a system application that shows up on any computer that is part of a domain. Only domain supervisor users can see it, but every user can run it through command line (though it will ask for a domain supervisor's username and password).
Here is the list of options the domain supervisor proposes:
TODO
Encryption
Encryption allows to prevent unauthorized access to a data with a password.
- Installation defaults
- Relayed encryption
- Full-storage encryption
- Per-user encryption
- Per-user shared global encryption (USGE)
- Which method to use?
Installation defaults
During installation, a check-up is performed to check if the system supports Secure Boot and if it has a modern TPM. If both are available, it will suggest to enable full-storage
- Both Secure Boot and TPM enabled => relayed encryption
- Otherwise => USGE
Relayed encryption
Related encryption is the combination of full-storage and per-user encryption. It works by encrypting the global storage using the master key, but users' data are only encrypted using their own user key. This avoids double encryption and still remain perfectly secure.
Full-storage encryption
Full-storage encryption encrypts the entire system storage. It is incompatible with USGE (only one of these two can be enabled at a time).
At any time, it can either be enabled or disabled through the the control center.
NOTE: The ability to encrypt the storage globally, change the master password or decrypt the storage is by default reserved to the main administrator, but this privilege can be given to normal administrators, though this is highly discouraged.
Passwordless encryption
The installer runs a quick check-up and if the computer is considered secure enough (which notably requires the presence of a TPM and support for Secure Boot), it will suggest to use a passwordless method. The random key is then stored in the TPM and the bootloader is checked by the UEFI using Secure Boot to ensure it has not been compromised, avoiding possible data extraction in case of the device being stolen.
If this method is selected, the whole installation will be encrypted using that key. On startup, the UEFI will check the bootloader's integrity through Secure Boot, and let it start if valid. The (unencrypted) bootloader will then retrieve the key from the TPM and decrypt the final bootloader, which will handle the remaining boot steps.
A recovery key is generated, which must be backed up somewhere, in case the bootloader asks for it.
Master password encryption
If the computer isn't deemed secure enough or if the user chooses to, a master password will be asked for. This password allows to encrypt the whole installation by using a global password.
On startup, the system will run the (unencrypted) bootloader which will ask for the master password. It will then retrieve the encrypted master key and try to decrypt it using the provided master password. If it succeeds, the boot process can continue.
This is more secure than the passwordless method as it does not depend on the UEFI's Secure Boot which reduces the attack surface, and also prevents cold boot TPM attacks, ensuring that (if implemented correctly) the storage will never be decrypted without the master key.
Per-user encryption
Any non-guest user can also use a built-in system tool to encrypt its data using its own password. This way, the user's data become unreadable without his password, making even administrators unable to read his data.
The encryption/decryption key is generated automatically when the user account is created and stored in the user's profile data (in /etc/sys/users
).
When enabled, the encrypted directories are:
- The homedir at
/home/[username]
- The tempdir at
/tmp/[username]
This feature is enabled by default, but can be disabled by the administrator. Also, domains communicate by default the secret key to the domain's manager.
Per-user shared global encryption (USGE)
USGE is a method used to perform near full-storage encryption, without a master password and without the need of an UEFI / TPM. It is does not prevent modifications in case of physical access to the device, but prevents data from being extracted if the device is stolen.
When configured, it will wait for the user's name and password to be input in the login screen to decrypt a shared master key, which will be used to decrypt the global storage. User's data will then be decrypted using the user key, just like relayed encryption. The main difference between the two being the way the storage itself is encrypted.
This allows to not have to input a master password, not rely on an UEFI's Secure Boot system and prevent cold boot TPM attacks, while still providing near full-storage encryption + per-user encryption for all users.
This feature forces encryption for all users and requires every user to set up a strong password.
Which method to use?
Every method is suited better for a specific type of environment.
If you want the maximum level for security, be the only one to use your computer and enable only full-disk encryption with a strong master password and a strong user password.
If you want to prevent data from being extracted in case of theft, enable relayed encryption or USGE.
If you want to have the most seamless experience, use USGE. You will not be prompted for a master password but still have system and user data encryption.
Freeze prevention
Freeze prevention prevents nearly all computer freezes, by reserving a little amount of resources to the system. It is enabled by default, but it can be disabled during installation process or from the control panel.
How freeze prevention works
The system forbids applications to access a fixed amount of RAM, called the freeze prevention resource (FPR), which is by default the smallest between 1% of the total RAM and 8 megabytes.
When more than 99% of CPU and/or RAM is used, the system puts itself in freeze prevention mode (FPM), which consists in listening to a specific keyboard shortcut (by default, Ctrl+Alt+P
), controllable using the keyboard or the mouse (with a special block cursor, not changeable by applications) which asks user if they want to terminate gently the more resources-consuming application instance (by sending the TERMINATE
signal), to kill it, to kill all instances of the application, or to see the list of running application instances with how much resources they consume, to make a choice on another one.
If developer mode is enabled, another option is added to access a tiny shell containing a set of commands made to list and manage running applications in a more powerful way.
Parental Control
Parental control allows to manage a child's access to a computer running NightOS.
How it works
Parental control works using (at least) two accounts: the parent's account, and the child's user account, which is related to as a child user. Note that multiple child users can be managed using parental control.
The parent account then gets access to an application called "Parental Control", which allows to access all parental control-related features for each child user.
It is also possible to get important notifications by email, and even to install a smartphone application (iOS/Android) which brings the parental control application to phones, allowing to manage child users even when not at home.
Integration
Parental control's settings are available to third-party applications using specific permissions. This way, some applications can provide an additionally layer of security for child users, like preventing access to adult contents in a game store.
Features
Restrict usage hours
This feature allows to restrict usage of the computer by the child user during specific hours in the day. These hours can be set independently for each day of the week, allowing for instance to set specific hours in the week-end. It's also possible to set specific hours for some specific weeks or periods of times (e.g. for four specific days in a row), allowing for instance to set specific hours for vacations.
Child user will not be able to log in outside of these hours. If they are logged in when the upper limit is reached (e.g. only 8 AM to 9 AM is allowed and the child user is still logged in as 9 AM), a warning message will be shown 15 minutes before reaching the time limit, 5 minutes before, and 1 minute before, allowing the child to save its data.
When this final minute comes, a pop-up shows telling the session is going to be closed in 60 seconds, and a cooldown is shown in a closable pop-up balloon in the taskbar. An option is also shown to extend the session by up to fifteen minutes, sending a notification to the parent's phone as well as an e-mail.
The parent can then do nothing and the session will extend for a maximum of fifteen minutes, or reject it and the computer will shut down after 1 minute. Then another option shows to ask if the parent wants to permanently disable time extension.
Session time extension can be enabled/disabled during the parental control installation process (enabled by default). If disabled, the time extension option will not be shown to the child user during the timeout.
Restrict session duration
This feature allows to restrict how much time a session can be active, allowing for instance to limit a child user's usage to one hour per day. Like the usage hours, it can be set independently for each day of the week, for specific weeks, and for specific periods.
Session time extension applies here as well, and works the same way as for restricted usage hours. The related settings are independent though, allowing for instance to enable session time extension for usage hours but not for session duration, or the opposite.
Restrict access to applications
This feature restricts access to some applications, either using a whitelist (listing all the allowed applications), or using a blacklist (listing all the forbidden applications).
It's possible to automatically while/black-list new applications.
Restrict access to websites
This feature restricts access to some websites, either using a whitelist or a blacklist. It's also possible to apply these restrictions only to compatible web browsers. Whitelist is strongly discouraged outside of web browsers-only usage, as it may prevent some applications to work properly.
Whenever an application tries to access a forbidden website, the request will fail with a specific code indicating it's forbidden by parental control. Applications can still enforce this rule but this requires a specific permission that cannot be allowed by child users.
These restrictions are enabled through the built-in firewall, Vortex.
Restrict mature contents
The child user's birth date can be declared during setup, so its age (not its birth date) will be available to every application which asks for it (with a specific permission, which is by default automatically granted to all applications which ask it).
This allows, for instance, for web browsers to block websites that declare themselves as showing adult contents. Or even more precise protection, like a movies store not showing movies whom age limit is beyond the child user's age.
Restrict installation of applications
This feature prevents child users from installing and running volatile applications. By default, installation is allowed but sends a notification to the parent user asking if the child user is allowed to install the requested application ; while running volatile applications is simply disabled.
Controlling session remotely
This feature allows parents to log out the child user remotely. This will show a pop-up indicating the session will be closed in 60 seconds, behaving just like usage hours restriction. It may also provide session time extension based on how the log out was triggered (log out the child user or log out the child user and disable session time extension) and the setting selected during the setup process.
Sandboxes
Sandboxes allow to test an application without applying modifications to the system.
How sandboxes work
A sandbox is an execution mode where an application's modifications to the disk are not applied directly to it, but instead to a virtual drive stored in its sandboxes
folder. When the app. exits, a confirmation overlay proposes to apply the modifications to the real storage - it's also possible to see the changes before applying them.
In developer mode, it is possible to export sandboxes and import them on other computers.
Persistent sandboxes
Sandboxes can also be created as persistent, which prevents them from being removed after the app. exited. Instead, the next time it will run, the same sandbox will be used again.
Puppet sandboxes
Sandboxes can be controlled by another application (this requires an administrator permission, though). This allows to automatically accept or decline every API usage, like permissions or I/O requests. Such sandboxes are called puppet sandboxes, and they can be especially useful for testing.
Synchronization
Online backup and synchronization is performed through Cloudy.
Technical
This folder contains technical documents which essential describe low-level parts of NightOS.
A global overview of the system can be found in the overview document.
- The controller - permissions management system
- Developer mode - enable powerful development options
- File formats - description of all native file formats
- I/O manager - manage input/output requests
- Inter-process communication - communication between processes
- Multi-platform management - how the NightOS ecosystem can be used on other operating systems
- Performances - system tweaks used to optimize general and specific-case performances
- Pre-compiling applications - pre-compiling applications to improve installation time and size
- Processes - low-level view of how code runs in a concurrent way
- The registry - configure the system's behaviour and features
- Services - special processes that run in the background and allow other applications to perform specific tasks
- The shell - the de-facto way to run complex and/or automatized tasks on NightOS
Overwiew
This document presents a global overview of NightOS. It is only a simplified representation of the system, but allows to better understand its architecture and organization.
The referenced documents are mostly technical overviews themselves, which is why links to their associated specification document are also provided (marked as (specs)).
Note that this is only an overview, and as such many topics will not be covered by this document. For more informations, refer to the global document.
- Program executions
- Permissions
- Hardware access
- Hardware drivers
- Hardware access performances
- Filesystem access
- Data loss prevention
- User interface
- Users management
- Children protection
- External security
- For developers
Program executions
NightOS doesn't allow to run standalone binaries. Every running code is either:
- Part of the system, as the kernel (specs) itself or as a system service (specs).
- Part of an application (specs)
System services are immutable and run for the entire system's lifespan, while applications can be opened and closed at anytime, and can also run as multiple instances in parallel.
Permissions
While the system has every right on the system itself, applications are restricted and can do almost nothing by default.
Capabitilies can be granted through permissions (specs), which are designed to allow precise control on what an application can do or not, while requiring as few user interactions as possible.
Hardware access
Hardware access is performed through two layers:
- First, the kernel detects and enumerate (specs) hardware devices
- Then, hardware is accessed through the hardware service (specs)
Hardware drivers
Unlike most operating systems, hardware drivers are simple applications with no specific integration in the kernel.
Any application can register itself as a driver (specs) using the hardware service (specs).
The relevant driver for each hardware device is selected using various criterias (specs).
Hardware access performances
Hardware access is performed through syscalls (specs) and signals (specs), which use CPU interruptions.
The access process is often:
- A userland process notifies a system service
- The system services contacts the hardware service (specs)
- The hardware service contacts the relevant (specs) driver (specs)
- The driver performs the requested task by communicating with the hardware through the hardware service
- The action's result is then transmitted to the hardware service, which then transmits it to the system service, which in turns transmits it to the userland process
Although this process can seem a bit long, CPU interruptions and forced threading (specs) makes the theoric latency low enough for intensive use.
Filesystem access
Accessing and managing filesystems and their content is faster than common hardware operations thanks to direct storage access (and direct driver access for worst-case scenarios).
The typical process involves communication with the filesystem service which communicates directly with storage devices.
For filesystems that aren't natively supported, a filesystem interface is involved to communicate with the storage driver service, which then communicates with the hardware service. This involves a higher latency, but is only limited to edge cases and remains in an acceptable range of performances.
Data loss prevention
Data loss prevention works essentially through crash saves, which ensure applications' state is regularly saved on disk in case something goes wrong.
User interface
The user interface is entirely managed by the desktop environment, which can be any application exposing the relevant service (specs).
Users management
Each person using a computer can have its own user account.
Children protection
Children can get their own user account with parental control.
External security
External security is guaranteed through by-default encryption, and additional per-user encryption.
For developers
Developers can access additional features through the developer mode.
Controller
The controller is a system library that manages permissions of processes.
It is concretely represented by the sys::perm
service.
Notion of scope
Permissions are split into several scopes:
- The application scope contains the permissions a given process is borned from ;
- The user scope contains the permissions the user who launched the application has ;
- The mode scope contains the permissions the execution mode (either system or userland) has.
The mode scope restricts the user scope, which itself restricts the application scope. This means that, if the application scope specify a permission that is not covered by the user scope, it is not applyable to the process. This allows to prevent applications and users from getting too high permissions.
The mode scope prevents applications from performing harmful tasks such as writing the system. Only system applications, which run in system mode instead of userland mode, gets an unrestricted mode scope.
The perm
system library
The perm
system library is an interface for the controller which allows processes to check their own permissions, ensure they can make I/O requests before effectively making them, and extend their permissions (see below).
Permissions extension
A process can, at any moment, send a permission extension request (PER) using the perm
library. It allows to gain a new permission by showing an overlay the user can accept or decline. If the permission is accepted, the requested permission is added to the process' one - and sometimes to the application's one.
If the requested permission is out of its maximum scope (e.g. asking for write access to /etc
while being ran as standard user), the request is rejected.
Developer mode
The developer mode enables several features that are not available to default users. Only administrator users get access to them when they are enabled.
How to enable
Developer mode can be enabled either by typing the following command in the terminal:
adm central --enable dev-mode
Or, while holding the Ctrl
Alt
and Maj
key, type DEV
(three letters).
A warning dialog will appear. If you confirm, it will display an UPE dialog for the administrator account. Then a final confirmation dialog will ensure you're sure about what you're doing. Then development mode will be enabled (no reboot is required).
Features
- Additional features will appear in the control center (see the options)
- The registry can be imported and exported
Application proxies
Application proxies are applications that intercept all applications calls to the system on-the-fly. It is useful for applications debugging.
When a proxy is set up for an application, all system calls sent to this application will be transferred to the proxy, which will be able to do whatever it wants with it. It's possible for the proxy to never answer the signal, to change its content before actually sending it to the system, etc.
Basic usage of an application proxy is as a "listener": it only listens to specific signals and logs them, without cancelling them and/or modifying them. This is useful to check all filesystem access requested by an application, in real time.
Devices
- Connecting a new device
- Interacting with a device
- Device handler files persistence
- Custom device handler filename
Connecting a new device
When a new device is connected to the computer, a device handler file (DHF) is created in the /dev
directory (see filesystem structure).
Depending on its type, it will be put in a different directory, as shown in the above document. Note that new category directories may be introduced later on.
The filename itself is a random unique identifier made of a single lowercase letter, a digit, another lowercase letter and finally another digit.
For instance, if a hard drive is connected to the computer, the DHF may be something like /dev/sst/f5t2
.
Interacting with a device
Device handler files are not simple files ; they can only be used through the sys::hw
service.
Different actions may happen depending on the device's type:
- Camera devices: when an application asks to capture a photo/video, the device will be suggested to take the images from
- Microphones: when an application asks to capture sound, the device will be suggested to capture the sound from
- Sound output devices: the device will be available for playback
- Network adapters: the device will be available to make network requests on
- Other supported wireless devices: depends on the type (Bluetooth adapter, ...)
- Basic/persistent storage devices: when possible, the device will be automatically mounted in
/mnt
and visible in the files explorer
There are many different types of devices, all can be found in the specifications of the sys::hw
service under the "Driven device type" (DDT) section.
For uncategorized devices (in /dev/etc
), a popup is shown to the user, to indicate the connected device is not recognized. Some applications may still be able to interact with it (for instance, a storage device using an unsupported protocol).
Device handler files persistence
When a device is connected, its DHF is not removed. Instead, if a process tries to interact with the DHF, the sys::hw
service will indicate the device is currently not connected.
When the device is connected again, it is associated to the same DHF again. This allows applications to persist the device's location and use it later on.
Custom device handler filename
It's possible to give a custom handler filename for intuitivity. In such case, a new DHF is created in the related directory, but the old DHF is kept anyway. This allows to not break compatibility with applications that rely on the old DHF.
The two DHF will point to the exact same device, without any difference. The custom DHF can be removed anytime, although this is discouraged as applications may rely on it.
Also, custom DHF names must always be longer than 4 characters, to avoid confusion with automatically-generated DHFs.
File Formats
This document presents the file formats natively handled by NightOS. Some files are only handled by optional first-party applications that may not be installed on the computer, so some formats may not be supported in specific installs where such applications have been removed/were never installed.
Common formats
Common file formats are natively handled:
- Text files (
.txt
,.md
, ...) with Gravity - Audio files (
.mp3
,.flac
, ...) with Sonata - Image files (
.png
,.jpg
, ...) with ShootingStar - Video files (
.mp4
,.mkv
, ...) with Milkshake - Archive files (
.zip
,.tar
, ...) with Blackhole - E-book files (
.cbz
,.cbr
, ...) with Reader - E-mail files (
.eml
,.vcf
, ...) with Postal - Web files (
.html
, ...) with Rocket
Virtual storages
Virtual storages are files that contain one or several virtual filesystems. Different filesystems can be put in, but all must be supported natively by NightOS in order to properly work without needing to install any additional application.
The filesystems can be encrypted individually. The whole storage can also be encrypted. They can also be compressed.
They have multiple purposes: store encrypted files, virtual filesystems for sandboxed applications, etc.
Virtual storage files have the .vsf
extension and are opened using Blackhole.
Application packages
Applications can be installed from standalone files called application packages.
Installable applications have the .nap
(NightOS Application Package) extension, while volatile applications have the .nva
(NightOS Volatile Application) one.
They are opened using Skyer.
System updates
The system is intended to be updated through the update section in the settings, but sometimes it may be required to install updates offline for specific reasons (e.g. no network connection available at the moment). In such case, it is possible to download incremental updates as system update files.
They may contain one or several updates, and are only installable on a very specific version of the system, to avoid missing some other incremental updates.
System update files have the .nsu
(NightOS System Update) extension and are opened using the control center.
Application updates
Applications are intended to be updated through the update section in the settings, but sometimes it may be required to install updates offline for specific reasons (e.g. no network connection available at the moment). In such case, it is possible to download incremental updates as application update files.
They may contain one or several updates, and are only installable on a very specific version of the application, to avoid missing some other incremental updates.
Application update files have the .nau
(NightOS Application Update) extension and are opened using the control center.
I/O nano-manager
The Input/Output Nano-manager, formerly known as Ion, is a part of the system which treats input/output requests from processes.
It is concretely represented by the sys::hw
service.
Hardware access
When a process tries to access the hardware, it must go through Ion, which will allow it or not to interact with the desired device.
System services such as sys::fs
or sys::net
use Ion to deal with the related hardware devices.
Agnosticity
Ion only permits agnostic access to hardware, meaning it does not have knowledge of the performed action (filesystem access, sensor reading, ...). It can only be accessed by drivers.
Non-agnostic access can be performed through various services, such as sys::fs
.
Requests priority
Requests are treated by priority, which is made both of its arrival timestamp (first one, first out) but also of the process' priority: a process with an higher priority will see its I/O requests treated more quickly.
Multi-platform
NightOS programs can be ran either on Linux, Windows or MacOS systems using a wrapper called the NightOS Calls Wrapper (NCW). It can be downloaded from NightOS' official website.
When installed on a system, it allows to use NightOS applications natively, although permissions management will not be as smooth and many options will not be available (e.g. no desktop, no access to native NightOS applications - only native libraries, etc.).
Performances
Cycle and phases
Because processes' instructions cannot always be processed parallely, the system will let each application run a fixed amount of instructions, before going to the next one. This short period of time is called a phase and generally lasts a few milliseconds. The treatment of a single phase for all running processes is called a run cycle. When a process isn't in a phase, it is "paused" until the next one.
Processes' priority
Each process is started with a given priority, which is a number from 0 to 10, which can be modified during its execution. The more priority it has, the more instructions it will run in a single phase.
A priority of 0 prevents the program from running any instruction at all. In this case, only an external process will be able to increase the priority to let the program run again.
By default, a background process gets a priority of 2, a visible process gets a priority of 4, the process linked to the active window gets a priority of 6, and the process linked to a fullscreen window gets a priority of 7.
When a process asks to set its priority to 9 or 10, a confirmation overlay is shown.
Balancing performances
See the performances balancer.
Pre-compiling
On traditional operating systems, programs are provided as binary and so are only available for one specific platform (Windows, Linux, MacOS, ...) with a specific architecture (x86, ARM, ...).
In order to simplify distribution, and to bring more stability and security to native programs, NightOS programs are shipped as NightOS Pre-compiled Programs (NPP) using a specific language called CommonAssembly.
How it works
CommonAssembly is very similar to WebAssembly: compressed very low-level source-files that are built ahead of time on the machine that will run it. This enables several advantages:
- Programs are multi-platforms, multi-architectures
- Programs run faster thanks to being optimized for the specific machine they will run on
- Programs are still very fast to compile
- Source code is protected for closed-source applications as CommonAssembly is made of basic instructions
- Better security thanks to programs being checked ahead-of-time
Compatibility
CommonAssembly can be ran on other operating systems than NightOS using a lightweight wrapper available on NightOS' official website.
NOTE: While CommonAssembly is multi-platform, NightOS applications take advantage of NightOS' features like more powerful signals that are not natively available on other systems. In order to run such applications, you must install the full wrapper which is available on the same website.
Usage
Open-source and closed-source NightOS applications usually bring pre-compiled versions of themselves in order to fasten a lot the installation process, and to get rid of the need of having the heavy build toolchain installed on the target computer.
This allows to install applications really fast, and to optimize them for the current machine.
Processes
WARNING: This document is far from being complete and may lack a good description of what processes are, and/or the features they offer.
NightOS processes are implemented a bit like Linux' ones, with additional features. There are several types of processes:
- System processes, which are created by the system ;
- Application processes, in which applications run ;
- Worker processes, in which applications' workers run
The base and system processes are called low-level processes, while application and worker ones are called userland processes.
You can find the implementation details of processes in the kernel document.
User privileges
Each process is ran as a specific user, which determines the maximum allowed scope for controller requests, and with a list of initial privileges (the ones given to the application).
Child processes
A process can create child processes (it's called a fork). The child process will roughly be a 1:1 copy of the parent process, but with its own unique PID.
To avoid copying the whole memory, copy-on-write is used, which means that pages are identical and require no allocation until they are modified, in which case a new page is created and attached to the process in place of the original one.
Child processes automatically inherit their parent's permissions.
When a process exits, all its child processes are immediatly killed. It's up to the process to ensure its children are properly terminated before it.
Threading
A process can create threads, which are still a part of the process. Threads allow to run multiple part of a process concurrently, as the kernel may run several threads in different processor cores.
All threads share the same address space and memory, although they also have a reserved space called the thread-local storage.
To avoid copying the whole memory, copy-on-write is used, which means that pages are identical and require no allocation until they are modified, in which case a new page is created and attached to the process in place of the original one.
Also, the stack is local to each thread.
Threads work as a hierarchy ; when a thread creates another, it is called the new thread's parent, while the new thread is its child. When a thread terminates, all its children are instantly destroyed.
Main thread
When a process starts, its instruction run its main thread. Due to threads being hierarchised, exiting the main thread will result in all other threads being closed immediatly, which is why the main thread should always first terminate its children properly to ensure all data are synchronized for instance.
Thread-local storage
Threads have a reserved portion of memory in their address space called the thread-local storage (TLS).
It is split into slots, which can contain arbitrary amount of data.
Each slot has a unique identifier, which can be used across threads to access TLS data from any thread of the process.
Automatic permissions inheritance
When an application process gets a new permission, all other processes from the same application inherit it, unless this permission is granted only for this instance of the application.
Registry
The registry contains all system configuration.
Format and content
The registry's format and content can be found in the specifications document.
Debugging
In developer mode, the registry can be exported to the HFRR format and imported back.
Services
A service is a process that launches at startup and may receive IPC messages from any process.
System services
System services are native applications. They have system permissions and as such are allowed to perform any task. Their purpose is to allow processes to perform specific actions like permissions management or filesystem access without hunging up the kernel.
Each system service is a system application, exposing a service. Most also expose command-line tools.
You can get more details about how services work in the specifications document.
You can find the list of system services in their specifications directory.
Application services
Application services, also called userland services, are provided by applications themselves.
Shell
The shell, called Hydre, is the part that interprets scripts.
Commands
Hydre works using commands, which can take arguments. When running an application's exposed command, the said application is run in a new process with a specific execution context.
The special thing about running an application from one of its exposed commands is that its CMDIN, CMDUSR, CMDMSG, CMDERR, CMDOUT and CMDRAW pipes are exposed to the caller.
You can find more informations about how a process can interact with a running command in the IPC documentation.
Pipes
When a command is run from Pluton, the command process' pipes are handled as follows:
- All user inputs are sent to the CMDUSR pipe
- All messages sent through CMDMSG and CMDERR pipes are printed as they are received by the shell
- The command's return value (from CMDOUT) is printed after the command completes
- Data written to CMDRAW is not shown, as they don't have to be UTF-8 encoded, or even to be a string. They can be redirected through pipes, though
Specifications
Hydre's specifications can be found in the related document.
User Experience
This folder contains technical documents about user experience.
They only act as drafts and may change at anytime. They mainly contain global ideas or technical overview for some parts. Some documents may also contain specifications.
Documents
Desktop Environment
The Desktop Environment (DE) is an application that acts as the main graphical interface of NightOS.
It is launched with the system, does never stop until the system itself stops, and handles most of the user interface.
Desktop Environment Applications
A desktop environment application (DEA) can only be launched by the system, and is only launched once during the whole system's lifetime, except for their commands which can be launched by anyone. It is also the very first application to be run.
There are two default DEA: Nova, and BareEnv.
The former acts as the default, reference and recommanded DE of NightOS, while the latter is for servers and other computers without a graphical output. It only supports text-based workflows, which limits it to command-line usage.
An application can register itself as a DEA by exposing a SYS_DENV
service in its manifest.
Permissions
A DEA does not require specific permissions in order to work ; it only requires to expose a specific integration service in its manifest.
Display layers
The graphical interface is split into layers, which represent the display priority when two elements overlap. For instance, when two elements A and B overlap visually (= part of them is displayed at the same coordinate than the other), the element located in the highest layer will be displayed on top of the other element.
Layers are indexed, starting from 0
with the lowest priority.
Windows
Windows are the base display element for applications. They can take any form depending on the DE's display method, but are usually made of the application's drawing zone and a titlebar.
Some desktop environments may not accept windows (for instance, BareEnv which is a text-only desktop environment). In such case, most applications will fail to run as they won't be able to build windows, but this is still allowed.
Security and operation
Unlike other operating systems, DEA can only access their own data and cannot manipulate the data inside other applications, even graphically.
Desktop environments rely on the sys::ui
system service to control the data they display.
This service provides them several methods and events, mainly to:
- Display graphical elements on layer 0
- Display the content of a window at provided coordinates
- Notify a window owner that its window should be closed
- Notify a window that a part of its state should change
- Reject a window's state change
- Be notified when a window's state changes
At no moment can the desktop environment access the graphical content of a window, nor affect it, as windows are displayed starting from layer 1.
On the other hand, the desktop environment can force a window owner (the application process that created it) to resize, minimize, restore, put to fullscreen, etc. and can also hide some elements from the system as it is not forced to display a window's content.
Window states
A window's state is made of its characteristics:
- Title
- Buttons
- Icon
- Dimensions (width / height)
- Coordinates (X and Y)
- Active attribute
- Interactive attribute
- Fixed-size attribute
- Display layer
- Display state
- Custom state attributes
The custom attributes are handled by the DEA itself, and can contain any piece of information relevant to displaying the window, such as the virtual desktop number if the DE supports it.
Window display state
Windows have a display state which indicate how they are displayed on the screen. These states can be:
- Restored: the default
- Maximized: the window takes all the available space, minus the space used by its optional titlebar and the elements displayed by the desktop environment
- Minimized: the window is hidden but still available
- Fullscreenized: the windows takes all the screen, hiding all other windows and elements from the desktop environment, including its own titlebar
Active/inactive windows
All windows are considered inactive unless they are focused, which on most desktop environments put them on the top layer. There are no specific difference between active and inactive windows, but this attribute is indicated to the owner application as this may impact the desired behaviour of the application.
Interactive windows
By default, windows are interactive, meaning user inputs can be sent to them. They may be set non-interactive to stop receiving such events and preventing users from interacting with them.
Fixed-size windows
Fixed-size windows are windows whose size is fixed and cannot be resized by the end user.
Common elements
Desktop environment are traditionally made of several elements:
- The taskbar lists all opened windows
- The notification center centralizes all pending notifications
- The titlebar is an element put on each window to see its title, icon and control its state
These elements are common but are NOT required to be present in a desktop environment.
Interface windows
Interface windows are windows that are created by another application to be used in a specific part of the desktop environment. It results in the integration of an application with the current DEA.
Interface windows are created the same way as usual windows, but do not usually have a titlebar and can often not be moved. They are mostly used to create widgets and display context menus on items.
Typically, an interface windows created by an application is hidden by default, and the DEA shows it only after getting its identifier returned by the service it is communicating with.
Popups
Popups are special windows which are not drawable directly by the application. They usually contain a single icon, as well as a message and at least one clickable button. They are used to transmit informations to the end user, such as an error or a warning message.
Notifications
Notifications are the way for both the applications and the system itself to indicate when an event happens which the user should be notified of, or to indicate a persistent state.
Structure
A notification is made of:
- A title
- A description
As well as optional fields:
- A description
- An icon (by default the icon of the application)
- A notification sound (by default selected by the system)
- A shaking effect
- A background color (by default chosen by the desktop environment)
- A set of buttons
- A cover image
- A persistent state
- A type-dependent set of data
- A timeout
- A handler called when the notification is clicked
- A context menu when the notification is right-clicked
Display rules
The way notifications are displayed depends on the desktop environment, therefore the requested shaking effect, background color, and so on may not be respected by the DE, although it's highly recommanded for a DE to follow these attributes.
Also, there is no rule indicating how elements are visually arranged inside a notification. Some DE may for instance put the icon on the left, while some others on the right. Additional elements may also be displayed, such as a cross to close the notification or an arrow to hide it to the notifications center.
Cover image
A notification can request to display a large image. Some desktop environments may decide to hide the application's icon in such case.
Buttons
Notifications can have action buttons, which are then linked to the owner application's event system. Buttons are usually limited in size and colors.
Persistent notifications
Persistent notifications don't disappear after a timeout. They can for instance be used to indicate a continuous state like the progress of a task.
Notification types
There are several notification types, which their own required sets of data.
Free notifications
This is the default type of notifications, which has no particular constraint.
Multimedia notifications
Multimedia notifications are used to indicate a multimedia playback. It must be composed of a cover image and six buttons (previous, rewind, play/pause, stop, fast-forward, next) whose appearance is decided by the desktop environment.
They are always persistent, and disappear when the playback stops completely.
Sound
This document describes how audio is implemented in NightOS.
Audio inputs
Devices recognized as audio inputs can act as global audio input devices. Multiple devices can be used at the same time, but only one default input can be selected.
By default, each time a new audio input device is connected, it is selected as the new default device. When disconnected, the previous default device is selected again.
It is also possible to set a device as always being the default one, even if other devices are connected afterwards.
Playback devices
Devices recognized as audio outputs can act as global audio output devices. Multiple devices can be used at the same time, but only one default output can be selected.
By default, each time a new audio input device is connected, it is selected as the new default device. When disconnected, the previous default device is selected again.
It is also possible to set a device as always being the default one, even if other devices are connected afterwards.
Multimedia playback
When a sound is played, the process must specify the type of the audio resource, which is either "standalone sound" or "multimedia sound". In the second case, a multimedia notification is emitted, which can then be controlled by the application but cannot be closed while the media is playing.
Specifications
This folder contains specification documents, whose purpose is to describe fully a specific concept or feature. Think of these as reference documents - they are not meant to be easily understood without a solid knowledge of how NightOS works. For more informations about low-level concepts these documents refer to, you can check the technical documents first.
- Applications and libraries - document describing common things between applications and libraries
- Applications - how applications are structured and behave
- Libraries - what are libraries
- Filesystem - how the filesystem works
- Storage permissions - how permissions are managed on filesystem elements
- The boot process - how the system starts
- Update processes - how updates are performed
- Containers - process isolation system
- Permissions - list of applications' permissions
- The registry - exhaustive specification of the registry's content
- Vocabulary - the list of NightOS-related terms
- The shell - how Hydre works
- Shell scripting - Hydre's scripting language
- Crash saves - what are crash saves and how they are handled
- Kernel - complete specifications of the kernel
- Services - specifications about services work and behave
- Translations - how content translation is handled
Integration services
Driver services
System services
sys::fs
: filesystem managementsys::fsh
: high-level filesystem managementsys::hw
: hardware communicationsys::perm
: permissions managementsys::net
: network communicationssys::crypto
: cryptography utilitiessys::crashsave
: crash saves managementsys::hydre
: Hydre shell servicesys::ui
: user interface servicesys::app
: applications management servicesys::process
: processes management service
Applications and libraries
This document describes the structure, features and behaviours common to both applications and libraries.
Name and slug
Each application has a name as well as a slug. The name can any valid UTF-8 string, while the slug must respect several rules:
- Only lowercase letters, underscores and digits
- Must not start by a digit
- Must not be the name of a native shell command
- Must not be the name of a native shell function
- Must not be the name of a shell type
By default, the slug is auto-generated from the name, but it can also be customized.
Application Identifier
From the slug is generated the Application's IDentifier (AID), which is prefixed by the developer's identifier (DID) specified in the application's manifest (it must match the publisher's identifier on the store), and followed by two double points. The DID is submitted to the same rules as the application's slug.
For instance, an application with a slug of utils
made by a developer whose DID is superdev
will get an AID of utils::superdev
.
The AID is unique across the store as well as in a single NightOS installation. As malicious application may provide the DID and the slug of a legit application (which is called AID spoofing), sideloading is verified by default.
As AID are text-based and can be quite long (up to 512 bytes), programs can instead use the Application's Numeric IDentifier (ANID), which is a 32-bit unique number randomly generated by the system to refer to this particular application. On two different systems, the ANID of a given application will likely be very different, and so cannot be guessed. It is provided by the system during the application's launch through its execution context.
System applications are registered under the reserved sys
DID.
Features
Applications as well as libraries can also declare features, which are opt-in parts of the program. They are described in the manifest and can be enabled or disabled when installing the program.
Each feature allows to embed additional assets and declare additional supported languages (e.g. language packs), and a specific build option is provided to the build tool to indicate at build time weither a feature is selected or not.
This allows to save space and build time when a specific feature isn't required, but should still be embedded in the program itself instead of making an additional application or library.
When the manifest declares one or more feature(s), it must also specify a set of default features to enable, which are selected by default.
Extensions
Applications and libraries can have extensions, which are additional content that may or may not be downloaded and installed alongside the original application/library.
If we take the example of a file browser, an extension could be an image previewer with support for many image formats, which would be unreasonable to put in the original application given most people won't use it. In a media player, it could be a set of additional codecs.
Extensions can contain executable code, but they must be run from within the original application/library only. They do not appear in the list of installed applications visible to the end user, and are instead located in a sub-menu for the application/library they belong to.
For apps and libs downloaded from the Store, extensions can be downloaded from within the original content by calling a dedicated API.
Translations
Applications' and libraries' name, description, content, etc. can be translated using translation files. The content itself can be handled in any other manner, e.g. using a translation framework, but the metadata must use a translation file so the system does not have to run code in order to perform the translation.
The manifest
A manifest is a file describing the application or library. It is mandatory to build and distribute it. Its content differ for applications and libraries, but there are common fields (REQ
: required, OPT
: optional):
# [REQ] Informations about the application
infos:
# [REQ] Application's name, as shown when installing it
name: Cloud Notepad App
# [REQ] Application's slug, as stored on the disk in the /apps directory
slug: cloud-notepad-app
# [REQ] Application's description, as shown when installing it
description: A notepad application that allows syncing your files in the cloud
# [REQ] Application's version, following semantic versioning
version: 1.0.0
# [REQ] Application's authors
authors:
- name: Clément Nerma
email: clement.nerma@gmail.com
# [REQ] Application's icons (in the archive)
# [REQ] "%{}": either 16, 32, 64, 128 or 256 pixels (icons must be square)
icon: assets/icons/app/%{}.png
# [REQ] Application's license (must be in a list of available licenses)
license: Apache-2.0
# [REQ] Application package's content
content:
# [REQ] Program
program:
# Packages can either contain source code only, pre-compiled programs only, or both
# <for source packages> [REQ]
source:
# [REQ] Build tool (must in the list of the toolchain's supported build tools)
toolchain: rust
# [REQ] Required build tool-related options
build: {}
# [OPT] Build tool-related options
options:
optimize: O3
# <for precomp packages> [REQ]
precomp: main.npp
# [OPT] Assets
assets:
# Set of path to assets
- ./assets/
# [REQ] Application's dependencies
dependencies:
# [REQ] Required libaries
libraries:
sysver: ^1.0.0 # Any stable version
# [OPT] Required fonts
fonts:
fonts:open-sans: true # Any version
# [OPT] Features
features:
# Describe each feature, by name
testing:
# [OPT] Additional assets
assets: []
# [OPT] Feature's dependencies
dependencies: {}
# [OPT] Required extensions
extensions: {}
# [OPT] Additional languages (e.g. language pack)
languages: []
# [OPT] Extensions
extensions:
# Describe each extension, by name
test:
# [REQ] Display name
name: Test
# [REQ] Description
description: A test extension
Applications
This document describes application-specific structure and behaviour. For common reference, see the applications and libraries document.
Application package
Application packages are files that have either the .nap
(NightOS Application Package) or .nva
(NightOS Volatile Application).
Content
NAP and NVA files are ZStandard archives which only requirement is to contain, at the archive's root, a manifest.toml
file describing the archive itself, a hash.md5
ensuring the archive has not been corrupted.
They are built around a manifest which can be found below.
Pre-compiled applications
By default, and if possible, the system will always try to install pre-compiled programs from applications' package. If the pre-compiled programs are not available, it will be built from source code - which takes a lot more time.
Libraries embedding
Although it's a better practice to split applications and libraries into different packages, sometimes it's more easy to embed both in the same package, especially in two cases:
- When the application is just a thin layer ahead of the library (e.g. CLI tool)
- When the library's API changes rapidly and the application relies on it
For such scenarios, it's possible for an application package to embed one or more libraries, and publish them all at once.
The application's and libraries' version may differ if required.
If an another application or a library specifies one of the embedded libraries as a dependency, only the said library will be installed, not the application.
Values encoding
The application's startup arguments and output value use the following encoding:
- The return value's length (8 bytes) ;
- The value's shell type code (see the table below) ;
- The encoded value (see the table below)
Type code | Type | Description | Representation |
---|---|---|---|
0x00 | void | Void | nothing |
0x01 | bool | Boolean | 1 byte, 0x00 = falsy, 0x01 = truthy |
0x02 | int | 64-bit signed integer number | Two's complement |
0x03 | float | 64-bit signed floating-point number | IEEE 754 |
0x04 | char | UTF-8 grapheme cluster | Character's length (8 bytes), followed by the UTF-8 grapheme cluster |
0x05 | string | UTF-8 string | String's length (8 bytes), followed by the UTF-8 encoded string |
0x06 | list | Typed linear list | Type code of the list's number of items (1 byte), length in bytes (64 bits), encoded items |
0x07 | path | Filesystem path | Represented as an UTF-8 string |
0x08 | command | Shell command | Represented as an UTF-8 string |
0x09 | stream | Pipe RC | RC identifier (8 bytes) |
The type code is present to avoid misinterpreting the value in case the command returned a value of the wrong type.
Returning and failing
The value must be returned using the CMDOUT pipe. The data sent through this pipe must follow the above encoding.
A command may also fail. To indicate so, the process must send the 0xFF
value through the pipe, and the shell will consider the command as failed (but not invalid, so the process won't be abruptly killed).
Volatile applications
Volatile applications cannot expose commands globally as they are technically not installed.
They can though be used in shell scripts through volatile imports.
Application manifest
Here is the specifications of the application manifest, in additional to the common manifest:
# [OPT] Event triggers
events:
# [OPT] Should the application start just after being installed?
postinstall: false
# [OPT] Should the application start just before being uninstalled?
preuninstall: false
# [OPT] Should the application start just before being updated?
preupdate: false
# [OPT] Should the application start just after being updated?
postupdate: false
# [OPT] Exposed commands (see the related document for additional informations)
commands: {}
# [OPT] Does the application expose services?
services:
# [REQ] Does the application expose a main service?
main: false
# [REQ] List of scoped services
scoped: []
# [REQ] List of integration services
integration:
# [OPT] Desktop environment service
desktop_env: false
# [OPT] File manager service
file_manager: false
# [OPT] Filesystem opener service
fs_items_opener: false
# [OPT] Filesystem interface
fs_interface: false
# [REQ] List of driver services
driver:
# [OPT] Storage driver service
storage: false
# [REQ] System features
sysfeatures:
# [REQ] Does the application support crash saves?
crash_saves_support: true
# [OPT] Additional informations
additional:
# [OPT] Available languages (in a list of existing languages)
languages: ["en-US"]
Commands
Applications can expose commands through their manifest.
The format is the same as the shell's command typing, although adapted to YAML:
- The
pos
ordash
indicator is turned into a (required)syntax
option - The
optional
indicator becomes a boolean that must be set totrue
- The
void
type is forbidden
Example
# [...beginning of the manifest...]
commands:
say_hello:
help: "A program that repeats the name of a list of person"
author: "Me <my@email>" # Optional
license: "MIT" # Optional
return: void
args:
# Declare a positional argument named 'names' with a help text
names:
syntax: pos
type: list[string]
help: "List of names to display"
# Declare a dash argument named 'repeat'
repeat:
syntax: dash
type: int
short: r
long: repeat
optional: true
# Return the time this command took to complete
get-duration:
syntax: flag
short: d
long: duration
$if:
cond: present()
return: void
# [...end of the manifest...]
Execution Context
An application's execution context is a piece of data that is provided when the application starts.
It indicates why the application was started and so what it is supposed to do.
Startup Reason
The most important information is the startup reason, which indicates why the application was started.
It is one-byte long, and is made of the following bits (starting from the strongest):
- Bits 0-3:
0x1
: the application was started as part of its post-installation process0x2
: the application was started as part of its pre-update process0x3
: the application was started as part of its post-update process0x4
: the application was started as part of its pre-uninstallation process0x4
: the application was started by the system as an application service0x5
: the application was started by the desktop environment0x6
: the application was started by itself (from another process of the same application)0x7
: the application was started by another application0x8
: the application was started using one its exposed shell commands0x9
: the application was started as a desktop environment
- Bit 4: set if the application was started automatically after a crash/improver shutdown and should to the
sys::crashsave
service to get a crashsave - Bit 5: set if the application's raw output (CMDRAW) will be read (e.g. through the use of a shell operator)
The startup reason is especially important as it determines what the application should do (e.g. uninstall itself, run as a command...) but also if it should output data through its CMDRAW in case it was called by a command.
Context header
The context header is stored as a single block of data, consisting of:
- The startup reason (1 byte)
- Ambiant informations (1 byte)
- Bit 0: set if the application is starting for the very first time since it was installed
- Bit 1: set if the application is starting for the very first time for this specific user
- Bit 2: set if the application is starting for the very first time as this specific service
- Bit 3: set if the application is starting for the first time after an update
- Bit 4: set if other instances of this application are running
- Special assignment information (1 byte)
- Bit 0: set if the application is starting for the first time after being assigned as the new desktop environment
- Bit 1: set if the application is starting for the first time after being assigned as the new default file manager
- Service type (1 byte):
0x00
: this process is not run as a service0x01
: this process is run as the application's main service0x02
: this process is run as an application's scoped service0x10
: this process is run as the application's desktop environment service0x11
: this process is run as the application's file manager service0x12
: this process is run as the application's file opener service
- Scoped service's name (8 bytes) - filled with zeroes if the process is not run as a scoped service
- The application's ANID (4 bytes)
If the command was started as a command, it also contains the following informations:
- The ANID of the caller application (4 bytes)
- The number of arguments the process was started with (1 byte)
- The cumulated size of all arguments, in bytes - up to 63.5 KB (2 bytes)
- RC identifier for the CMDIN pipe (8 bytes)
- RC identifier for the CMDUSR pipe (8 bytes)
- SC identifier for the CMDMSG pipe (8 bytes)
- SC identifier for the CMDERR pipe (8 bytes)
- SC identifier for the CMDRAW pipe (8 bytes)
- SC identifier for the CMDOUT pipe (8 bytes)
- Future-proof shift space (196 bytes)
RC and SC identifiers come from the relevant flows.
Arguments structure
The context header is followed by the list of each command-line argument, taking up to 64 KB.
Arguments are a simple concatenation of encoded values.
Libraries
Libraries are NightOS' way to share code between multiple applications.
Manifest
As libaries are only meant to share code, there manifest is a lot more simple than the applications' one as it is only made of the common fields.
The system library
The system provides a single system library with the sys::lib
AID, which is built in the system and not removable. Apart from these points, it acts like a standard library. Its version is identical to the system itself, allowing applications and libraries to specify a minimum system version in their dependencies field.
System library modules
The library contains several modules (called SLM for System Library Module), which act like language namespaces to expose specific features:
fs
: Filesystem managementnet
: Network communicationsipm
: Inter-process management (create processes, workers, IPC, shared memory, ...)gui
: Graphical user interface library (relies ondesktop
)apps
: Applications management (installation, removal, ...)perm
: Permissions controllerhydre
: Shell interface (run commands, ...)input
: Input interface (keyboard, mouse, microphone, ...)sound
: Sound interfacesystem
: System interface (low-level changes, updates, ...)sandbox
: Sandboxes management (run applications in sandboxes, ...)desktop
: Desktop management (desktop, windows, notifications, ...)hardware
: Hardware management (drivers and devices)i18n
: Translations
System applications' libraries
Each system application also exposes a library with the sysl::<app name lowercased>
AID which applications can rely on, as these applications are not removable. These are abbreviated SAL for System Application Library.
Filesystem
This document presents how files are stored in NightOS.
Partitions
NightOS uses the Btrfs filesystem for the main storage due to its robustness, performance, and features (e.g. snapshots).
Three partitions are used to store the data:
- One FAT32 partition for BOOT1 ;
- One FAT32 partition for BOOT2 (slot 1) ;
- One FAT32 partition for BOOT2 (slot 2) ;
- One Btrfs partition for users' data (
/etc
except/etc/sys
,/apps
and/home
)
Identifiers and limitations
Filesystem unique identifier
Each existing filesystem gets a unique identifier called the Filesystem Iddentifier (FSID).
It is unique across all filesystems, and consistent across reboots.
Element unique identifier
Each filesystem item has an 8-byte identifier, called the Filesystem Element Identifier (FEID).
It is only unique in the filesystem the item is stored in.
Note that some filesystems may not support this feature. For those, the FEID will be derived from the item's split path, which means it will still be unique in the filesystem but will not be resilient to file moving or another file replacing the previous one.
Temporary FEID
A temporary FEID can be created to grant access to a resource to an application without giving actual access to the original item itself. A temporary FEID only lives in memory, and refers an existing filesystem item. If the original item is deleted, the temporary FEID is deleted too.
Paths' size limit
Paths' length is encoded on 2 bytes, allowing up to 65 534 characters plus the NULL
character.
This limit exists to avoid too costly string copies on path manipulation, while being long enough for the very large majority of use cases.
Filenames
For naming conventions, see the related specification.
Permissions
Permissions on individual items is achieved through storage permissions maps.
Symbolic links
Symbolic links, abbreviated symlinks, are files that point to another location.
Concept
A symlink points to a specific item: file, folder, device, anything. It's just not a shortcut, though, as the symlink will still work if its target is moved.
When a symlink is accessed, the system will transparently access its target item instead.
When a symlink is removed, it does not affect the original target. Also, any number of symlinks can target the same item, and symlinks can target other symlinks to. When accessing a symlink, if its target item is a symlink itself, the latter's target will be accessed instead, and so on, until we do not encounter a symlink anymore.
This can be explicitly disabled when interacting with the filesystem, or limited to a specific number of children.
Also, symbolic links may point to a location on another storage.
Cyclic symlinks
Given the following situation:
- We create a symlink
A
which points to a random file - We create a symlink
B
which points toA
- We update the target of
A
to beB
When we will try to access A
, the system will access B
, then A
, then B
, and so on. This is called a cyclic symlink chain. In such case, the chain is reduced to the minimum (for instance, if we had C
pointing to A
, the minimum chain would not be C
A
B
but just A
B
), and marked as erroneous. The process that tried to access the symlink will receive a specific error code to indicate a cyclic symlink chain was encountered.
Flows
Flows are a simple and efficient way for shell scripts to allow manipulating asynchronous streams of data.
Concept
A flow is a file without extension, located in the /fl
directory, that can either send data to reader processes (read-only) or receive data from writer processes (write-only).
To understand the concept better, here is the list of native flow files that are always available:
Flow file | Type | Description |
---|---|---|
/fl/zero | Read-only | Outputs zeroes all the time ; useful to zero a file or device or to benchmark a storage |
/fl/urand | Read-only | Outputs cryptographically-secure random numbers. Useful to randomly fill a storage or memory area |
/fl/crand | Read-only | Outputs non-cryptographically-secure random numbers, thus faster that /fl/rand |
/fl/null | Write-only | Receives data but does nothing with them |
Processes are based on pipes.
Creating a flow
When a process wants to create a flow, it follows the following procedure:
- The process asks the
sys::fs
service to create a flow - The service creates the related flow file in
/fl
- When a process reads from the (readable) flow file, all data is continuely retrieved from the creator's SC (until the flow is closed)
- When a process writes to the (writable) flow file, all data is continuely written to the creator's RC (the flow is not closed after that though)
- When the creator closes its SC/RC, the IPC channels duo is closed and the flow file is removed
Connecting to a flow
When a process wants to read from or write to a file, it first asks the sys::fs
service to connect to this file. If accepted, it receives a SC or RC to interact with the flow.
Structure
Below is the file tree indicating how elements are organized in NightOS.
NOTE: <F>
indicates the item is a file.
/
├── app Interactables available to all users
│  └── <appname> An application's folder (NOTE: one sub-folder per version for libraries)
│  ├── content Application's program (executables, static resources, ...)
│  ├── crashsaves Application's crash saves
│  ├── data Application's data (e.g. database)
│  ├── packages Application's packages (original package + update packages)
│  └── sandboxes Application's sandboxes
├── dev Connected devices
│ ├── cam Cameras
│ ├── bst Basic storage devices (SD cards, USB keys, ...)
│ ├── etc Uncategorized devices
│ ├── mic Microphones
│ ├── net Network adapters (Ethernet adapter, WiFi card, ...)
│ ├── snd Sound-related output devices (Sound card, DAC, ...)
│ ├── sst Sensitive storage devices (Hard drives, SSDs, ...)
│ └── wrl Other supported wireless devices (Bluetooth adapter, ...)
├── etc Mutable data folder
│  ├── env <F> Environment variables
│  ├── hosts <F> Hosts overriding (e.g. 'localhost')
│  ├── logs Log files
│  | └── upe <F> History of UPE requests (1)
│  ├── public Public data, readable and writable by everyone
│  └── sys (4) System's mutable data - available to system only
│  ├── registry <F> System's registry
│  ├── awake <F> System's shutdown indicator to detect if there was an error during last shutdown
│  ├── integrity Integrity data used during the boot process (2)
| | ├── signkey <F> Key used for signing system files
| | └── signs <F> System file signatures
│  ├── enckey <F> Global storage's encryption key (3)
│  └── users <F> User profiles and groups
├── fl Flow files
├── home Users' data
│  └── <user> A specific user's data
│  ├── apps User's applications (same structure as for `/apps`)
│  ├── appdata User's applications persistent data (not removed when the application is uninstalled)
│  ├── desktop User's files appearing on the desktop
│  ├── documents User's documents
│  ├── downloads User's downloads
│  ├── music User's music files
│  ├── pictures User's pictures
│  ├── videos User's videos
│ └── trash User's trash
├── mnt Mounted storages
│  └── root Soft link to `/`
├── sys (4) System - immutable outside of installation, repair processes and updates
│  ├── apps System applications
│  ├── boot (5) System's boot program (BOOT2)
│  ├── langs Translation files
│  ├── old Old versions of the system, used during the repair process (compressed archives)
│  ├── backup Copy of the last system version (compressed archive)
│  ├── kernel Custom micro-kernel
│  └── valid <F> A file that just contains "ValidMasterKey" to test if the provided master key is valid at startup
├── tmp Temporary folder (cleaned during shutdown)
│  └── <user> Temporary folder for a specific user
Links:
- (1) UPE requests
- (2) Used for integrity checking
- (3) Global storage's encryption key
- (4) Not stored in the main data partition but in BOOT2's partition
- (5) Raw boot program (without header), represented by BOOT2
Notes
Globally installed applications (located in /apps
) can store user-specific data in /home/[user]/appdata/[appname]
, which will only be made of the data
, crashsaves
and sandboxes
folder.
Storage permissions
This document presents how permissions are handled on filesystems.
How permissions work
Each item (identified by its FEID) uses a set of permissions determined by multiple rules.
- Items in the
/dev
,/etc
and/sys
are not available to any user. - Items in a
/app/<xxx>
directory are only available to thexxx
application. Users do not have direct access to these files. - Items in a
/home/<xxx>
directory are only available to thexxx
user. - Items in a
/mnt/<xxx>
directory can be available to various users depending on the mount options.
Note that an exception is made for the system user which gets access to every single file.
These are the default permissions, but it's possible to restrict or extend permissions based on a Storage Permissions Map (or SPM).
Structure
The SPM is a non-contiguous data structure which can be located anywhere in the filesystem.
Header
The SPM's header is a contiguous block located at its beginning.
It is made of the following:
- Number of pages (8 bytes)
- Address of the first page (8 bytes) -
0
if none - Address of the last page (8 bytes) -
0
if none
Pages' header
Each page is made of a contiguous header (data block) made of the following:
- Maximum number of entries (capacity) (8 bytes)
- Number of free entries (8 bytes)
- Address of the previous page (8 bytes) -
0
if none - Address of the next page (8 bytes) -
0
if none - For each entry:
- Address of the entry (8 bytes)
Entries
Each page contains a set of entries, which may be split on the disk. An entry is made of the following:
- Address of the page referencing this entry (8 bytes)
- FEID of the item this entry is for (8 bytes)
- Number of entities described in the entry (8 bytes)
- For each entity:
- Entity type (1 byte):
- Entity ID (8 bytes)
- Permission levels
- Bits 00-01: List directory's content
- Bits 02-03: Read files
- Bits 04-05: Create new items
- Bits 06-07: Edit items
- Bits 08-09: Delete items
- Bits 10-11: Read metadata
- Bits 12-13: Write metadata
- Bits 14-15: Change owner
- Bits 16-17: Edit SPM permissions
An item's owner will have all of these permissions set by default.
An entry's content must be contiguous ; if it grows too large to fit inside its allocated space when updated, it must be moved somewhere else.
Permission levels
A permission level is a 2-bit value which can either be:
0b00
: inherit from the parent entity / from the system's settings0b01
: refuse for this specific item0b10
: allow for this item, recursively0b11
: allow for this item but only for content owned by the referred entity, recursively
File table pointer
Unless a filesystem does not support it, nor natively or through the use of extended attributes, each file and directory's entry must contain the address of its entry in the SPM, with 0
indicating no entry exists for it.
When the item is created, no entry is created for it by default. It's only when permissions are set for the item that an entry is made and referenced in the file table.
When the entry is cleared, it is destroyed and the 0
value is assigned back to the file table entry's SPM pointer.
Boot process
The system's boot process is divided in two parts: the unsecure bootloader (BOOT1) and the system bootloader (BOOT2).
Note that BOOT1 and BOOT2 have their own dedicated partitions, the latter getting one for slot 1 and another for slot 2 (see below for more details).
Stage 1: unsecure bootloader (BOOT1)
This is the component loading at the very beginning, called directly by the UEFI. It is unencrypted (hence the name "unsecure").
It starts by initializing the minimum required computer's components.
If the "Escape" key is detected as being pressed during a short time frame, it shows a troubleshooting menu, notably switching the boot slot.
If other bootloaders are found on the storage, it displays a boot menu asking which system to boot. If another system is selected, it launches it.
Otherwise, it tries to get the master key depending on the configured encryption method:
- If storage encryption is disabled or if only USGE is enabled, nothing to do ;
- If full-storage encryption is enabled:
- For passwordless authentication, ensure Secure Boot has verified BOOT1
- If so, retrieve the master key from the TPM and use it to decrypt the storage
- Otherwise, ask for the recovery key to decrypt the storage (only required once)
- Otherwise ask for master password and use it to decrypt the master key
- For passwordless authentication, ensure Secure Boot has verified BOOT1
It checks the boot slot to use (1 or 2), and read BOOT2 from it.
If BOOT2 is encrypted (full-storage encryption only), the master key is used to decrypt it. Its signature is then checked to ensure it has not been modified. If the signature don't match, the computer will by default refuse to boot to avoid corruption and/or booting malicious programs. By inputting a specific phrase displayed on the screen, the user can force the boot process, at the expense of security.
If signatures match, BOOT2 is launched directly.
Stage 2: system loader (BOOT2)
This component is responsible to user account selection and storage decryption. It is stored in a dedicated partition as the /sys/boot
file, a header-less raw executable program.
It starts by checking signature of all system files (everything located in /sys
and /etc/sys
).
If signatures are not valid, an error message is shown and the booting process is halt. By inputting a specific phrase displayed on the screen, the user can force the boot process, at the expense of security.
BOOT2 also provides more advanced troubleshooting thanks to the whole storage being decryptable.
It then initializes all required drivers, initialize a graphical session, and asks to select a user account. At this point, it also provides more troubleshooting options.
If the provided username and password are valid, it then does the following:
- If USGE is enabled, the master key and user key are decrypted
- If per-user encryption is enabled, the user key is decrypted
The user session is then opened by calling the relevant system component.
Note that the BOOT2 partition is read-only except to the system user itself.
It is only updated through the system update process and when rollbacked.
Update processes
This document describes how updates are performed.
System updates
The system selects an update source, which is one of:
- The official servers (default)
- An alternative server (configured by the user)
- A local update repository package
The source is authenticated, then the latest updates (not yet present on the system) are pulled from it as update package files (.nsu
).
If multiple updates must be installed, they are installed one after the other. This will probably change in the future when an incremental update system is designed.
System update packages
Each package file is a simple TAR + GZ archive with the following structure:
/metadata
: contains metadata on the updates/update
: "up-down" update program/assets
: a directory containing various files (can be anything)
Update steps
- The update package is extracted in a temporary path
- Update metadata are checked
- The system checks the currently used slot
- The active slot (= all files currently in
/sys
) is copied to the other slot - An incremental system backup is created, containing all data from active slot (
/sys
) as well as the/etc/sys
directory is created - A non-incremental (full) system backup is created
- A Btrfs snapshot of the main partition is created
- The "up-down" update program is run
- In case of success, the active slot number is inverted
- System reboots
- After a week or two more reboots (whichever happens last), the main partition snapshot as well as the non-incremental system backup are deleted
This allows the following:
- If the new BOOT2 does not work, we can revert it instantly until the next update ;
- If some modifications created problems, we can roll them back using the main partition snapshot ;
- If some problems appear later on, the boot snapshot allows to entirely rollback both
/sys
and/etc/sys
Incremental system snapshots are kept indefinitely in order to allow an easy rollback. A timestamp, system version and update number are associated, in order to quickly determine the state it was in.
Up-down update program
An up-down update program is a program that can either install or uninstall an update.
It is provided an history of all system updates as well as a snapshot of the BOOT2 partition at each point in time.
Application updates
An application update can happen in either scenario:
- The system checked for updates in the Store (official or custom) and found one ;
- The application asked the system to check for update and it found one ;
- A custom application update package (
.nua
) was provided
The update package file is then checked. It is a simple TAR + GZ archive containing the following:
metadata
: contains update metadatafiles
: a directory containing all files to replace in the application
Note that an update can only apply to a specific version of the application, specified in the metadata. Otherwise, previous updates must be installed first.
If valid, the system asks the user to close the application if it is opened.
After the application is fully closed (including services), its files are replaced with the ones provided in the archive's files
directory.
The application is then started with its new state.
Permissions
Permissions are used to control what applications can do or not. They have no effect by themselves, but allow different system services to ensure an application has enough permissions to perform a specific action.
WARNING: This document is far from being complete, and as such may be edited in any way at any moment.
Levels of permissions
Permissions are split across five different levels:
- Level 1: non-sensitive permission like creating a window ; these are granted automatically by defualt
- Level 2: accessing and modifying non-critical parts of the state of the system, like controlling the global volume
- Level 3: performing sensitive actions, like reading files or accessing the network
- Level 4: performing highly-sensitive actions, like accessing the microphone or the webcam
There are also domain-controlled permissions as well as application proxies which influence how permissions are granted.
When using visual applications, requesting interactive, sensitive and privacy permissions will show a popup asking the user if they want to grant the permission:
- Only one time
- For the active application (until it stops)
- For the current session (until the user logs out or the system is shutdown)
- Forever
By default, it is set to "forever". But for the privacy level, this choice is set by default to "forever while the application is active", which prevents the application from accessing them from background tasks.
NOTE: If the application is uninstalled and re-installed later, all the granted permissions will have been dropped, so confirmation will be required again.
NOTE: Permissions granted to volatile applications are saved using the application's hash, so while the exact same application is opened volatilely, permissions are kept in memory.
NOTE: Permissions are specific to the current user (the AUC is not taken in account here), so in the case of a global application, each user will have to approve the permission manually (unless the administrator set it otherwise).
User privileges
Some permissions require the user to be an administrator. These permissions are marked in this document with an {A}
.
List of permissions
Below is the exhaustive list of all available permissions, with their level between parenthesis (e.g. (5)
for level 5, etc.).
Many permissions require to have an associated scope, indicating the resources they can access. Each scope is a path or filesystem with a set of permissions that apply on this specific path/filesystem.
Devices
Scope: list of device patterns the permission applies on
- (4)
devices.enum
: enumerate devices - (4)
devices.subscribe
: subscribe to devices - (4)
devices.register_driver
: register a device driver - (4)
devices.ask_driver
: ask a device's driver to perform a normalized method
For praticity purpose, the list of device patterns is converted to a human-readable format, with more or less informations depending on the user's complexity level.
Filesystems
Scope: filesystems the permission applies on
- (2)
fs.filesystems.mounted
: check if a given filesystem is mounted (all otherfs.filesystems.*
permissions imply this one) - (3)
fs.filesystems.metadata
: get metadata on a given filesystem - (3)
fs.filesystems.list
: enumerate mounted filesystems - (3)
fs.filesystems.mount
: mount an existing filesystem - (3)
fs.filesystems.unmount
: unmount filesystems mounted by other applications - (3)
fs.filesystems.watch
: be notified when a filesystem is mounted / unmounted - (3)
fs.filesystems.format
: format an existing filesystem
Filesystem elements
Scope: paths the permission applies on
These permissions are automatically granted if their scope is one of the application's own data directories.
-
(3)
fs.feid.path
: convert a given FEID to a split path -
(3)
fs.feid.exists
: check if a given FEID exists in a filesystem -
(3)
fs.path.canonicalize
: canonicalize a split path -
(3)
fs.items.exists
: check if an item exists at a given path -
(3)
fs.items.metadata
: get metadata on a given item -
(3)
fs.items.create
: create new filesystem elements -
(3)
fs.items.move
: rename and move existing filesystem elements -
(3)
fs.items.remove.trash
: send items to the current user's trash -
(3)
fs.items.remove
: delete items permanently -
(3)
fs.items.read
: read an existing filesystem element's content -
(3)
fs.items.write
: update an existing filesystem element's content -
(3)
fs.dir.read
: list items contained inside given directories -
(3)
fs.dir.read.hidden
: list hidden items in directories -
(3)
fs.dir.lock
: lock a full directory's content to prevent modifications from other processes -
(3)
fs.dir.lock.full
: lock a full directory's content to prevent modifications and access from other processes -
(3)
fs.symlinks.create
: create symbolic links -
(3)
fs.symlinks.update
: update existing symbolic links -
(3)
fs.symlinks.read
: read the target of a symbolic link
Flows
- (3)
flow.list
: list flows opened by all applications - (3)
flow.metadata
: get metadata on flows - (1)
flow.create
: create flows - (3)
flow.read
: read from flows - (3)
flow.write
: write to flows
Network
- (3)
net.fetch
: fetch a resource
Scope: list of domains (wildcards supported for subdomains), list of protocols - (3)
net.expose
: listen to a specific port or a range of ports Scope: range of ports (may cover a single port)
User accounts
- (1)
users.current.level
: get the current user's permission level - (1)
users.current.name
: get the name of the current user account - (1)
users.current.icon
: get the icon associated to the current user account - (4)
users.current.photo
: get the (optional) photo associated to the current user account - (3)
users.current.lastconn
: get the timestamp of the current user's last login
All these permissions have a variant to get informations about other user accounts (replace current
with all
). These have the same permission level but require the current user to be administrator.
System
-
(1)
system.clock.date.read
: get the current date -
(1)
system.clock.time.read
: get the current time -
(1)
system.clock.timezone.read
: get the current timezone -
(1)
system.clock.uptime
: get the system's uptime -
(1)
system.hw.cpu.count
: get the number of CPUs -
(1)
system.hw.cpu.list
: list CPUs (model, frequency, etc.) -
(1)
system.hw.mem.total
: get the amount of total memory -
(1)
system.hw.mem.available
: get the amount of available memory -
(1)
system.hw.mem.slots
: get the number of RAM slots -
(1)
system.hw.mem.list
: list RAM slots (model, frequency, etc.)
Hardware interfaces
- (4)
hwmid.keyboard.write
: simulate key presses - (5)
hwmid.keyboard.read
: read key presses from the keyboard - (4)
hwmid.pointer.write
: simulate actions from pointer devices - (5)
hwmid.pointer.read
: read actions from pointer devices - (5)
hwmid.video.read
: access video input streams (cameras, ...) - (5)
hwmid.audio.read
: access audio input streams (microphones, ...) - (5)
hwmid.screen.read
: read portions of the screen as image buffers (screenshots, ...) - (1)
hwmid.screen.read_self
: read portions of the current application's windows (screenshots, ...)
State
- (1)
state.laptop.is
: check if the device is a laptop - (1)
state.laptop.is_open
: check if the laptop is physically opened - (1)
state.battery.has
: check if the device has a battery - (1)
state.battery.level
: check the battery level - (1)
state.battery.is_charging
: check if the battery is charging - (1)
state.battery.is_full
: check if the battery is considered full - (1)
state.battery.is_critical
: check if the battery is at a critically low level - (1)
state.battery.estimated_remaining
: get the remaining running time estimated by the system in minutes
Programs
- (5) + {A}
programs.running.processes
: get the list of all running processes - (5) + {A}
programs.running.windows
: get informations on each running application's windows - (5)
programs.running.apps
: get the list of running applications
System-reserved permissions
These permissions are reserved to processes marked as system.
sysapp.process.kill
: suspend or kill external processessysapp.process.signal
: send a signal to an external process
Containers
A container is a virtual space containing a set of limited resources. They are used to isolate sets of applications and establish virtual environments.
- Concept of container
- Containers hierarchy
- Process isolation
- Virtual hardware
- Virtual filesystem
- Performances
- Limitations
Concept of container
A container is represented by a single 64-bit identifier, randomly generated by the kernel. It is attached as a process' attributes.
It is created, managed and destroyed by the sys::proc
service.
Containers hierarchy
A given container may create child containers, in which case the creator is called the parent container.
When a container is destroyed, all children containers in the hierarchy are destroyed as well.
Process isolation
Processes in containers can only communicate with processes of the same container. No outside access is permitted.
In practice, communication with system services is possible thanks to them being aware of the container's informations when receiving a process request.
Communication with the kernel is fully permitted, with limitations applying to the container itself.
Virtual hardware
No hardware device is available by default to a container. Real hardware devices can be connected using a passthrough method from the parent container, and virtual hardware devices can be plugged as well.
Virtual filesystem
The main filesystem of a container is by default a virtual filesystem stored inside a virtual storage file (VSF).
The parent container cannot directly connect a part of its own filesystem into a child's one ; it needs to setup a virtual storage device, which can then be mounted using the related system services.
Note that the system has builtin support for a set of virtual devices which are meant to be used on containers, with native support, which permits to prevent any driver overhead.
Performances
Performances of a container are exactly the same as a normal running program, given there is no emulation of any sort.
However, some I/O operations may result in a small overhead given that system services, as well as the kernel, need to check the permissions associated to the container in addition to transmitting methods and notifications from virtual hardware devices.
A container can be created instantly.
Limitations
A container can only run NightOS applications. As it is not a virtul machine, the kernel version and behaviour will also remain exactly identical across all containers, and upgrading the kernel itself will upgrade it for all containers as well.
If an application is not directly installed inside a container but instead shared as a mount point from the parent container, upgrading or removing the application will have the same effect in the child container.
Registry
This document describes the format of the system registy as well as its content.
Format
The registry is located in the /etc/sys/registry
file. It's basically a nested B-Tree map.
Each map has string keys and a value that describes the entry: its purpose, its type with optional constraints, and then the type-safe value.
All types are described as a name, optionally followed by a maximum number of entries between brackets or a maximum number of bytes between braces. Here is the list, in HFRR notation:
Name | Size (s) in bytes | Description |
---|---|---|
struct | s = ? | A structure - keys are ASCII strings and values' type may be various |
string | s = ? | An UTF-8 encoded string |
string[x] | x <= s <= x * 4 | An UTF-8 encoded string with a maximum capacity of x characters |
string{x} | s <= x * 4 | An UTF-8 encoded string with a maximum capacity of x bytes |
asciistr | s = ? | An ASCII encoded string |
asciistr[x] | s <= x | An ASCII encoded string with a maximum capacity of x characters |
asciistr{x} | s <= x | An ASCII encoded string with a maximum capacity of x bytes |
int{x} | s = x | A signed integer on x bytes |
uint{x} | s = x | An unsigned integer on x bytes |
float{x} | s = x | A floating-point number on x bytes |
pfloat{x} | s = x | A positive floating-point number on x bytes |
ints | s = {CPU bits} | A signed integer on as many bytes as the CPU |
uints | s = {CPU bits} | An unsigned integer on as many bytes as the CPU |
bool | s = 1 | A boolean |
list:type | s = ? | A list of entries with the provided type |
list[x]:type | s = sizeof(type) * x | A list of entries with the provided type with a maximum of x entries |
structlist | s = ? | A list of structures guaranteeing its first entry (the structure model) will never be removed |
map:(key):(val) | s = ? | A dictionary mapping keys of the key to values of the val type |
mapc:(key):(val) | s = ? | A dictionary mapping all possible keys of the key to values of the val type |
structmap:(key) | s = ? | A dictionary mapping keys of the key to structures |
structmapc:(key) | s = ? | A dictionary mapping all possible keys of the key to structures |
time(p) | s <= 8 | A duration with a precision of p (see below) |
tmin(p,min) | s <= 8 | Equivalent of time(p) but with a minimum value |
tmax(p,min) | s <= 8 | Equivalent of time(p) but with a maximum value |
tbtw(p,min,max) | s <= 8 | Equivalent of time(p) but with a minimum and a maximum value |
stime(p) | s <= 8 | Equivalent of time(p) but allows negative durations |
size(p) | s <= 8 | A data size with a precision of p (see below) |
smin(p,min) | s <= 8 | Equivalent of size(p) but with a minimum value |
smax(p,min) | s <= 8 | Equivalent of size(p) but with a maximum value |
sbtw(p,min,max) | s <= 8 | Equivalent of size(p) but with a minimum and a maximum value |
id:app | s <= 256 | Identifier of an installed application (ASCII string) |
id:lib | s <= 256 | Identifier of an installed library (ASCII string) |
id:user | s = 4 | Identifier of an installed user (32-bit unsigned number) |
in:(type):(a...) | s = max(sizeof(type)) | Any value in the provided tuple |
enum:(a,b,c,...) | s = 1 | One of a , b , c ... |
The registry's root is a struct
. Each key-value association in a struct is called a node. Nodes that are single values (neither a list, a map or a structure) are called leafs.
All sizes that are exceptly provided in bytes must be a multiple of two.
Notes about types
-
Durations (
time
,tmin
,tmax
andtbtw
types) have a precision which indicate the smallest unit of time they accept.1
is for years,2
for months,3
for weeks,4
for days,5
for hours,6
for minutes,7
for seconds,8
for milliseconds,9
for microseconds and10
for nanoseconds. -
Sizes (
size
,smin
,smax
andsbtw
types) accept values that are a multiple of their precision.1
is for terabytes,2
for gigabytes,3
for megabytes,4
for kilobytes,5
for bytes and6
for bits. -
For the
struct
type, there is no need to specify the list of possible keys and the type of associated values, because they are already present in the registry when it's installed by the system - so it's all implicit. The keys in a struct should never change through time, nor the type of the value of each key. As such, it is not possible to create a list ofstruct
, for instance. -
For map structures (
structmap
andstructmapc
), the first key when sorting using the default algorithm for each type is guaranteed to never be removed. Its mapped value acts as the model structure for all over values in the map.
HFFR Format
The registry can be converted for debugging to an HFRR text file (Human-Friendly Registry Format).
In this format, structures' keys are described using the key(type):
format. Each line following it describing its value is indented by a tabulation (\t
). The tabulation is decreased when the value has been fully describes.
Strings are describes using double quotes, with a r
prefix symbol for ASCII strings. Floating-point numbers use a .
symbol as their decimal separators, and an explicit +
or -
symbol indicates all numbers' sign.
Each list entry is prefixed by a -
symbol, and each key is described as a set of key: value
list (on multiple lines for lists and maps).
Empty lists are represented as []
and empty maps as {}
.
Durations are represented as a combination of one or more integer numbers each followed by a time suffix (ns
for nanoseconds, us
for microseconds, ms
for milliseconds, s
for seconds, m
for minutes, h
for hours, d
for days, w
for weeks, mo
for months or y
for years). Also, combinations use the space separator and numbers are represented with leading zeros if necessary, except for the first number.
So for instance, a duration of 2 days and 10 seconds will give the string 2d 10s
.
Sizes are represented just like times, but with different suffixes (TB
for terabytes, GB
for gigabytes, MB
for megabytes, KB
for kilobytes, B
for bytes and b
for bits), and without leading zeroes.
So for instance a size of two terabytes and three kilobyte will be represented as 1TB 3KB
.
The easiest way to understand the format is to look at the default registry structure presented below.
Structure
NOTE: This section is under heavy development and is by no mean complete!
Here is the content of the default registry file (when installing NightOS with all default settings), converted to the HFRR format:
# Kernel configuration (only editable in developer mode)
kernel(struct):
# Signals configuration
signals(struct):
# Delay before forced suspension after WILL_SUSPEND signal
suspend_delay(time(ms)): 500ms
# Delay before forced termination (kill) after WILL_TERMINATE signal
terminate_delay(time(ms)): 500ms
# Delay for a service to answer a connection request
service_answer_delay(ntime(ms,100ms)): 500ms
# System configuration
system(struct):
# Debugging options
debugging(struct):
# Is development mode enabled?
dev_mode(bool): true
# Encryption
encryption(struct):
# Is the global storage encrypted?
global_encrypted(bool): false
# Date and time
datetime(struct):
# Is it based on the internet? (if not, it's a custom one)
internet_based(bool): true
# In the case the date is not internet-based, provide a timestamp which indicates the difference between the computer's
# local datetime and the one that should be displayed
custom_based_diff(stime(ns)): 0ns
# Crash saves
crash_saves(struct):
# Restore crash saves as soon as the user logs in
restore_on_login(bool): true
# Do not save crash saves for guest users
disable_for_guest_user(bool): true
# Delay between each crash save
collect_every(tmin(s,1s)): 60s
# Ask an application to create a new crash save even if the previous new has not been completed yet
collect_if_previous_unanswered(bool): false
# Freeze-prevention
freeze_prevention(struct):
# Reserved amount of memory (0 = disabled)
reserved_memory(MB): 16MB
# Reserved amount of CPU per virtual core (0 = disabled)
reserved_cpu_vcore(uint{1}): 1
# Processes' lifecycle
processes(struct):
# Delay before suspension
suspend_delay(time(ms)): 500ms
# Delay before termination
terminate_delay(time(ms)): 2000ms
# Delay for services to answer connections
service_answer_delay(time(ms)): 2000ms
# Users (keys = user's unique identifier, value = user's type with 0 = main admin., 1 = admin., 2 = standard, 3 = guest)
# Detailed informations about each user (nickname, privileges, encryption password, ...) and groups are stored
# in the user profiles file at '/etc/sys/users'
users(map:(uint{4}):(enum:(MainAdmin, Admin, Standard, Guest))):
0: Admin # System
1: Admin # Administrator
Vocabulary
This document introduces all vocabulary related to NightOS;
Graphics-related terms
Surface
A surface is a two-dimension area displayed on the screen. It is often used to refer to an application's window.
Interaction
An interaction with a given surface consists in the user clicking on it on pressing a key while this surface is focused.
Windows
Window
A window is a 2D figure provided to an application for manipulation. It does not refer to the desktop environment itself, or to the bootloader's screens for example. There are several types of windows: resizable windows and borderless windows.
Borderless windows
Borderless windows are windows without any titlebar nor resizing lines.
Resizable windows
Resizable windows are the most common type of windows. They feature a title bar as well as resizing lines.
Windows' title bar
A window's title bar is a bar located above the window's content. It contains:
- On its left: a set of four buttons (customizable) which allow to hide the window in the taskbar, restore/maximize it, close it, or suspend/resume the related application ;
- On its center: the window's title, which is controlled by the application and may change during the window's lifetime
Windows' resizing lines
A window's resizing lines are four lines on all sides of a window, 1-pixel wide or high, which allow to extend or reduce the window's size relatively to the line's position.
For example, dragging the top line to a pixel higher on the screen will result in the window being extended to this direction - but other borders of the window won't move. Dragging the top line to the opposite direction will result in the window being compressed to this direction.
The top line is located above the window's titlebar.
Hydre
The shell is the part responsible for running scripts, which are small text-based programs that are natively available on every NightOS installation. It is called Hydre and is part of the system under the form of the sys::hydre
service.
You can get a quick overview of Hydre in its technical document.
NOTE: The behaviours described here only apply when using the Pluton terminal application. Although all terminals are expected to follow these specifications as they act as a standard for terminals, they are technically not forced to and so misbehaviours may appear when using an alternative terminal application.
Shell sessions
Before evaluating commands, a shell session needs to be created using the sys::hydre
service.
A session has the following properties:
- A (changeable) width and height, in number of characters
- Commands output are concatenated
Commands can then be run inside the created session, and once they are all finished the same session can be closed using the same service. This simplify execution chaining, but also enables commands to be notified through the service's pipes when the session is resized.
Only one shell instruction can be run at a time, but as it may be made of multiple commands (e.g. cmd1 | cmd2
), multiple processes may be binded to the same shell session. Also, background commands may be running in parallel of the running command.
For instance, when using the Pluton terminal, it creates on opening a shell session to run the commands in. When a new tab is opened, another shell session is opened for that tab. When the tab is destroyed, the session is destroyed as well, which means all commands running in it (including pipe and background commands) are killed.
Sessions security
When a command is run, the command's PID is registered in the session's actors list. When the command ends, it is removed from the list.
When a process contacts the sys::hydre
service to access the session it runs in, Hydre gets the session associated to this process' PID, and returns it. If the PID is not associated to any session, the access is refused and the command won't be able to get any information on any session.
This prevents processes from accessing sessions they aren't part of.
Commands evaluation
Commands are evaluated one by one, as scripts cannot be run in a concurrent way. They are handled as follows:
- Builtin commands are treated internally by the shell
- Application commands will result in launching the requested application in a separate process
Command pipes
When an application is started from a command, its execution context indicates it and the process gets access to several pipes called the command pipes:
Pipe identifier | Standard pipe name | Pipe type | Format | Description |
---|---|---|---|---|
CMDIN | Typed input | Raw | typed | Data coming either from a command pipe (` |
CMDUSR | Interactive input | Message | UTF-8 | Data coming from a terminal session (e.g. user inputs) |
CMDMSG | Messages output | Message | UTF-8 | Messages to display in the console, which won't be redirected by default |
CMDERR | Errors output | Message | UTF-8 | Messages to display as errors in the console, which won't be redirected by default |
CMDRAW | Raw bytes | Raw | Raw | Output data, which will be redirected if an output pipe (> ) is used |
CMDOUT | Typed output | Raw | typed | Typed output data, which will be used by shell scripts) |
The SC/RC identifiers of these pipes are available in the application's execution context.
The process that launched the command gets the ability to:
- Send data to the callee's CMDIN/CMDUSR pipe ;
- Read data from the callee's CMDMSG/CMDERR/CMDRAW/CMDOUT pipes
As this only applies to processes that can be started from commands, this only applies to application processes ; system processes not being linked to commands and worker processes being started from application processes (and so not directly from commands).
Technically speaking, commands are started by the sys::hydre
service and so by a system process. This same service creates the pipes and handles them. For more informations on this, please check the service's documentation.
If the process terminates before the return value has been fully transmitted through CMDOUT or if it closes the CMDOUT pipe before fully transmitting the value, the process is considered as faulty and killed immediatly (if still alive). The calling script (if any) exits with an error message, unless the error is caught with catch
, the error message being generated by the system.
Even if the process closes its CMDMSG or CMDRAW pipe properly (by calling the CLOSE_PIPE
), the command is not considered as finished until the process itself did not terminate.
Note that when a return value has been fully transmitted through CMDOUT, all pipes are closed and the command is considered as finished.
Interactivity
When an application command is run, the pipes are handled as follows.
Input data
All data coming from a command pipe (|
) or from an input pipe (<
) (if the command's input type is stream
) are transmitted through CMDIN.
Once the input data have been fully transmitted, the CMDIN pipe is closed.
User inputs
All user inputs (including raw keystrokes) are transmitted to CMDUSR, except a few ones:
Ctrl-.
, which asks the process to suspend (triggers theSUSPEND
signal)Ctrl-Shift-.
, which forces the process to suspend (triggers theWILL_SUSPEND
signal)Ctrl-C
, which asks the process to terminate (triggers theTERMINATE
signal)Ctrl-Shift-C
, which forces the process to terminate (triggers theWILL_TERMINATE
signal)- Custom GUI keystrokes like
Alt-F4
- System-handled keystrokes like
Ctrl+Alt+Del
User input messages use the following format (always starting from the strongest byte/bit):
- Byte 0: modifier keys
- Bit 0: set if the
Command
orWindows
key is pressed - Bit 1: set if the
Ctrl
key is pressed - Bit 2: set if the
Alt
key is pressed - Bit 3: set if the
Shift
key is pressed - Bit 4: set if the
Fn
key is pressed - Bit 5: set if Numeric Lock is enabled
- Bit 6: set if Caps Lock is enabled
- Bit 7: set if Scroll Lock is enabled
- Bit 0: set if the
- Byte 1: keycode
- Bytes 2-5: UTF-8 printable character on 4 bytes, or
0x00
if the character is not printable
Text output
Commands may output either simple text messages (via CMDMSG) or error text messages (via CMDERR).
Conventionnally (but it's up to the terminal application), text messages are displayed by default in white while error messages are displayed in red.
Messages must use the following format (always starting from the strongest byte/bit):
- Byte 0-2: RGB foreground color (
0
will use the current one) - Byte 3-5: RGB background color (
0
will use the current one) - Byte 6: style
- Bit 0: if set, the message will be displayed in bold
- Bit 1: if set, the message will be displayed in italic
- Bit 2: if set, the message will be displayed with a line below it
- Bit 3: if set, the message will be displayed with a line above it
- Bit 4: if set, the message will be displayed reversed
- Byte 7: number of control characters
- Byte 7-(N): control characters on 2 bytes each
- Bytes (N+1)-(END): The message itself, encoded in UTF-8 (can be empty)
Control characters use the following format:
- Strongest byte: control character code
0x00
: no control character0x01
: move cursor up X times0x02
: move cursor left X times0x03
: move cursor right X times0x04
: move cursor down X times0x05
: move the cursor to the beginning of the line
- Weakest byte: data byte
For instance, an 0x0305
control character is decomposable in the 0x03
code and the 0x05
data byte, which means moving the cursor to the right 5 times.
Invalid messages
A message is considered invalid if at least one of the following conditions is verified:
- The message's length is lower than 8 bytes + 2 * (number of control characters)
- The provided control character is invalid
Messages that do not follow this format will result in displaying Unicode's replacement character (0xFFFD
: ďż˝) instead.
Messages providing an invalid foreground and/or background color will conventionnally (but it's up to the terminal application) in a specific color to indicate no right color could be determined.
Events handling
Commands can get informations on the current session using the sys::hydre
service.
This allows the command to be notified of events like windows resizing. For more informations, see the service's specifications document.
Scripting language
You can find more about the script language in the language's specifications document.
Pre-evaluation checking
Before executing a script, the shell looks for errors in it, such as unknown command, invalid argument, type mismatch and so on. If an error is foud, the script doesn't even start to run ; an error is directly reported and the command is considered as failed.
This prevents errors from happening in the middle of a script, leaving it in an inconsistent state ; this also makes script errors easier to debug as they are reported at compile time.
Shell scripting
The scripting language of Hydre offers a lot of powerful easy-to-use features. This allows to create complex script that are still very readable and maintanable.
- Running a command
- Comments
- Variables
- Value types
- Expressions
- Computing values
- Lists and maps
- Blocks
- Functions
- Nullable types
- Advanced types
- Data validation
- Event listeners
- Imports
- Commands input & output
- Running in background
- Environment variables
- Commands typing
- Native library
- Utilities
env(varname: string) -> any?
prompt(message: string) -> string
prompt_int(message: string) -> fallible int
prompt_float(message: string) -> fallible float
confirm(message: string) -> fallible bool
choose(options: list[string]) -> fallible int
retry_cmd(cmd: command, retries: int) -> fallible
exit()
last_failed() -> bool
rand() -> float
rand_int(low: int, up: int) -> fallible int
rand_float(low: float, up: float) -> fallible float
- All types
- Nullable types
- Characters
- Strings
string.chars() -> list[char]
string.codepoints() -> list[int]
string.len() -> int
string.bytes() -> int
string.parse_int(base = 10) -> fallible int
string.parse_float(base = 10) -> fallible float
string.upper_case() -> string
string.lower_case() -> string
string.reverse() -> string
string.concat(right: string) -> string
string.split(str: string, sep: string) -> string
- Lists
list[char].stringify() -> str
list[string].join(sep = ",") -> string
list[T].get(index: int) -> T?
list[T].expect(index: number, message: string) -> T
list[T].unshift(value: T)
list[T].push(value: T)
list[T].unshift() -> T?
list[T].pop() -> T?
list[T].sort(asc = true) -> list[T]
list[T].reverse() -> list[T]
list[T].len() -> int
list[T].concat(another: list[T]) -> list[T]
list[T].concat(lists: list[list[T]]) -> list[T]
- Maps
- Commands
- Streams
- Utilities
- Examples
- Native commands
Running a command
Hydre uses an intuitive syntax. Commands are written like they would be in *-sh shells: the command name is followed by arguments, each separated by a space.
Arugments can either be positional (they are written directly), shorts (prefixed by a -
symbol and one-character long), or longs (prefixed by two -
symbols). Here is an example:
cmdname pos1 pos2 -a --arg1
This line runs the command called cmdname
, provides two positional arguments pos1
and pos2
, a short one a
and a long one arg1
.
Short and long arguments are called dash arguments.
Combining short arguments
It's possible to combine multiple short arguments in once by writing them one after the other:
cmdname -abc
This line is strictly equivalent to:
cmdname -a -b -c
Argument values
Short and long arguments can also require a value. This value must be provided using an =
symbol or by simply using a space:
cmdname -s=1 --long=2
# or:
cmdname -s 1 --long 2
Comments
Comments can be written on a single line with the #
symbol:
cmdname # single-line comment
Everything after the #
symbol is ignored. For multiple-line comments, it's required to use three #
symbols:
###
this
is a
multi-line
comment
###
Variables
Variables are declared with the var
keyword:
let age = 19
Here, we declare a variable age
with value 19
. As variables are typed, this variable will only be allowed to contain numbers from now on - no strings, no booleans, nothing else.
And to assign a new value to it:
age = 20
Value types
All arguments must match their expected type: if the command is expecting a number, we can't give it a list for instance.
Here is the list of all types and how they are written:
# Booleans (bool) (`true` or `false`)
_ = true
# Integers (int)
_ = 3
# Floating-point numbers (float)
_ = 3.14
# Characters (char)
_ = 'a'
# Strings (string)
_ = "abc"
# Lists of a given type (list[type])
_ = [ 3, 3.14 ]
# Maps (key-values) (map[key_type, value_type])
_ = { "a": 1, "b": 2 }
# Paths (path) - must contain at least one `/` to indicate clearly that it's a path and not a string or something else
_ = dir/file.ext
_ = ./file.ext
_ = /tmp
# Commands (used to run custom commands later in functions)
_ = @{ command "pos1" -s --long }
A string
is composed of multiple char
s, which are made of single codepoints. This means a grapheme cluster made of multiple codepoints will need to be encoded in a string
.
There is also the any
type which accepts values of all types, and stream
which we'll talk about later as it's a special type.
Finally, there is the void
type which cannot be written 'as is' but is used in special contexts like commands return values. It's a type that contains no data at all.
There are also flag arguments, which are dash arguments that take no value. The command will simply check if the argument was provided or not.
NOTE: In order to avoid writing errors, positional arguments cannot be provided after a flag argument.
For instance, considering pos1
and pos2
are positional arguments, --flag
a flag argument and --val
a non-flag long argument:
# VALID
command pos1 pos2 --flag --val 2
# VALID
command pos1 --val 2 pos2 --flag
# VALID
command pos1 --flag --val 2 pos2
# VALID
command --flags --val 2 pos1 pos2
# INVALID (we could think by reading this that "pos2" is the
# value of the non-flag argument "--flag")
command pos1 --flag pos2 --val 2
As you can see, suites of characters that do not start with a dash (-
) can be written without quotes when they're part of a command. Any special character (like spaces or dashes) can be escaped using the \
prefix:
command this\ is\ the\ same\ \-\ argument
# Strictly equivalent to:
command "this is the same - argument"
Variables shadowing
Any variable can, inside a program, be replaced by a new variable with a same name but with a different type. This is called shadowing.
It can be useful when converting data from a type to another, such as:
let names: list[string] = [ "Jack", "John" ]
let names: string = names.join(",") # this is called a _method_, we'll talk about them later
Here, we shadowed the names
variable to create a new one with a different type from the data we had before, which means we cannot access the original names
variable anymore.
Expressions
To use a variable, we can directly use it like this::
tellage $age
So this code:
let age = 20
tellage $age
Is equivalent to this one:
tellage 20
It's also possible to perform computations using expressions:
let age = 20
let add = 12
tellage (age + add)
String, characters and numbers can also be inserted inside strings:
let name = "Jack"
echo "Hello, $name!" # Hello, Jack!
Other expressions require to use ${...}
. For instance, values in lists through their index (starting at 0):
let names = [ "Jack" ]
echo "Hello, ${names[0]}!" # Hello, Jack!
Values in maps through their key:
let ages = { "Jack": 28 }
echo "Jack is ${ages["Jack"]} years old!"
Note that getting an out-of-bound index will make the program panic, which means it exits immediatly with an error message.
let names = [ "Jack" ]
echo "Hello, ${names[1]}!" # Panics
In commands, expressions can be provided wrapped in (...)
:
echo (2 + 1) # Will display: "3"
Computing values
It's also possible to compute values using operators. Each operator takes one or multiple operands, which can be either a variable or a literal value.
Mathematical operators
Mathematical operators all take two operands. If any of the operands is not a number, it will fail. If both operands are integers, the result will be an integer, but if at least one is a floating-point number, so will be the result.
The operators are:
+
: addition-
: substraction*
: multiplication**
: pow/
: division//
: floating-point division (gives a floating-point number even if the two operands are integers)%
: remainder (works only with two integers)
Bit-wise operators
Bit-wise operators only take integer operands and produce an integer result:
&
bit-by-bit and|
bit-by-bit or^
bit-by-bit exclusive or<<
binary left shift operator>>
binary right shift operator~
one's complement - takes a single number
Logical operators
Logical operators take two operands and return a boolean:
Symbol | Name | Returns true if... |
---|---|---|
&& | and | a and b are true booleans |
|| | or | a , b or both are true booleans |
== | equal to | a is equal to b |
!= | different than | a is different than b |
> | greater than | a is greater than b |
< | lower than | a is lower than b |
>= | greater than or equal to | a is greater than or equal to b |
<= | lower than or equal to | a is lower than or equal to b |
Finally, there is the !
operator, which takes a single operand on its right, and simply reverts a boolean.
Assignment operators
The neutral assignment operator =
can be prefixed by any mathematical, bit-wise or logical operator's symbol(s). The operator's left operand will be the current variable's content, while the right one will be the value on the left of the assignment operator. The result will then be stored in the variable.
Note that for a variable to be modified, it must be declared as so:
# Immutable
let a = 3
# Mutable
let mut a = 3
Otherwise, re-assigning a value to the variable would fail.
# We declare the variable as mutable...
let mut a = 3
# ...and we can then re-assign to it
a += 1
a *= 8
a /= 2
echo $a # 16
Note that the result's type must also be compatible with the variable:
let a = 0
a &&= 1 # ERROR: Cannot assign a 'bool' to an 'int'
There are also the ++
and --
operators, which respectively increase and decrease the desired variable:
let a = 0
a ++
a ++
a --
echo $a # 1
Lists and maps
Lists and maps behave in a similar way: while lists have indexes, which are handled behind-the-scenes, maps have keys, which are provided explicitly.
Here is how we declare a list or a map:
# Type: list[string]
let names = [ "Jack", "John" ]
# Type: map[string, int]
let ages = { "Jack": 28, "John": 29 }
To add a new value (requires the variable to be mutable):
# Lists
names[] = "Paolo"
# Maps
ages["Paolo"] = 26
To get a value:
# Type: int
names[0]
# Type: int
ages["Paolo"]
To remove a value:
# Lists
delete names[0]
# Maps
delete ages["Paolo"]
To get the number of entries:
# Type: int
names.len()
# Maps: int
ages.count()
Blocks
Blocks allow to run a piece of code multiple times or if a specific condition is met. They are useful combined to comparison operators.
Conditionals
Conditionals uses the following syntax:
if # condition
command1
end
When this block is ran, if condition
(which is an expression that must result in a boolean) is equal to true
, command1
is ran. Here is an example:
if 2 + 2 == 4
command1
end
It's possible to specify multiple commands at once:
if 2 + 2 == 4
command1
command2
command3
end
Note that all commands must be indented by one tabulation.
It's also possible to run a set of commands in case the condition isn't met too using else
:
if 2 + 2 == 4
command1
else
command2
end
Finally, conditions can be chained using elif
:
if 1 + 1 == 4
echo "Bizarre."
elif 1 + 1 == 3
echo "Bizarre!"
else
echo "Normal."
end
Switches
A switch allows to perform actions depending on a value. It's roughly equivalent to a combination of multiple if
and elif
statements.
switch rand_int(0, 10)
when 0
echo "It's zero!"
when 1
echo "It's one!"
else
echo "It's something else"
end
Note that, for blocks that only contain a single instruction, we can shorten this using the following syntax:
switch rand_int(0, 10)
when 0 => echo "It's zero!"
when 1 => echo "It's one!"
else => echo "It's something else!"
end
Loops
Loops allow to run a piece of code for a while. The most common loop is the range loop:
for i in 0..10
command $i
end
This will run command 0
to command 9
. To include the upper bound, we must add an =
symbol:
for i in 0..=10
command $i
end
This will run command 0
to command 10
.
We can also iterate on a list:
let str = "Jack"
let list = [ "Jack", "John" ]
for name in list
echo $name
end
This will display Jack
and John
.
To get the indexes as well, we can do:
for i, name in list
echo "$i: $name"
end
This will display 0: Jack
and 1: John
.
For maps:
let ages = { "Jack": 28, "John": 29 }
for name, age in ages
echo "$name is $age years old!"
end
There is another type of loop, which runs a piece of code while a condition is met:
while # condition
command
end
If the condition
is false
when the loop is reached, the command
will not be ran once. Else, it will be ran as long as the condition
is true
.
Note that loops can be broke anytime using the break
keyword:
for i in 0..10
command $i
if i == 2
break
end
end
This will run command 0
, command 1
and command 2
only.
Filesystem iteration
It's possible to iterate on a list of files and directories:
for file in (./*.txt)
echo "Found a text file: $file"
end
The pattern between parenthesis must be a glob pattern. Recursivity is supported to:
for file in (**/*.txt)
echo "Found a text file: $file"
end
Variables scoping
When a variable is declared, it is scoped to the current block, meaning it doesn't exist outside of the current block:
# This variable is declared in the "global" block
# so it's available everywhere in the current script
let firstName = "Jack"
if firstName == "Jack"
# This variable is declared in an "if" block
# so it's not available outside of it
let lastName = "Sparrow"
end
echo $firstName # Prints: "Jack"
echo $lastName # ERROR ("lastName" is not in scope)
Also, variables are not shared between scripts.
Functions
Functions allow to split the code in several parts to make it more readable, as well as to re-use similar pieces of code across the script.
fn hello()
echo "Hello!"
end
Now we can call hello
this way:
hello() # Will print "Hello !"
Arguments
Function can also take arguments, which must have a type.
fn hello (name: string)
echo "Hello, $name!"
end
hello("Jack") # Will print "Hello, Jack!"
Arguments can be made optional by providing default values. This also allows to get rid of their explicit type as it's now implicit:
fn hello (name = "Unknown")
echo "Hello, $name!"
end
hello() # Will print "Hello, Unknown!"
hello("Jack") # Will print "Hello, Jack!"
Note that a function's arguments do not require to wrap expressions between ${...}
as it's implicit. Which means we can write:
fn sayNumber (num: int)
echo "Number is: $num"
end
sayNumber(2 + 1) # Will print "Number is: 3"
We can also combine functions and blocks, for instance:
fn greet (names: list[string]) -> int
# .len() is called an _extension_, we will see more about that later
if names.len() == 0
echo "No one to greet :|"
else
for name in names
echo "Hello, $name!"
end
end
end
hello([]) # No one to greet :|
hello(["John", "Jack"]) # Hello, John! Hello, Jack!
Return types
Functions can also return values. In such case, they must specify the type of values they return, and ensure all code paths will return a value of this type:
fn add (a: float, b: float) -> float
return a + b
end
Methods
All value types expose specific functions that can be used with a dot after a variable of the given type, called methods:
let letters = "abcdef"
let letters = letter.split(",")
Here, we use the split
method of the string
type, which returns a list[string]
.
Failing
Functions can also fail to indicate something went wrong:
fn divide (a: float, b: float) -> fallible float
if b == 0
fail "Cannot divide by 0!"
else
return a / b
end
end
The fallible
keyword must be present before the return type to indicate the function may fail (even if the function doesn't return anything).
When a function fails, the program stops and print the provided error message. But it's also possible to handle the error:
fn handle_bad_div (a: float, b: float) -> float
catch divide(a, b)
ok result
echo "Divided successfully: $a / $b = $result"
err errmsg
echo "Division failed :("
echo "Here is the error message: $errmsg"
end
end
Note that you may handle only the success or error case depending on your needs ; you do not have to handle both cases.
This keyword also allows to catch errors from commands:
# Run a command and get error messages from CMDERR instead if the command fails
catch $(somecommand)
ok data => echo "Success: $data"
err msg => echo "Errors: ${msg.join("; ")}"
end
Retries
It's possible to retry a function until it succeeds using the retry
keyword:
fn may_fail ()
if rand() > 0.5
fail "I don't like high numbers"
end
end
retry may_fail()
This will run may_fail
, and run it again if it fails, until it succeeds.
It's also possible to specify a maximum of retries:
retry(5) may_fail()
For information, here is the declaration of the native retry_cmd
command, which allows to try to run a command until it succeeds:
fn retry_cmd(cmd: command, retries: string) -> fallible
retry(retries) cmd()
if status() != 0
fail "Command did not suceed after $retries retries."
end
end
It can be used like this:
retry_cmd(@{ read "file.txt" }, 10)
Global failing
A whole script can fail using this keyword, which will result in displaying the error message in CMDERR and exiting immediatly.
The failure may be handled using fallible
in the caller script.
Nullable types
Sometimes, it's useful to be able to represent a value that may be either something or nothing. In many programming languages, "nothing" is represented as the null
, nil
or ()
value.
Nullable types are suffixed by a ?
symbol, and may either contain a value of the provided type or null
. Here is an example:
fn custom_rand() -> int?
let rnd = rand_int(-5, 5)
if rnd > 0
return rnd
else
return null
end
end
To declare a variable with an nullable type, we wrap its initialization value in the nullable operator ?(...)
:
let a = 1 # int
let b = ?(1) # int?
Or we explicitly give the variable a nullable type:
let b: int? = 1 # int?
To initialize the variable with the null
value instead, we must use the explicit version:
let c: int? = null # int?
Note that imbricated types are not supported, which means we cannot create int??
values for instance.
Handle the null
value
If we try to access an nullable value "as is", we will get a type error:
let a = ?(1)
let b = 0
b = a # ERROR: Cannot use an `int?` value where `int` is expected
We then have multiple options. We can use one of the nullable types' function:
let a = ?(1)
let b = 0
# Make the program exit with an error message if 'a' is null
b = a.unwrap()
# Make the program exit with a custom error message if 'a' is null
b = a.expect("'a' should not be null :(")
We can also detect if a value is null
by using the .isNull()
method:
let a = ?(1)
let b: int? = null
echo ${a.isNull()} # false
echo ${b.isNull()} # true
There is also the .default(T)
method that allows to use a fallback value in case of null
:
let a = ?(1)
let b: int? = null
echo ${a.default(3)} # 1
echo ${b.default(3)} # 3
We can also use special syntaxes in blocks:
let a = ?(1)
if some a
# While we are in this block, 'a' is considered as an 'int'
else
# While we are in this block, 'a' is considered as 'null'
end
while some a
# Same here
end
Also, if the program exits in all cases when the argument is considered as null or non-null, the opposite type will be applied to the rest of the program:
let a = ?(1)
if some a
exit
end
# 'a' is considered as 'null' here
let b = ?(1)
if none b
exit
end
# 'b' is considered as 'int' here
The case of optional arguments
When a command takes an optional argument, it's possible to provide a nullable value of the same type instead:
let no_newline = ?(false)
echo "Hello!" -n $no_newline
If the value is null
, the argument will not be provided. Else, it will be provided with the non-null value.
Nullable any
The any
type covers any type of values, meaning it accepts absolutely every single value, except the null
value. Indeed, any
is not nullable by default, so to make it accept null
values we must use any?
instead.
Advanced types
Structures
Structures map one or multiple fields to as many values. Here is an example:
fn sayHello(person: struct { firstName: string, lastName: string })
echo "Hello, ${person.firstName} ${person.lastName}!"
end
sayHello({ firstName: "Bat", lastName: "Man" }) # Prints: "Hello, Bat Man!"
In such a simple example, it's easier to directly use a firstName
and lastName
parameters instead of a struct
, but they are useful when returning heterogenous sets of data, for example in a list:
fn listRecursively(dir: path) -> list[struct { name: path, size: int }]
let list: list[struct { name: path, size: int }] = []
for item in $(ls $dir --details)
if item.isDirectory
listRecursively(dir)
else
list[] = { name: item.path, size: stats.sizeOf }
end
end
return list
end
But, as this is not very readable, it's better to use a type alias:
type fsItem = struct { name: path, size: int }
fn listRecursively(dir: path) -> list[fsItem]
let list: list[fsItem] = []
# ...
end
Closures
Closures are anonymous functions which are generally used to repeat the same group of operations.
let test: fn (string, int) = { a, b -> echo (a.repeat(b)) }
test("Hello world! ", 3) # Prints: "Hello world! Hello world! Hello world! "
Note that closures can also return values implicitly:
let test: fn (string, int) -> string = { a, b -> a.repeat(b) }
echo (test("Hello world!", 3)) # Prints: "Hello world! Hello world! Hello world! "
Also, closures are not forced to take their declared parameters:
type testType = fn (string, int)
let test: testType = { a, b, c -> ### ... ### } # NOT VALID
let test: testType = { a, b -> ### ... ### } # Valid
let test: testType = { a -> ### ... ### } # Valid
let test: testType = { -> ### ... ### } # Valid
Here is a concrete usage example:
fn forEachFile(dir: path, callback: fn (path))
for item in $(ls $dir --details)
if item.isFile
callback(item.fullPath)
end
end
end
forEachFile(./, { file -> echo "File: $file" })
Streams
An usual type for manipulating large data is stream
, which is notably used to treat a chunk of data that is either too large for the memory or is more easier to treat as things progress.
Data validation
Some commands may return a value whose type cannot be predicted before runtime. For instance, a command fetching a JSON object from a remote server will, in case of success, return a value but whose type cannot be known beforehand.
Data validation is a feature allowing to scripts to check the type of an unknown value at runtime, using type assertions.
Here is an example:
let test: any = [ 2, 3, 4 ]
# 1. Here, "test" is considered to be of type "any"
if test is list[int]
# 2. Here, "test" is considered to be of type "list[int]"
else
# 3. Here, "test" is considered to be of type "any"
end
The isnt
keyword can also be used:
let test: any = [ 2, 3, 4 ]
# 1. Here, "test" is considered to be of type "any"
if test isnt list[int]
# 2. Here, "test" is considered to be of type "any"
else
# 3. Here, "test" is considered to be of type "list[int]"
end
If, in an "isnt" conditional, the script exits/fails in all cases (or returns if we're in a function), the value is considered with the asserted type for the rest of the script:
let test: any = [ 2, 3, 4 ]
# 1. Here, "test" is considered to be of type "any"
if test isnt list[int]
# 2. Here, "test" is considered to be of type "any"
exit
end
# 3. Here, "test" is considered to be of type "list[int]"
# > Because it's impossible for "test" to not be "list[int]" as in that case the program would have exited
This can be used to check complex structures as well:
# Considering we have a `fn fetchJson(url: string) -> any' function that fetches a JSON value from the web
type User = struct { id: int, firstName: string, lastName: string, email: string }
let json = fetchJson("https://mysuperapi.../users/all")
if json isnt User
fail "JSON doesn't have the correct structure"
end
# "json" is considered as a "User" here
Event listeners
Scripts can listen to events using the on
keyword:
on keypress as keycode
echo "A key was pressed: $keycode"
end
This program will display a message each time a key is pressed.
If the event listener is registered in a function, the is automatically unregistered when that function returns. If it's registered outside a function, it is unregistered when the script ends.
fn test()
on keypress as keycode # (1)
# Do something
end
# Event listener (1) is unregistered here
end
on keypress as keycode # (2)
# Do something
end
echo "Hello world!"
# Event listener (2) is unregistered here
Waiting
Scripts may wait for a specific event before continuing. This can be achieved without a while
loop that consumes a lot of CPU, using the wait
keyword:
wait condition
The script will block while the provided condition
is not true
. The checking interval is defined by the system, and the condition should as fast to check as possible to consume as little CPU as possible.
echo "Please press the <F> key to validate your choice"
let validated = false
on keypress as keycode
if keycode == KEY_F
validated = true
end
end
wait validated
echo "Thanks for validating your choice :D"
It's also possible to wait for a variable to not be null
:
echo "Please press a key"
let key: int? = null
on keypress as keycode
key = keycode
end
wait some key
echo "You pressed key: $key"
Imports
As you may already know, command names can be quite long and complicated. In order to prevent from having to repeat very long names that are not really readable, it's recommanded to use imports which are declared at the beginning of script with the form <dev>::<app>::<command>
:
import system::fs::read_file
read_file # ...
It's then possible to use the read_file
without prefixing.
To perform multiple imports at once:
import system::fs::{read_file, write_file}
It's also possible to only bind the application itself:
import system::fs
fs::read_file # ...
Aliases
Imported commands can also be aliased:
import system::fs as sysfs
sysfs::read_file # ...
Import expansions
It's possible to import all commands from an application, with:
import system::fs::*
read_file # ...
But also to import all applications from a developer, with:
import system::*
fs::read_file # ...
Note that, if a name clash occurs - if two applications or commands with the same name are imported -, the script won't be able to run.
Non-clashing namespace
The non-clashing namespace is a namespace that can be imported, where live all commands whose name is unique across all applications.
For instance, let's imagine we have two applications:
AppA
byDevA
, which exposes acmd_a
and acmd_z
command ;AppB
byDevB
, which exposes acmd_b
and acmd_z
command
What happens here? While the cmd_z
command has a name clash between AppA
and AppB
, the cmd_a
and cmd_b
commands don't. This results in these last two commands being also put in the nonclashing
namespace, which can then be imported like a traditional application:
import shell::nonclashing
nonclashing::cmd_a # OK
nonclashing::cmd_b # OK
nonclashing::cmd_z # ERROR (not in namespace)
This means we can also import all commands that don't clash with other ones:
import shell::nonclashing::*
cmd_a # OK
cmd_b # ...
cmd_z # ERROR (not in namespace)
Volatile imports
As volatile applications' commands are not exposed globally, there is a special import syntax for such applications, allowing to import their commands directly from their application package:
import ./app.nva::super_command
super_command # ...
# OR
import ./app.nva as app
app::super_command # ...
Commands input & output
Reading a command's output
Commands output data through pipes. There are several output pipes that can be used:
- CMDOUT is the default output pipe, which returns typed values (the command declares its output type beforehand)
- CMDRAW allows to send a
stream
, which is useful when dealing with a lot of data or with external data - CMDMSG allows to send
string
messages that are displayed in the terminal's windows - CMDERR allows to send
string
messages that are also displayed, but as error messages
There is a specific syntax to get the output from each pipe. To get the (typed) output from CMDOUT:
_ = $(echo "Hello!") # Contains: "Hello!"
This is called the typed reception operator. It can be used like this for instance:
echo ${$(echo "Hello!")} # Prints: "Hello!"
But, as this syntax is not very readable, evaluating a single command can be made without the ${...}
expression wrapper:
echo $(echo "Hello!") # Prints: "Hello!"
Note that this only work if the command supports piping through CMDOUT.
To get the result from CMDRAW instead (as a stream
), if the command supports it:
# '-b' makes 'echo' read from a stream
echo -b $@(streamify "Hello!") # Prints: "Hello!"
To get the output of CMDMSG instead (as a list[string]
):
echo $?(echo "Hello!") # Prints: "["Hello!"]"
To get the output of CMDERR (as a list[string]
):
echo $!(echo "Hello!") # Prints "[]"
To get the combined output of CMDMSG and CMDERR (as a list[string]
):
echo $*(echo "Hello!") # Prints "["Hello!"]"
Note that using the $(...)
operator will make the program panic if the command exits with a non-zero status code.
Redirecting the output to a file
It's also possible to redirect the output of a command to a file, using the >
operator. The values are converted to strings before being written, except stream
values which are written as they are.
echo "Hello!" > ./test.txt
This works because echo
outputs by default to CMDOUT, not to CMDMSG. If it did, we could still perform the redirection this way:
echo "Hello!" ?> ./test.txt
The prefixes are the same as for the $(...)
operator:
>
for CMDOUT@>
for CMDRAW?>
for CMDMSG!>
for CMDERR*>
for CMDMSG and CMDERR combined
Output data
If a script is declared as a command, it gets its own CMDIN, CMDOUT and CMDRAW pipes (the CMDUSR, CMDMSG and CMDERR pipes remain as usual).
They can be accessed using three built-in commands: cmdin
, cmdout
and cmdraw
.
Reading from CMDIN
The cmdin
command simply writes in its CMDOUT the value provided in the shell's CMDIN. For instance:
# Considering this shell script accepts `string` values as input.
# If this shell script is called with 'Hello world!' as an input:
echo $(cmdin | length) # Prints: 12
The original type is preserved, which means we can perform typed operations on the input value.
Returning with CMDOUT
The cmdout
command takes a typed value and writes it to the shell script's CMDOUT pipe. This also makes the program exit.
Writing to CMDRAW
The cmdraw
command takes a stream
value and writes it to the shell script's CMDRAW pipe. Only one stream can be piped at a time, so if cmdraw
is called while another is pending, the command will simply fail (this can be caught with catch
).
Input of a command
Some commands accept inputs through the command pipe |
operator. They can be used this way:
echo "Hello world!" > ./somefile.txt
read ./somefile.txt # Prints: "Hello world!"
read ./somefile.txt | length # Prints: 12
How does this work exactly? First, read
reads the file and outputs it to CMDOUT as a string
, which is then passed to length
which happens to accept strings as an input. It then computes the length of the provided input and writes it to CMDOUT, as an int
. Which means we can do that:
echo ${ $(read ./somefile.txt | length) * 2 } # Prints: 24
The input may be typed and only accept specific types of values. For instance length
only accepts strings, so if we try to give it something else:
echo $(pass 2 | length) # ERROR
What happens here is that we use the builtin pass
command which writes to CMDOUT the exact same value we gave it as an input. Then we give it to length
, which fails because it doesn't accept int
s.
There's also a shorthand syntax for providing a file's content as CMDIN to a command:
echo $(length < ./somefile.txt) # Prints: 24
For commands that only accept string
inputs, the file is automatically decoded and converted to a string. Else, it's kept as a stream
.
Running in background
It's possible to run multiple commands in parallel by using background commands. A background command runs, as the name suggests, in the background, and so its output isn't visible. If it fails, it won't generate any error nor affect the status()
code.
To run a command in backgroud, we use the bg
keyword:
bg hello = sleep 5 -x { i -> echo "Counter: $i" }
This will declare an hello
variable and put an int
value inside it, which is the background command's identifier (BGID). The command will be started and run in parallel of the current program. For instance, the following program:
bg hello = sleep 5 -x { i -> echo "Counter: $i" } --end { -> echo "Counter completed!" }
for i in 1..=5
sleep 1
echo "Loop: $i"
end
echo "Loop completed!"
Will print:
Counter: 1
Loop: 1
Counter: 2
Loop: 2
Counter: 3
Loop: 3
Counter: 4
Loop: 4
Counter: 5
Loop: 5
Counter completed!
Loop completed!
It's possible to wait for a background command by making it run back in the foreground:
bg hello = sleep 5 # The command runs in background
fg hello # The commands comes back to the foreground
# The program pauses until it completes like for a normal command
It's also possible to let the command run even when the program finishes with detach
:
bg hello = sleep 5
detach hello
Or to stop the background command with kill
:
bg hello = sleep 5 -x { i -> echo "Counter: $i" }
sleep 3
kill hello
echo "Killed."
### Output:
Counter: 1
Counter: 2
Counter: 3
Killed.
<Program stops>###
Environment variables
While traditional variables are always scoped, environment variables are variables that are provided to the script when it starts, and are forwarded to all scripts the main scripts calls, recursively.
They are mostly used to share configuration between programs.
They are usually set:
- Globally, using Central
- For the terminal, using the terminal application's settings
- For the current script, by providing them when running the script
The third case is the most common, here is what it looks like:
# Without environment variables
./myscript.ns
# With environment variables
with MESSAGE="Hello world!" run ./myscript.ns
The environment variable will then be provided to ./myscript.ns
, and will also be available in all scripts this script calls.
Reading an environment variable
Environment variables cannot be accessed like traditional variables, they must be retrieved through the env
builtin function:
# In "myscript.ns"
let message = env("MESSAGE") # any?
As the variable may not be defined, the function returns a nullable value, so we must check if the variable is indeed defined:
let message = env("MESSAGE")
if none message
fail "MESSAGE environment variable was not provided"
end
Now we are sure that message
is defined, we get an any
value, because we don't know the type of the environment variable. So we must use type assertions for that:
let message = env("MESSAGE")
if none message
fail "MESSAGE environment variable was not provided"
end
if message isnt string
fail "MESSAGE environment variable is not a string"
end
# Here, "message" is a string
Perfect! Note that, if you want to check if the environment variable exists and is of a specific type at the same time, you can skip the first checking, which is only here to perform specific actions in case the environment variable isn't even defined:
let message = env("MESSAGE")
if message isnt string
fail "MESSAGE environment variable was not provided or is not a string"
end
# Here, "message" is a string
Commands typing
For script files to be called as commands, they must define a main
function and declare a command description.
Here is an example of command description:
cmd
author "Me <my@email>" # Optional
license "MIT" # Optional
help "A program that repeats the name of a list of person"
return void
args
# Declare a positional argument named 'names' with a help text
pos "names"
type list[string]
help "List of names to display"
optional
# If this argument is omitted, the command will expect the list of names to be provided through CMDIN
if absent
cmdin list[string]
end
# Declare a dash argument named 'repeat'
dash "repeat"
type int
short "r"
long "repeat"
optional
# Get the time this command took to complete
flag "duration"
short "d"
long "duration"
# Conditional return type
# 'present()' also accepts an optional argument name to check if another argument is present
if present
return int
end
end
Arguments type
Arguments type can be any existing type, or:
anystr
: accepts any type of argument exceptstream
, which will be converted to astring
when the command is called (which means the argument will be astring
from the command's point of view)
Enumerations
The enum
type for arguments indicate the argument only accepts a subset of values (whose type is inferred), which must be specified as a constant. This means the caller cannot use a variable as this argument's value, because the return type may depend on it.
Syntax is: enum[value1 | value2 | value3]
It may also be used as a list of custom values by wrapping the enumeration into a list[...]
.
Return type
The command's return type can be any existing type.
The options for each argument are:
type
: Required, the type of the argument (nullable types are forbidden)help
: A help message indicating what the argument doesshort
: Short name for a dash argumentlong
: Long name for a dash argumentoptional
: Indicate the optional can be omitted (the type will be converted to a nullable one)default
: Make the value optional, but with a default value (so the type will not be nullable)requires
: Indicate one or several other arguments are required to use this dash oneconflicts
: Indicate this dash argument cannot be used when one or several other specific arguments are already in useenum
: Allow only a subset of values
For dash arguments, at least short
or long
must be provided. Also, optional
and default
cannot be provided at the same time.
For flag arguments, at least short
or long
must be provided. Also, type
, optional
, default
and enum
are not accepted.
Conditionals
Conditions can also use the elif
and else
keywords, and use the present()
and absent()
operators as well as usual relational operators like ==
or <
for constant values like enums.
Example
Here is an example that uses all these options:
cmd
args
# ...
dash "repeat"
type int
enum [1 | 2 | 3 | 4]
help "How many times to repeat the names"
short "r"
long "repeat"
default 1
requires "arg1"
conflicts "arg2" "arg3"
end
end
# ...
end
The main
function takes arguments with the same name as described in the cmd
block, and in the same order:
fn main(names: list[string], repeat: int?)
for i in 0..=repeat.default(1)
echo (names.join(", "))
end
end
The script can then be called like any command, with the default $(...)
operator returning the script's return value:
./myscript.ns ["Jack", "John"] -r 1
# or
let result = $(./myscript.ns ["Jack", "John"] -r 1)
Also, know that scripts can fail
too. This allows errors to be handled when the script is run as a function:
# main(names: list[string], repeat: int?) -> fallible
catch $(myscript ["Jack", "John"])
ok _ => echo "Everything went fine :)"
err _ => echo "Something went wrong :("
end
Native library
The native library is a list of functions that are provided by the shell.
All types have extensions, which are functions that can be called using the .
symbol, like my_list.extension()
.
Utilities
env(varname: string) -> any?
Get an environment variable.
Returns null
if the environment variable cannot be found.
prompt(message: string) -> string
Ask the user a string.
prompt_int(message: string) -> fallible int
Ask the user an integer number. Fails if the provided input is not a number. Fails if the shell is not interactive.
prompt_float(message: string) -> fallible float
Ask the user a floating-point number. Fails if the provided input is not a floating-point number. Fails if the shell is not interactive.
confirm(message: string) -> fallible bool
Ask the user to confirm a message using an [Y/n]
prompt.
Fails if the shell is not interactive.
choose(options: list[string]) -> fallible int
Ask the user to pick a value from a list and get the index of the chosen value. Fails if the shell is not interactive.
retry_cmd(cmd: command, retries: int) -> fallible
Run a command and retry it a given number of times if it fails. Fails if the command still fails after all allowed tries.
exit()
Make the program exit.
last_failed() -> bool
Check if the previous command failed.
Returns 0
if no command was ran since the beginning of the script.
rand() -> float
Generate a random floating-point number between 0 and 1.
rand_int(low: int, up: int) -> fallible int
Generate a random integer between low
and up
.
Fails if low
is not strictly less than up
.
rand_float(low: float, up: float) -> fallible float
Generate a floating-point number between low
and up
.
Fails if low
is not strictly less than up
.
All types
any.str() -> string
Turns the provided value into a string, depending on the value's type:
_ = (true).str() # true
_ = (3).str(3) # 3
_ = (3.14).str() # 3.14
_ = ('B').str() # B
_ = ("Yoh").str() # Yoh
_ = @{ command --arg1 -c 2 -d=4 }.str() # command --arg1 -c 2 -d 4
_ = ["a","b"].str() # [ "a", "b" ]
_ = @{ streamify "Hello world!" }.str() # "<stream>"
Nullable types
T?.isNull() -> T
Check if the value is null
.
let a: int? = 1
let b: int? = null
echo ${a.isNull()} # false
echo ${b.isNull()} # true
T?.default(fallback: T) -> T
Use a fallback value in case of null
:
let a: int? = 1
let b: int? = null
echo ${a.default(3)} # 1
echo ${b.default(3)}
T?.unwrap() -> T
Make the program exit with an error message if the value is null.
let a = ?(0) # int?
let b = a.unwrap() # int
T?.expect(message: string) -> T
Make the program exit with a custom error message if 'a' is null
let a = ?(0) # INt?
let b = a.expect("'a' should not be null :(") # int
Characters
char.single() -> bool
Indicate if a character is made of a single codepoint.
char.codepoints() -> list[int]
Get the codepoints composing a character.
char.len() -> int
Get the number of codepoints composing a character.
char.bytes() -> int
Get the size of a character, in bytes.
Strings
string.chars() -> list[char]
Get the characters composing a string.
string.codepoints() -> list[int]
Get the codepoints composing a string.
string.len() -> int
Get the number of codepoints composing a string.
string.bytes() -> int
Get the size of a string, in bytes.
string.parse_int(base = 10) -> fallible int
Tries to parse the provided string as a number in the provided base.
Leading zeroes are accepted.
0
symbol followed by b
, o
, d
or x
is accepted for bases 2, 8, 10 and 16 respectively.
Fails if the string does not represent a number in this base.
_ = ("2").parse_int() # 2
_ = ("A").parse_int() # <FAIL>
_ = ("A").parse_int(16) # 11
string.parse_float(base = 10) -> fallible float
Identical to string.parse_int(base)
but with floats.
string.upper_case() -> string
Convert a string to uppercase.
_ = ("aBc").upper_case() # "ABC"
string.lower_case() -> string
Convert a string to lowercase.
_ = ("aBc").lower_case() # "abc"
string.reverse() -> string
Reverse a string.
_ = ("abc").reverse() # "cba"
string.concat(right: string) -> string
Concatenate two strings (equivalent to "$left$right"
).
_ = ("a").concat("b") # "ab"
string.split(str: string, sep: string) -> string
Split a string into a list.
split("ab", "") # [ "a", "b" ]
split("a b", " ") # [ "a", "b" ]
Lists
list[char].stringify() -> str
Turns a list of characters to a string.
_ = ([ 'a', 'b', 'c' ].str() == "abc") # true
list[string].join(sep = ",") -> string
Join a list of strings with a separator.
join([ "a", "b" ]) # "a,b"
join([ "a", "b" ], "; ") # "a; b"
list[T].get(index: int) -> T?
Try to get an item from the list, without panicking if the index is out-of-bounds.
let names = [ "Jack", "John" ]
names.get(0) # "Jack"
names.get(1) # "John"
names.get(2) # null
list[T].expect(index: number, message: string) -> T
Get an item from the list, and panic with a custom error message if the index is out-of-bounds.
let names = [ "Jack", "John" ]
names.expect(2, "Third item was not found")
list[T].unshift(value: T)
Insert a new value at the beginning of the list.
list[T].push(value: T)
Push a new value at the end of the list.
list[T].unshift() -> T?
Remove the first value from the list and return it.
list[T].pop() -> T?
Remove the last value from the list and return it.
list[T].sort(asc = true) -> list[T]
Sorts a list.
_ = [ 2, 8, 4 ].sort() # [ 2, 4, 8 ]
_ = [ 2, 8, 4 ].sort(false) # [ 8, 4, 2 ]
list[T].reverse() -> list[T]
Reverse a list.
_ = [ 2, 8, 4 ].reverse() # [ 4, 8, 2 ]
list[T].len() -> int
Count the number of entries in a list.
_ = [ 2, 8, 4 ].len() # 3
list[T].concat(another: list[T]) -> list[T]
Concatenate two lists.
_ = [ 1, 2 ].concat([ 3, 4 ]) # [ 1, 2, 3, 4 ]
list[T].concat(lists: list[list[T]]) -> list[T]
Concatenate multiple lists.
_ = [ 1, 2 ].concat([ [ 3, 4 ], [ 5, 6 ] ]) # [ 1, 2, 3, 4, 5, 6 ]
Maps
map[K, V].has(key: K) -> bool
Check if a key exists in a map.
map[K, V].get(key: K) -> V?
Get a value without panicking if the key doesn't exist.
map[K, V].keys() -> list[K]
Get all keys of a map.
map[K, V].values() -> list[V]
Get all values of a map.
map[K, V].values() -> list[struct { key: K, value: V }]
Turn a map into a list.
map[K, V].expect(key: K, message: string) -> V
Get an item from the map, and panic with a custom error message if the key doen't exist.
map[K, V].delete(key: K) -> bool
Delete a key, returns false
if the key didn't exist in the map, or true
otherwise.
map[K, V].count() -> int
Count the number of entries in a map.
Commands
command.run() -> int
Run the command and gets its status code after exit.
command.fallible()
Run the command and fail if the status code after exit is not 0.
Equivalent to calling the command with simple parenthesis like cmd()
.
command.ret_str() -> string
Run the command and get its stringified return value.
command.cmdraw() -> stream
Run the command and get its CMDRAW output.
command.cmdmsg() -> list[string]
Run the command and get its CMDMSG output.
command.cmderr() -> list[string]
Run the command and get its CMDERR output.
command.output() -> list[string]
Run the command and get its CMDOUT and CMDERR outputs combined.
Streams
stream.pending() -> bool
Check if the stream is still pending. If the pipe is complete (which means if its pipe is closed), false
will be returned.
stream.size_hint() -> int?
Get the stream's size hint. If no size hint was provided for this stream, null
will be returned.
Examples
Guess The Number
while true
let won = false
let secret = rand_int(0, 100)
echo "Secret number between 0 and 100 has been chosen."
while !won
let user_input = retry prompt_int("Please input your guess: ")
if user_input < secret
echo "It's higher!"
elif user_input > secret
echo "It's lower!"
else
echo "You guessed the number \\o/!"
won = true
end
end
tries = retry(5) confirm("Do you want to play again?")
if !(retry(5) confirm("Do you want to play again?"))
echo "Goodbye!"
break
end
end
Native commands
The native commands are commands that are available in every program without import.
echo
: display a value
Display a value to CMDOUT.
# echo [-n] { <anystr> | -b <stream>}
#
# "-b": print a stream as UTF-8
# "-n": don't put a newline break at the end of the content
echo "Hello world"
echo -n "Hello world!"
echo -b $(streamify "Hello world!")
wt
: write a file
Write a content to a file.
# wt [-a] [-n] [-c] <path> <anystr>
#
# "-a": append to the end of the file
# "-n": don't append a newline symbol at the end of the content
# "-c": fail if the file doesn't exist instead of creating it
Note that sometimes it can be clearer to use a redirection pipe:
# Overwrite file
echo "Hello world!" > ./test.txt
# Append to file
echo "Hello world!" >> ./test.txt
rd
: read a file
Read a file as a string
value.
# rd [-s] [--stream-size <int>] <path>
#
# "-s": read a stream instead of reading the full content
# "--stream-size": specify the size of the stream when "-s" is provided (rounded to higher pipe buffer multiplier)
mkdir
: create a directory
Create a new directory.
# mkd [-s] <path>
#
# "-s": fail if the directory already exists
ren
: rename a filesystem item
Rename a filesystem item.
# re [-m] <oldname: path> <newname: path>
#
# "-m": move if the 'newname' is located inside another directory
mv
: move a filesystem item
# mv [-n] <file: path> <dest_dir: path>
#
# "-n": create the target directory if it does not exist
rm
: remove a filesystem item
Remove a filesystem item.
# rm [-r | --recursive] [-n | --non-empty] [-t | --trash] {<path> | -l <list[path]>}
#
# "-r": allow removing empty directory
# "-n": allow removing even non-empty directories
# "-l": remove a list of paths
# "-t": move the item to the user's trash
ls
: list filesystem items
List filesystem items.
# ls [-t | --tree] [-r | --recursive] [-h | --hidden] [--file-only | --dir-only] [<path>]
#
# "-t": display as a tree (implies "-r")
# "-r": list recursively
# "-h": list hidden files
# "--file-only": only display files
# "--dir-only": only display directories
fd
: find filesystem items
Find filesystem items matching provided criterias.
# fd [-t | --types enum["dir" | "file" | "symlink" | "device"]]
# [-a | --absolute]
# [-L | --follow]
# [-e | --extension <string>]
# [-n | --name <string>]
# [-r | --regex <string>]
# [-i | --ignore-case]
# [-x | --exec <command>] <path>
#
# "-t": only list items of a given type (by default, only files and symlinks are shown)
# "-a": list absolute file paths instead of relative ones
# "-L": follow symbolic links
# "-e": only list files with the provided extension (directory will be excluded)
# "-n": only list items whose name contain the provided string (`^` and `$` can be used for indicating items must start or end by it)
# "-r": only list items matching a provided POSIX regex
# "-i": ignore case (requires "-n" or "-r")
# "-x": execute a command for each found item
syml
: manage symlinks
Manage symbolic links.
# sl {-r | --read} <path>: check a symlink's target
#
# sl [-u | --update] <srcpath> <targetpath>: create a symlink
#
# "-u": update the symlink to the new target path if it already exists instead of failing
Crash Saves
Crash saves is a major feature of NightOS. It consists in collecting the state of running applications and saving it for later restore in case of application or system crash.
Crash saves is handled through the sys::crashsave
service.
Applications support
To support crash saves, applications must opt-in through an option in their manifest.
Crash saves collection
Every once in a while (once per minute by default), a CRASH_SAVE_COLLECTION
notification is sent to the application, which is expected to write a state it will be able to restore later on in a temporary file provided by the sys::crashsave
service.
Crash saves restoration
If a crash save must be restored (occurs after an application is opened for the first time following a detected crash), a RESTORE_CRASH_SAVE
signal is sent to the application with a temporary file containing the state it previously wrote in, as well as a few other informations.
The application can then decide if the crash save file must be removed or not.
Crashes detection
Invalid shutdown indicator
When the system starts up, it creates an empty file in /etc/sys/awake
.
When the system shutdowns gracefully, this file is removed.
If, during startup, this file already exists, the system hasn't shutdowned gracefully. When this happens, a dialog message is shown, suggesting to re-open crashed applications with their last crash save.
For each application that do not have an available crash save (e.g. when the system crashed before a crash save could have been collected for this application), an indicator in the dialog box will show this application cannot be restored.
Application's crash indicator
When an application starts, the system creates an empty file in /home/[user]/appdata/[appname]/crashsaves/awake
for user applications (even for global applications).
When the application exits gracefully, the file is removed automatically. When the system detects the application exited, if this file still exists, it shows a dialog box asking if the user wants to relaunch the application with the last crash save.
NOTE: If there is no available crash save (e.g. when the application crashed before a crash save could have been made), the dialog box will simply show the application crashed.
NOTE: Because a crash save could have been collected for several instances of an application, they can all be restored afterwise.
Restoration process
When a crash save is attempted to be restored, the sys::crashsave
service. When the application is ready, a SYS_CRASHSAVE_RESTORE
confirmable signal is sent, with the application's crash save.
The application is expected to confirm the signal when it has finished restoring its state using the crash save.
The crash save is not deleted directly, though. It is renamed using the new instance's PID and kept until the next collect process receives a new crash save for this instance.
If the application crashes before another crash save can be made and didn't confirm the restoration signal, when this process happens again, system will indicate the application appear to have crashed during crash save restoration.
Services
This document describes the architecture of services, as well as the list of all system services and their main features.
Types of services
An application can expose four types of service:
- A main service, which is used as the default one
- Scoped services, which are used as interfaces for conventional tasks between applications which are not specific to the current one
- Integration services, which are used to interact with the system in specific ways (see the complete list)
- Driver services, which are used to interact with hardware devices through the
sys::hw
service.
A scope's name is made of up to 8 extended ASCII characters.
Applications exposing integration services get special recognition for the tasks associated to these services. These services are not available directly to the end applications ; they can only be used through system services.
Architecture of a service
Applications have exactly one running process per service, and each service has exactly one running instance per active user, to prevent a user to connect to the service of a user with more privileges than itself. System services, on their side, only have one global instance.
An application process can tell if it was started as a service or not by looking at its execution context.
Once a service process indicates it's ready using the READY
syscall, other processes will be able to connect to this service.
Connections
A connection essentially consists of a service socket between a service and another process called its client.
When a process wants to connect to a service, it uses the CONNECT_SERVICE
to send a connection request to this service.
The service process then receives the request through the SERVICE_CONN_REQUEST
.
It is expected to answer under a short delay specified in the registry's system.processes.service_answer_delay
key (2000 ms by default).
If the service refuses the connection, it provides its answer through the REJECT_SERVICE_CONN
syscall, and the procedure stops here.
Else, it accepts it through the ACCEPT_SERVICE_CONN
syscall. A new thread is then created in the service's process, and the signal returns with a code indicating if the current thread is the one that was just created.
Communication
The service and its client(s) communicate through the service socket created during the connection.
The different message formats a client can send to a service are a called the service's methods, while the different message formats the service may send to a client are called the service's notifications.
Thread types
A service process' main thread is called its dispatcher threads, while threads created using the ACCEPT_SERVICE_CONN
syscall are called client threads.
Closing a connection
The service cannot terminate a connection by itself.
If a client thread terminates brutally, the SERVICE_SERVER_QUITTED
signal will be sent to its client.
Only clients can properly close a connection to a service, using the END_SERVICE_CONN
syscall. The service socket is then immediatly closed (on both the client and the thread's side).
The service then receives the SERVICE_CLIENT_QUITTED
signal.
If the client terminates brutally (before the connection was properly ended), the client thread receives the SERVICE_CLIENT_CLOSED
signal.
When a client thread receives on these two signals, it is expected to end as soon as possible (though there is no time limit).
System services
System services are run by the system and get access to special system calls and signals that aren't available otherwise.
They also have a specific process identifier (PID) to identify them easily.
You can find the list of all system services in the related directory.
Third-party communication
An application can enable communication to and from other applications using a service.
For instance, let's consider two applications, A and B. If A wants to be able to be contacted by other applications (such as B), it sets up a service and subscribes to it. Then, when B sends a message to the service, it is forwarded to A through a notification by the service itself.
It can also work the other way: B subscribes to the service, and when A wants to contact B, it simply sends a message to B.
The downside of this structure is that latency is approximately doubled compared to a direct A-B communication. But as they use messages and notifications, which are based on hardware interrupts, the latency is extremely low, mainly composed of the time the kernel spends to ensure the communication between A and the service and then the service and B (or the other way around) is allowed.
Although this method works fine for small pieces of data, it isn't suited for transmitting large chunks in a performant way. For that purpose, A can share a memory segment with the service, who shares it in turn to B (with restricted permissions if required).
Note that this only applies to different applications ; processes from a same application can communicate directly through IPC.
Translations
Translations are a complex topic and can be handled in several ways.
Applications' and libraries' content can either be translated using a custom way (e.g. a translation framework), or by using the system's builtin translation library, i18n
.
Translation sets
Translation sets are a way to specify how to translate a set of content.
They use an extremely compact and efficient format to minimize disk usage and maximize performances. Some trade-offs are made to accelerate performance at the expansve of file size.
They are not made to be modified directly, and should instead be derived from other text formats if the translation sets need to be manually edited by a person.
Header
The header is made of the following informations:
- Number of translatable languages (2 bytes)
- For each language:
- Language's code in ASCII (3 bytes, using the ISO 639-2 format)
- Location of the language's translation table in the file (8 bytes)
- Code of the reference language(3 bytes)
- Number of translation models (8 bytes)
- For each translation model:
- The translation model itself
The reference language is the one the translations are made from. For instance, a translation set may be procuced by translating english models to french. But when the set is bundled, both english and french will actually be written in their own translation tables, meaning we can't easily reverse the process. The reference language is here to ensure that we can automatically determine which language was used to create the translations. It can be also be used in debug messages in case something goes wrong.
Translation models
A translation model is a translatable string. They can use variables which are provided at runtime.
It only consists in a variable set declaration.
Translation models contain no text as that is the role of the translation strings, which means that decoding a translation set will not allow to directly see what the source text is, as there is in fact no text in the models.
Most translation tools provide a way to start from a source language (like english) and then translate to other languages, but when the file is actually bundled, the source language is just written as any other inside its own translation table.
Variable set declaration
A variable set declaration of made of the following:
- Number of variables in the set (2 bytes)
- For each variable:
- Unique identifier of the variable (4 bytes)
- Variable type (1 byte):
0x00
: delimited string (string
)0x01
: boolean (bool
)0x02
: 64-bit signed integer (int
)0x03
: 64-bit unsigned integer (uint
)0x04
: 64-bit floating-pointer number (float
)
Translation tables
A translation table represents the translation of each defined model for a provided language.
It consists in the following:
- For each model defined in the header:
- Relative address of the translation string in this table
- For each model defined in the header:
Translation strings must be in the same order as defined in the header.
Translation strings
A translation string is the dynamic translation, for a given language, of a translation model.
They use a complex format as they permit to achieve both compacity in term of file size, high performance when decoding and actually performing the translations, as well as allowing complex translations. It is for instance possible to link together multiple conditionals to only show a text if a set of provided numbers meet a requirement.
Translation strings use the following structure:
- Length of the translation string, in bytes (4 bytes)
- Assignable variables set
- Number of direct translation segments (4 bytes)
- Relative address of the dynamic library (4 bytes)
- For each translation segment:
- Number of optional translation segments in the dynamic library (4 bytes)
- For each optional translation segments:
- Unique identifier for this segment (4 bytes)
- Translation segment
Translation segments
- Length of the segment in bytes (4 bytes)
- Segment type (1 byte):
0x00
: fixed text- Followed by a delimited string
0x01
: toggle- Followed by the identifier of a boolean variable (4 bytes)
- Followed by the ID of the optional translation segment to use if the variable is truthy
- Followed by the ID of the optional translation segment to use if the variable is falsy
0x02
: comparison- Followed by the ID of the variable to compare (must be of a number type)
- Comparator (1 byte):
0x01
: equal0x02
: not equal0x03
: greater than0x04
: less than0x05
: greater than or equal to0x06
: less than or equal to
- Comparison object (1 byte):
0x01
: direct value- Followed by the raw number (must be of the same number type)
0x02
: variable- Followed by the ID of the variable to compare it to (must be of the same number type)
- Followed by the ID of the optional translation segment to use if the comparison is truthy
- Followed by the ID of the optional translation segment to use if the comparison is falsy
0x03
: property checking- Followed by the ID of the variable to check
- Negation (1 byte)
0x00
: perform the check normally0x01
: revert the check's result
- Check (1 byte):
0x01
: (string
-only): string is empty0x02
: (string
-only): string only contains ASCII characters0x20
: (int
-only): number is positive0x21
: (int
-only): number could be converted to auint
without loss0x30
: (uint
-only): number could be converted to anint
without loss0x31
: (uint
-only): if the number was signed as anint
, it would be negative0x40
: (float
-only): number has a non-zero decimal part0x41
: (float
-only): number could be converted to anint
without loss0x42
: (float-only
): number could be converted to auint
without loss
- Followed by the ID of the optional translation segment to use if the variable is truthy
- Followed by the ID of the optional translation segment to use if the variable is falsy
0x04
: assignments- Identifier of the assignable variable (4 bytes)
- Action type (1 byte):
0x00
: Raw assign- Followed by the value to assign (must be of the exact same type)
- Followed by the identifier of a variable (4 bytes)
0x01
: (bool
-only) Result of a comparison- Followed by a comparison's data (type, comparator, ...)
0x02
: (bool
-only) Result of a property checking- Followed by the check's data (negation, check, ...)
0x10
: (uint
-only): length of a string, in bytes- Followed by the identifier of a
string
variable
- Followed by the identifier of a
0xFF
: empty (used for conditionals)
Kernel
NightOS' kernel is named Cosmos. It is a micro-kernel which tries to be as simple and straightforward as possible, delegating all non-trivial tasks such as filesystem access or permissions management to services.
NOTE: This document is in its very early stages, and so is far from being complete. Major changes may and will be made to related documents.
Documents
- Hardware - how the kernel interacts with hardware
- Memory - memory organization and management
- Processes - processes concept and management
- Scheduling - tasks scheduling
- Data structures - data structures used by the kernel to represent things in memory
- Kernel-process communication - how the kernel communicate with processes and vice-versa
- Inter-process communication - communication between processes
- Signals - complete specification of signals
- System calls - complete specification of system calls
Hardware management
This document describes how the kernel interacts with hardware.
- Hardware detection
- Connection interface identifier
- Connection-specific device descriptor
- Kernel device identifier
- Raw device descriptor
- Input/output ports
- Drivers
- Kernel communication
Hardware detection
Devices are detected during the boot process and then periodically after startup. This allows to hotplug some additional devices afterwards.
As all devices do not use the same connection protocols, the detection process depends on the connection:
- PCI-Express devices are detected through their Configuration Space
- IDE/SATA devices are detected through the IDE/SATA controller
- USB devices are enumerated through the USB protocol stack
- Motherboard-connected devices are enumerated through the BIOS/UEFI (e.g. CPU and case fans)
Some devices may not be detected through these though, such as some legacy ISA devices, which will be detected through a set of methods like ACPI enumeration or simply checking UART serial ports.
Connection interface identifier
The connection interface identifier (CII) is a 4-byte number describing what a device is connected to:
- Connection type (1 byte):
0x01
: PCI-Express0x02
: IDE0x03
: SATA0x04
: M.20x05
: USB0x06
: RGB0x07
: Fans
- Bus number (1 byte)
- Port number (2 bytes)
For instance, the seventh USB port on the second bus will have the 0x05010006
CII.
Connection-specific device descriptor
All hardware devices expose a normalized identifier whose format depends on the connection type (PCI-Express, SATA, ...). This identifier is called the connection-specific device descriptor (CSDD).
Its size can vary up to 256 bytes.
Kernel device identifier
The kernel device identifier is an 8-byte identifier computed from the CII and the CSDD. It is unique across all devices, consistent across reboots, and identical from one computer to another for the same device. It is only meant for internal use by the sys::hw
service, which generates an UDI for external use.
Raw device descriptor
The raw device descriptor (RDD) is a data structure (up to 260 bytes) made of the followings:
This descriptor is then used by the sys::hw
service to expose the device to the rest of the operating system.
Input/output ports
The kernel first detects the I/O ports used by each device, and maps it to the device's KDI.
They are uni-directionals and as such are split in two categories: input ports and output ports.
Input ports are used by devices to transmit informations to the kernel. When this happens, the data is put on a stack, and drivers can then retrieve it using the READ_IO_PORT
system call.
Output ports are used by drivers to transmit informations to devices, using the WRITE_IO_PORT
system call.
I/O ports identifiers
I/O ports are accessed using relative identifiers, which corresponds to the nth port of the physical device, meaning it starts to 0
and ends to <number of ports in the device> - 1
. The kernel transparently translates the relative identifier to the real port number.
The association with the real port number is transparently performed by the kernel.
Drivers
A driver is a process which can directly communicate with hardware devices using the dedicated system calls.
They communicate with external processes through the sys::hw
system service, or sys::fs
when direct driver access is possible.
You can find more about how drivers work in this section.
Direct communication with device hardwares is made through system calls.
The sys::hw
service is considered as a driver for all devices.
Kernel communication
The kernel does not identify devices, as this task is relegated to the sys::hw
service. In order to communicate with hardware devices, only the CII is provided.
Memory
This document describes how the kernel organizes and manages the memory.
WARNING: This document is only a short draft and is missing a LOT of informations on how the memory will be structured and managed.
Structure
Every running code that is not part of the kernel itself doesn't have access to the physical memory. Instead, every process has a virtual 64-bit address space split into pages.
Pages
A page can either be small (usually 4 KB) or large (usually 2 or 4 MB), the exact sizes depending on the CPU architecture.
Pages can be allocated through the dedicated syscall syscall, or mapped using abstract memory segments.
Abstract memory segments
An abstract memory segment (AMS) is an identifier which refers to a segment of memory which doesn't actually exist. To be used, they must be mapped in a process' memory to be accessed like regular memory. The kernel then intercepts all memory accesses to these mappings and handle them, depending on their nature which cover three cases:
- Mapping existing memory pages to others, or sharing them with other processes ;
- Mapping a device's memory into the process' own memory space ;
- Making a virtual memory space handled by signals
An AMS can then be mapped at multiple places in a process' memory, or shared with other processes. The kernel handles mappings to get optimal performances and reduce the number of memory accesses as much as possible.
Addresses randomization
Allocations happen at random addresses using address space layout randomization (ASLR).
Kernel memory's randomization using processes like KASLR or KARL are currently being considered.
Write-or-exec
Memory implements a W^X model where memory can be writable or executable, but not both. This way, an attacker cannot write arbitrary instructions in memory and then execute them.
Processes
Apart from the kernel itself, all programs run in processes.
- Why processes
- Types of processes
- Process identifier
- Process attributes
- Drivable devices
- Raw permissions
Why processes
Separating programs in threads presents several advantages:
- This allows to take advantage of multi-core architectures by running multiple programs in parallel
- Each program has its own data space and does not share its data with other programs
- Each process has its own permissions and thus cannot bypass what the user chosen
Types of processes
There are two types of processes:
- System services, which are handled by the system and have a fixed PID
- Userland processes, which are application processes
This implies it is not possible to run an executable as standalone in its own process.
Process identifier
Each process is identified by a 64-bit number, which is guaranteed to be unique until the system restarts. When a process terminates, the PID is not freed. The PID is not randomly generated, but retrieved from a system-wide counter incremented when a new process starts. PID cannot be equal to 0.
System services can be identified by a PID strictly smaller than 100, while userland processes have a PID greater or equal to 100.
Process attributes
Each process has a set of attributes which contains critical informations on it (lists are usually PLL):
- PID (8 bytes)
- Priority (1 byte)
- System marker (1 byte) to indicate if the process comes from a system program
- Container's ID (8 bytes)
- Running user's ID (8 bytes)
- Parent application ANID (8 bytes) -
0
for system services - Pointer to the execution context (8 bytes) -
0
for system services PLL(e=32)
for memory mappingsPLL(e=32)
for raw permissionsPLL(e=32)
for drivable devices
Drivable devices
The drivable devices attribute contains the list of all devices' KDI the current process can drive.
The goal of this attribute is to determine if the process is allowed to map a device's memory by creating an AMS from it using the DEVICE_AMS
syscall, as well as using DMA-related instructions in the CPU.
This attribute is managed by the sys::hw
service and can only be updated by it.
Raw permissions
Raw permissions are used by system services to determine the permissions of a process without sending a message to the sys::perm
service and waiting for its answer, which would be costly in terms of performance.
These permissions use a specific structure, specified in the related service's specifications document.
Permissions can be managed using the sys::perm
service.
Scheduling
This document presents how tasks are scheduled internally.
Performance balancing
Each process has a priority number, between 1 and 20, which indicates how much its performances must be prioritized compare to other processes.
Scheduling is achieved in a preemptive way, to prevent processes from taking too much CPU time.
The basics can be found here.
More specifically, the higher the priority of a process is, the faster it will run. Here are the priority-dependant aspects of a process:
- Number of instructions run per cycle
- Priority when accessing I/O through services
Comparatively, when a process has a high priority, other processes will run a tad slower.
Cycles and context switching
To run processes, the kernel simply iterates over the list of existing processes, and allow them to run a given number of instructions. Then, the control is taken back by the kernel which runs the next process, and so goes on.
This happens as follows:
- A process is selected
- If the process is currently suspended, it is ignored
- Its registers are restored by the kernel (if any)
- The process runs a given number of instructions
- The kernel takes back the control of the CPU
- The process' registers are saved
- Go to step 1
These steps are known as a cycle.
Automatic priority attribution
Processes' priority is automatically adjusted by the kernel, unless it is manually assigned through the SYS_PROCESS_ATTRIBUTES
syscall.
The priority is determined based on multiple factors:
- Does the process have a fullscreen window?
- Does the process owns the active window?
- Does the process owns a visible window?
- Is the process a driver or service? If so, how much is it used?
Data structures
This documents describes the structures used by the kernel to represent the data it uses in memory.
All contiguous data structures are made such as it is easy to delimit them by computing their size in the memory.
- Data structures
Booleans
A boolean is a single byte value that's either 0x01
for true
or 0x02
for false
.
Timestamps
Timestamps are stored as milliseconds, starting from January 1st, 1970. This is to guarantee interopability with existing algorithms using Unix's EPOCH constant.
They are represented as an 8-byte unsigned integer number.
Delimited lists
Delimited lists are made of:
- Their length in bytes (8 bytes)
- Their content
Each element must be delimited.
Delimited strings
Delimited strings are made of:
- Their length in bytes (8 bytes)
- Their content (UTF-8 encoded)
Buffer pointers
A buffer pointer refers to a buffer that is either readable, meaning its creator process has read permission on its entire memory location, and/or writable, meaning its creator process has write permission on its entire memory location.
It is made of:
- The memory address of the buffer (8 bytes)
- The buffer's length (8 bytes)
Options
An option is a data structure that may contain a specific data type or nothing.
It is made of a variance byte, set to 0x01
followed by the data if any, or a single 0x00
byte to indicate no data is present.
Fallible results
A fallible result is a data structure describing a fallible operation's result. It starts by a variance byte to indicate its result type:
- Either
0x00
to indicate the operation was successfully, followed by the success data - Or
0x01
to indicate something went wrong, followed by the error data
Bitmap images
Bitmap images are represented as a header and a pixel list.
The header is composed as a suite of 8 bytes:
- Image width (IW), in pixels (2 bytes)
- Image height (IH), in pixels (2 bytes)
- Number of colors (power of 256) for the red channel (NR),
0
if unused (1 byte) - Number of colors (power of 256) for the green channel (NG),
0
if unused (1 byte) - Number of colors (power of 256) for the blue channel (NB),
0
if unused (1 byte) - Number of colors (power of 256) for the alpha channel (NA),
0
if unused (1 byte)
The pixel list is made of the data for each pixel, contiguously.
Each pixel is encoded as follows:
- Value for the red channel (NR bytes)
- Value for the green channel (NG bytes)
- Value for the blue channel (NB bytes)
- Value for the alpha channel (NA bytes)
As shown above, if the number of colors is set to 0
for a specific channel in the header, it must not be contained in the pixel's content. The number of bytes used for each channel of a pixel is equal to the number provided for this specific channel in the header, allowing for 256 power <number in the header>
different colors.
Pixels are listed from the top left corner of the image to the bottom right corner. They are always square.
The size of pixel list can be calculated as IW * IH * (NR + NG + NB + NA)
bytes. Add another 8 bytes for the header.
Bitmap videos
Bitmap videos are represented as a header and a frame list.
The header is composed as a suite of 16 bytes:
- Frames width (IW), in pixels (2 bytes)
- Frames height (IH), in pixels (2 bytes)
- Number of frames per second (1 byte)
- Number of frames (4 bytes)
- Number of colors (power of 256) for the red channel (NR),
0
if unused (1 byte) - Number of colors (power of 256) for the green channel (NG),
0
if unused (1 byte) - Number of colors (power of 256) for the blue channel (NB),
0
if unused (1 byte) - Number of colors (power of 256) for the alpha channel (NA),
0
if unused (1 byte) - Future-proof (3 bytes)
Each frame is a bitmap image without the header.
All frames will inherit the informations stored in the video's header (width, height, color channels).
Packed linked lists
Packed linked lists (PLL) are linked lists used for items whose size is both small (usually <= 32 bytes) and fixed for all items.
It uses a system of same-size entries, each containing a micro bump allocator.
The goal of a PLL is to provide a blazing fast read and iteration speed, while compromising on insertion and deletion speeds.
Caracteristics
A PLL is caracterized by its item size (in bytes), number of items per entry (NIE, up to 255) and its length (the number of items in the list), which is the number of items which can be stored per entry. It is noted PLL(e=<number of items per entry>[, s=<item size in bytes>][, l=<length>])
.
Structure in memory
Each entry is a contiguous suite of bytes which can store up to NIE
items contiguously. It starts by either a pointer to the next entry (on 8 bytes), or the number of items actually initialized in the current entry (pre-filled with zeros to be stored on 8 bytes).
For instance, let's take a PLL(e=3, s=2, l=4)
. Its content is the four following items:
0xDEADBEEF
0x01234567
0x89ABCDEF
0xBEEFDEAD
If the first entry is located at address 0x00001000
and the second at address 0x00002000
, here is the PLL's representation in memory with big-endian representation (with _
representing garbade data):
0x0000000000001000: 00 00 20 00 DE AD BE EF 01 23 45 67 89 AB CD EF
0x0000000000002000: 00 00 00 01 BE EF DE AD __ __ __ __ __ __ __ __
As you can see, the first entry contains the address to the next entry, followed by the content of the first three items (contiguously).
The second entry is the last one and so simply contains the number of initialized items, followed by the last item's content, and then garbage as this memory zone is not initialized yet.
Checking an entry's type
To check if the first byte of an entry is the next entry's address or the number of initialized items, we simply have to perform a simple comparison: if the byte is greater than 0xFF
(255 in decimal, which is the maximum allowed number of items per entry), then it's an address, else it's a number of initialized elements.
The ratio
A PLL also has a ratio, which is the number of bytes reserved for items in each entry, divided by the total number of bytes. So, in our example, 3 items per entry * 4 bytes = 12 bytes, while the total number of bytes per entry also takes into account the address itself, so 12 bytes + 8 bytes = 20 : our PLL's ratio is 0.6
.
This is quite a low ratio, meaning we waste a lot of space. The ratio must be kept as near as 1.0
as possible, while maintaining a reasonable memory footprint for each entry.
Performances bottlenecks
A thing to keep in mind is that PLL have a considerable performances bottleneck: when an entry is filled, the next must be allocated with a size that's a lot larger than a single item's size. That's why, when an entry is full, it should be allocated on the moment time is the less critical.
Performances are especially bad when inserting new items in the list or when removing ones, as many data needs to be moved around.
Regarding updating elements, this requires to write both the item itself. Inserting elements in a not-yet full entry requires to write the element itself as well as incrementing the entry's counter.
Performance advantages
As you can see in the above bottlenecks, PLL are not meant to be performant on writings. They are meant to be fast for reads, especially sequential reads. The higher the NIE
of the list, the fastest it will be to find an element in it, or to read every element of the list one by one (iteration).
Also, as the counter is packed with the other data of each entry, it presents a reduced risk of cache miss.
Computing the length is reasonably fast.
Length-first variant
There is a variant of the PLL that stores the total length of the list somewhere in the memory. In that case, the first byte of the last entry does not need to store the number of items in the entry.
This variant is interesting being we can instantly get the number of items in the list, but the downside is that reading sequentially the list will also incur an additional reading to know where the last entry ends, and updating the total length incur a high risk of cache fault as the chances of the memory area where the length is as well as the entry itself be in the cache at the same time are pretty low.
The length-first variant should only be used when accessing the length instantly is critical.
Tweaking the NIE
Increasing the NIE will:
- Reduce the needs of allocating
- Speed up iteration times
- Speed up insertion times when the last entry isn't occupied
But also:
- Increase the memory cost of the last entry when it isn't occupied
- Slow down insert times when the last entry is occupied (need to allocate)
Unions
Unions allow to construct multiple types of data in a single one.
It is made of a type ID (1 byte) followed by the data.
For instance, if we want to store either a string or a list, we can associate type ID 0 to the former and type ID 1 to the latter, then append the actual string or list to it.
Enumerations
Enumerations allow to switch between behaviours using a unified data type.
It is made of a variant ID (1 byte) followed by the data (which can be of any fixed type).
Kernel-Process Communication
The Kernel-Process Communication (KPC) describes how a process can interact with the kernel, and vice-versa.
There are two types of KPC:
- System calls, which are used by a process to ask the kernel to perform an action ;
- Signals, which are used by the kernel to send informations about an event to a process
Note that, unlike many operating systems like Linux, it's not possible for a process to send a signal to another. Only the kernel is allowed to emit signals.
For more advanced features, like permissions management or filesystem, check IPC.
Inter-Process Communication
This document describes the way Inter-Process Communication (IPC) works.
Pipes
Inter-process Uni-directional Channels (IUC), also called pipes, allow two distinct processes to communicate.
Pipes are made of two uni-directional parts:
- A sender channel (SC) which can only be written to (write-only)
- A receiver channel (RC), which can only be read from (read-only)
The two processes sharing a pipes are:
- The sender process, which uses the SC to send data to the other process
- The receiver process, which uses the RC to retrieve data sent by the other process
The process that creates the pipe gets both the SC and the RC, and is expected to provide one of them to another process.
Each SC and RC has a unique identifier, which is binded to the process that created it.
The process receiving an SC or RC receives another identifier for it, unique to that process, which prevents IDs collisions.
Opening pipes
A process can open a pipe with another process using the OPEN_PIPE
syscall.
The other process will then receive the RECV_PIPE
signal. If no handler is set when the signal is sent, the opening syscall fails.
Pipes' pending data
When a pipe is written to, the data is written to a memory zone. This zone is called the pipe's buffer and it's content is called the pending data.
When a pipe is read from, the pending data is progressively retrieved, erased as the read progresses.
The default size of the pipe's buffer is 64 KB, but this can be extended to up to 16 MB during its creation. When it is reached, no data can be written to the pipe anymore, meaning the other process must read data from it in order to free space to write it.
Pipes locking
When a pipe is being written to or read from, it is locked, which means no other writing or reading can happen during this time. This prevents data races which are a common source of bugs which are complex to debug, while not compromising performances.
Closing pipes
Any of the two processes (be it the receiver or the sender) can close a pipe using the CLOSE_PIPE
syscall, providing its SC or RC identifier. The pipe is immediatly closed on both sides, and the other process receives the PIPE_CLOSED
signal.
Message pipes
Pipes are designed to transmit streams of data, but sometimes we need to use them to transmit messages. This is why there are specific syscalls and signals to deal with this problem.
A message pipe is a pipe that only sends and receives dynamic-sized messages instead of a stream of bytes.
They have a maximum length of 64 KB, which is the pipes' buffer's minimal capacity. Messages must always be sent at once and cannot be sent partially.
Their length is determined when the message is sent which, coupled to pipes locking, allows to retrieve complete messages directly.
It's not possible to send "non-message" data through a message pipe, as the action of writing to a pipe will automatically check if it's a message pipe and ensure the size and "send at once" requirement are met.
Interactive usage
You can find more informations on interactive usage in the shell specifications.
Service sockets
The downside of message pipes is that they are not designed to handle responses from the receiver process, and they also don't have built-in errors handling.
To solve this, it's possible to use service sockets, which as their name indicate are mainly used to communicate with services. A socket is caracterized by the process opening it, called the service, and the process receiving it, the client.
Opening
Sockets are opened with the OPEN_SERV_SOCK
syscall. The other process receives the RECV_SERV_SOCK
signal on its side.
Exchanges and messages
Service sockets are based on exchanges, which are sets of messages. An exchange is described by a structure made of the following:
- Identifier (8 bytes)
- Method or notification code (2 bytes)
- Status (1 byte):
0x01
if the exchange must be closed,0x00
otherwise - Error code (on 2 bytes) - only valid if the exchange is closed by this message
- Message counter for this exchange (2 bytes)
- Message's length (2 bytes)
This structure is handled by the kernel itself and can be managed using a set of system calls and signals.
Messages can then be sent using the SEND_SOCK_MSG
syscall and received through the RECV_SOCK_MSG
signal. To be read, the receiver process must use the READ_SOCK_MSG
syscall.
They must be sent at once, like in message pipes. The maximum size is 4 KB by default, but can be extended through the opening syscall up to 256 MB.
A message can either initiate an exchange or answer to an existing one: in the first case, an identifier will be generated by the kernel and returned by the sending syscall, and in the latter the message will receive an incremented identifier that allows to track where in the exchange the message is. This also prevents a process to send two messages before the other one answers.
Concluding exchanges
Exchanges can be either be concluded either by sending an error message or by sending a message with a close indicator to indicate this message will be the last one in the socket and no answer will be accepted.
Sending a message in a concluded exchange will result in a syscall error.
Methods and notifications
An exchange can be opened by a client to request something to the service, it is then called a method. In that case, the message's body is called the arguments.
When the server opens itself an exchange with the client, it's called a notification. A notification message's body is called its data.
Closing service sockets
Service sockets can be closed with the CLOSE_SERV_SOCK
syscall, which triggers the SERV_SOCK_CLOSED
signal on the other process' side.
Although exchanges are based on signals which are asynchronous by design, the answer mechanism and messages tracking which ensures the receiver process answers before sending another message allows for simplier communications and synchronization between the two processes.
Shared Memory
Shared memory allows a process to share a part of its memory with another process. It has multiple advantages over pipes:
- Data is not copied twice, as the sender process directly shares a part of its own memory
- There is no pipe management, which results in saving operations and less memory accesses
- There is no pipe buffer to manage which means all the data can be sent at once
Its main disadvantage being that all data is shared at once, so there is no synchronization or "asynchronous sending" mechanism, which is the purpose of pipes.
It works by asking the kernel to share the memory through the SHARE_AMS
syscall, the target process receiving the RECV_SHARED_AMS
signal.
There are two types of sharing:
- Mutual sharing allows both the sharer and the receiver processes to access the shared memory ;
- Exclusive sharing unmaps the shared memory from the sharer and only allows the receiver process to access it
Exclusive mode has several advantages: the sender process to not have to care about managing this memory and avoid overwriting it by accident, but it also ensures the receiving process that the sender will not perform malicious modifications on the shared buffer while the data is processed on its side.
Mutual memory sharing can be stopped using the UNSHARE_AMS
syscall, while exclusive sharing are left to the receiver process.
Signals
Signals are a type of KPC. They are used by the kernel to send informations to processes about a specific event.
- Technical overview
- List of signals
0x01
HANDLER_FAULT0x02
MEM_FAULT0x20
RECV_PIPE0x21
PIPE_CLOSED0x26
RECV_SERV_SOCK0x27
RECV_SOCK_MSG0x29
SERV_SOCK_CLOSED0x2A
SERVICE_CONN_REQUEST0x2B
SERVICE_CLIENT_CLOSED0x2C
SERVICE_CLIENT_QUITTED0x2D
SERVICE_SERVER_QUITTED0x33
READ_BACKED_AMS0x34
WRITE_BACKED_AMS0x35
RECV_SHARED_AMS0x37
UNSHARED_AMS0x44
SUSPEND0x45
WILL_SUSPEND0x46
UNSUSPENDED0x4E
TERMINATE0x4F
WILL_TERMINATE0xD4
DEVICE_CHANGED0xD4
DEVICE_INTERRUPT
Technical overview
When a process is created, the kernel associates it:
- A signals handler table (SHT) ;
- A signals queue ;
- A readiness indicator
Each signal has a 8-bit code that identifies it, as well as a 32 bytes datafield which is used to attach additional informations about the signal.
When the kernel sends a signal to a process, it first checks if an handler is already running. If so, it simply pushes the signal to the queue.
Else, it checks the readiness indicator. If it is false
(so if the process did not sent the READY
syscall yet), the signal is pushed to the queue.
Else, it checks in the SHT if the signal has a handler. If there is no handler, depending on the specific signal, it may either be ignored or use a default behaviour (this is documented for each signal).
If a handler is found, the kernel checks if the pointer points to a memory area that is executable by the current process. If it isn't, the signal is converted to an HANDLER_FAULT
one. If the signal that was being sent was already an HANDLER_FAULT
, the process is killed.
The kernel then switches the process to its main thread and makes it jump to the handler's address, then resumes it.
When the handler returns (or the default behaviour completes), if the signal was expecting an answer, the kernel reads it from specific registries and does whatever it needs to do. Then, itchecks if the signals queue is empty. If it is, the kernel simply makes the process jump back to the address it was to before the signal was emitted, and switch to the original thread.
Else, it interrupts the process again and proceeds to treat the first signal on the queue after removing it.
On the performances side, signals use interrupts, meaning the process' current tasks are instantly interrupted to let it handle the signal without delay. Also, the datafield and answer are provided through CPU registers, avoiding memory accesses.
List of signals
You can find below the exhaustive list of signals.
0x01
HANDLER_FAULT
Sent when a signal is sent to a process but the registered handler points to a memory zone that is not executable by the current process. If the sending of this signal to the process results to another fault, it's called a double handler fault and the process is immediatly killed.
If no handler is registered for this signal, it will kill the process when received.
Datafield:
- Faulty signal ID (8 bytes)
0x02
MEM_FAULT
Sent when the process tried to perform an unauthorized access on a memory address.
Datafield:
- Faulty address (8 bytes)
- Access error (1 byte):
0x01
: tried to read memory0x02
: tried to write memory0x03
: tried to execute memory
0x20
RECV_PIPE
Sent to a process when another process of the same application and running under the same user opened an pipe with this process, giving it the other part.
The command code can be used to determine what the other process is expecting this one to do. This code does not follow any specific format.
Datafield:
- Pipe creator's PID (8 bytes)
- Pipe creator's application's ANID (4 bytes)
- Pipe SC or RC identifier (8 bytes)
- Command code (2 bytes)
- Pipe identifier type (1 byte):
0x00
if the pipe identifier is an RC,0x01
if it's an SC - Mode (1 byte):
0x00
if it's a raw pipe,0x01
if it's a message pipe - Size hint in bytes (8 bytes), with
0
being the 'no size hint' value
0x21
PIPE_CLOSED
Sent to a process when a pipe shared with another process is closed.
NOTE: This does not apply to service pipes.
Datafield:
- Closing type (1 byte):
0x00
if the pipe was closed properly using the CLOSE_PIPE syscall0x01
if the other process brutally terminated |
- Pipe identity (1 byte):
0x00
if this process contained the RC part,0x01
if it contained the SC part (1 byte) - RC or SC identifier (8 bytes)
0x26
RECV_SERV_SOCK
Sent to a process when another process opened a service socket with this one.
Datafield:
- Service socket creator's PID (8 bytes)
- Service socket creator's application's ANID (4 bytes)
- Service socket identifier (8 bytes)
- Size of the buffer, multiplied by 4KB (2 bytes)
0x27
RECV_SOCK_MSG
Sent to a process when a message has been sent through a service socket.
To read the message, the process must use the READ_SOCK_MSG
syscall.
Datafield:
- Servive socket identifier (8 bytes)
- Exchange identifier (8 bytes)
- Exchange method (1 byte)
- Size of the message (4 bytes)
- Status (1 byte):
- Bit 0: set if this message did create a new exchange
- Bit 1: set if this message is an error message
- Bit 2: set if this message closed the socket
0x29
SERV_SOCK_CLOSED
(was there a message sent before that closed the socket)
Sent to a process when a pipe shared with another process is closed.
NOTE: This does not apply to service pipes.
Datafield:
- Closing type (1 byte):
0x00
if the pipe was closed properly using the CLOSE_PIPE syscall0x01
if the other process brutally terminated |
- Pipe identity (1 byte):
0x00
if this process contained the RC part,0x01
if it contained the SC part (1 byte) - RC or SC identifier (8 bytes)
0x2A
SERVICE_CONN_REQUEST
Sent to a service process' dispatcher thread when another process tries to etablish a connection through the CONNECT_SERVICE
syscall.
The process is expected to answer using the ACCEPT_SERVICE_CONNECTION
under the provided delay, else it's considered as a rejection.
If no handler is registered for this signal, it will kill the process when received.
NOTE: This signal cannot be received if the application does not expose a service.
Datafield:
- Callee process' ID (8 bytes)
- Connection's unique request ID (8 bytes)
- Command code (2 bytes)
- Registry's
system.processes.service_answer_delay
key (default: 2000ms) (2 bytes)
0x2B
SERVICE_CLIENT_CLOSED
Sent to a client thread to indicate its client closed before the connection was properly terminated. The thread is expected to terminate as soon as possible (there is no time limit though).
0x2C
SERVICE_CLIENT_QUITTED
Sent to a client thread to indicate its client asked to close the connection. The associated RC and SC are immediatly closed.
0x2D
SERVICE_SERVER_QUITTED
Sent to a process that previously established a connection with a service, to indicate the associated service thread closed before the connection was properly terminated.
Datafield:
- Connection's unique request ID (8 bytes)
0x33
READ_BACKED_AMS
Sent to a process when a signal-backed abstract memory segment (AMS) is accessed in read mode.
Datafield:
- AMS ID (8 bytes)
- Relative address accessed in the segment (8 bytes)
- Access mode (1 byte):
0x00
for read,0x01
for execution
Expected answer:
- Associated data for this file (4 bytes)
- Page fault (1 byte):
0x00
: no page fault0x01
: address is out-of-range0x02
: hardware fault
0x34
WRITE_BACKED_AMS
Sent to a process when a signal-backed abstract memory segment (AMS) is accessed in write mode.
Datafield:
- AMS ID (8 bytes)
- Relative address accessed in the segment (8 bytes)
- Data to write (4 bytes)
Expected answer:
- Page fault (1 byte):
0x00
: no page fault0x01
: address is out-of-range0x02
: hardware fault
0x35
RECV_SHARED_AMS
Sent to a process when an abstract memory segment (AMS) is shared by another process.
Datafield:
- Sender PID (8 bytes)
- Command code (2 bytes)
- AMS ID (8 bytes)
- Sharing mode (1 byte):
0x00
for mutual sharing,0x01
for exclusive sharing - Access permissions (1 byte):
- For mutual sharings: strongest bit for read, next for write, next for exec
- For exclusive sharings:
0b11100000
0x37
UNSHARED_AMS
Sent to a process when an abstract memory segment (AMS) is unshared by the sharer process.
Datafield:
- Unsharing type (1 byte):
0x00
if the shared memory was unshared properly using the UNSHARE_AMS syscall0x01
if the other process brutally terminated
0x44
SUSPEND
Sent when the process is asked to suspend. It's up to the process to either ignore this signal or suspend itself using the SUSPEND
syscall.
0x45
WILL_SUSPEND
Sent when the process is asked to suspend. If it is not suspended after the provided delay, the process is suspended.
Datafield:
- Registry's
system.processes.suspend_delay
key (default: 500ms) (2 bytes)
0x46
UNSUSPENDED
Sent when the process was just unsuspended.
Datafield:
- Suspension duration, in microseconds (8 bytes)
0x4E
TERMINATE
Sent when the process is asked to terminate. It's up to the process to either ignore this signal or terminate itself (preferably by using the EXIT
syscall).
0x4F
WILL_TERMINATE
Sent when the process is asked to terminate. If it does not terminate by itself before the provided delay, the process is killed.
If no handler is registered for this signal, it will kill the process when received.
Datafield:
- Registry's
system.processes.terminate_delay
key (default: 2s) (2 bytes)
0xD4
DEVICE_CHANGED
Sent to the sys::hw
service only, when a hardware device is plugged, unplugged, or when its raw device descriptor (RDD) changes.
The KDI is guaranteed to remain the same for each individual devices.
Datafield:
- Event code (1 byte):
0x01
: a device was connected on this port0x02
: the connected device was disconnected from this port0x03
: the device connected to this port changed its RDD
- RDD
0xD4
DEVICE_INTERRUPT
Sent to the sys::hw
service only, when a hardware device raises a CPU interruption.
Datafield:
- KDI (8 bytes)
- Interruption code (1 byte)
System calls
System calls, abbreviated syscalls, are a type of KPC. They allow a process to ask the kernel to perform an action.
- Technical overview
- List of syscalls
0x01
HANDLE_SIGNAL0x02
UNHANDLE_SIGNAL0x03
IS_SIGNAL_HANDLED0x04
READY0x20
OPEN_PIPE0x21
SEND_PIPE0x22
PIPE_WRITE0x23
PIPE_READ0x24
PIPE_INFO0x25
CLOSE_PIPE0x26
OPEN_SERV_SOCK0x27
SEND_SOCK_MSG0x28
READ_SOCK_MSG0x29
CLOSE_SERV_SOCK0x2A
CONNECT_SERVICE0x2B
END_SERVICE_CONN0x2C
ACCEPT_SERVICE_CONN0x2D
REJECT_SERVICE_CONN0x2E
HAS_SCOPED_SERVICE0x30
MEM_ALLOC0x31
MEM_FREE0x40
VIRT_MEM_AMS0x41
BACKED_AMS0x42
SHARE_AMS0x43
AMS_SHARING_INFO0x44
UNSHARE_AMS0x45
MAP_AMS0x46
UNMAP_AMS0x50
CREATE_PROCESS0x51
WAIT_CHILD_PROCESS0x52
KILL_CHILD_PROCESS0x53
GET_PID0x54
SUSPEND0x55
UNSUSPEND0x56
HAND_OVER0x5F
EXIT0x60
CREATE_THREAD0x61
CREATE_TLS_SLOT0x62
READ_TLS_SLOT0x63
WRITE_TLS_SLOT0x64
DELETE_TLS_SLOT0x6F
EXIT_THREAD0x70
READ_IO_PORT0x71
WRITE_IO_PORT0x72
DEVICE_INTERRUPT0x73
DEVICE_AMS0x74
SET_DMA_MEM_ACCESS0xA0
EXECUTION_CONTEXT0xD0
SYS_CREATE_PROCESS0xD1
SYS_MANAGE_PROCESS0xD2
SYS_PROCESS_ATTRIBUTES0xD4
SYS_ENUM_DEVICES0xD5
CREATE_CONTAINER0xD6
LINK_CONTAINER_DEVICE0xD7
DESTROY_CONTAINER
Technical overview
Syscalls are performed using CPU interruptions to notify the kernel.
A syscall is made of a 8-bit code, as well as up to 8 arguments with up to 64-bit value each.
When performing a syscall, the process will put in a specific CPU register an address poiting to a memory address containing in a row the syscall's code and its arguments. For most syscalls, code and arguments will be not be longer than 128 bits, but some may use larger arguments.
Some syscalls require the process to send a buffer of data. In such case, the process simply provides a pointer to the said buffer - so the argument's size will vary depending on the length of memory addresses.
After preparing the syscall's code and arguments, the process raises a specific exception that is caught by the kernel. When the syscall is complete, the kernel puts the result values in specific registers and resumes the process. This means that all syscalls are synchronous.
System calls always return two numbers: a 8-bit one (errcode) and a 8 bytes one (return value). If the errcode is not null, then an error occured during the syscall. The specific value indicate the encountered type of error:
0x00
: cannot read syscall's code or arguments (error while reading memory)0x01
: the requested syscall does not exist0x02
: at least one argument is invalid (e.g. providing a pointer to the0
address)0x03
: unmapped memory pointer (e.g. provided a pointer to a memory location that is not mapped yet)0x04
: memory permission error (e.g. provided a writable buffer to an allocated but non-writable memory address)0x10
to0x1F
: Invalid argument(s) provided (constant checking)0x20
to0x2F
: Provided arguments are not valid in the current context (in relation with other arguments)0x30
to0x3F
: Provided arguments are not valid (after resources checking)0x40
to0x4F
: Resource access or modification error0x50
to0x5F
: Handled hardware errors0x60
to0x6F
: Other types of errors
System calls' code are categorized as follows:
0x00
to0x0F
: signal handling0x10
to0x1F
: process management0x20
to0x29
: pipes0x2A
to0x2F
: services communication0x30
to0x3F
: memory management0x40
to0x4F
: AMS management0x50
to0x5F
: processes management0x60
to0x6F
: threads management0x70
to0x7F
: hardware interaction0xA0
to0xAF
: applications-related syscalls0xD0
to0xDF
: reserved to system services
Note that advanced actions like permissions management or filesystem access are achieved through the use of IPC.
List of syscalls
You can find below the exhaustive list of system calls.
0x01
HANDLE_SIGNAL
Register a signal handler.
If the address pointed by this syscall's is not executable by the current process when this signal is sent to the process, the signal will be converted to an HANDLER_FAULT
signal instead.
Arguments:
- Code of the signal to handle (1 byte)
- Pointer to the handler function (8 bytes)
Return value:
Empty
Errors:
0x10
: The requested signal does not exist
0x02
UNHANDLE_SIGNAL
Unregister a signal handler, falling back to the default signal reception behaviour if this signal is sent to the process.
Arguments:
- Code of the signal to stop handling (1 byte)
Return value:
Empty
Errors:
0x10
: The requested signal does not exist0x30
: The requested signal does not have an handler
0x03
IS_SIGNAL_HANDLED
Check if a signal has a registered handler.
Arguments:
- Code of the signal (1 byte)
Return value:
0
if the signal is not handled,1
if it is (1 byte)
Errors:
0x10
: The requested signal does not exist
0x04
READY
Indicate the system this process has set up all its event listeners, so it can start dequeuing signals.
NOTE: When this signal is sent, all queued signals will be treated at once, so the instructions following the sending of this signal may not be ran until quite a bit of time in the worst scenario.
WARNING: Signals will not be treated until this syscall is sent by the process!
Arguments:
None
Return value:
Empty
Errors:
0x30
: The process already told it was ready
0x20
OPEN_PIPE
Open a pipe with a process of the same application and running under the same user and get its SC.
The buffer size multiplier indicates the size of the pipe's buffer, multiplied by 64 KB. The default (0
) falls back to a size of 64 KB.
The command code can be used to indicate to the target process which action is expected from it. It does not follow any specific format.
The target process will receive the SC/RC's counterpart through the RECV_PIPE
signal, unless notification mode states otherwise.
Arguments:
- Target process' PID (8 bytes)
- Command code (2 bytes)
- Pipe type (1 byte):
0x00
to create a write pipe,0x01
to create a read pipe - Buffer size multiplier (1 byte)
- Transmission mode (1 byte):
0x00
to create a raw pipe,0x01
to create a message pipe - Notification mode (1 byte):
0x00
to notify the process with theRECV_PIPE
signal,0x01
to skip it - Size hint in bytes (8 bytes), with
0
being the 'no size hint' value
Return value:
- Pipe SC identifier (8 bytes)
Errors:
0x10
: Invalid transmission mode provided0x11
: Invalid notification mode provided0x30
: The provided PID does not exist0x31
: The target process is not part of this application0x32
: The target process runs under another user0x33
: Notification mode is enabled but the target process does not have a handler registered for theRECV_PIPE
signal
0x21
SEND_PIPE
Share an RC or SC identifier with another process.
This will trigger in the target process the RECV_PIPE
signal, unless the notification mode tells otherwise.
When the target process writes through the received SC or read from the received RC, the performance will be equal to writing or reading through the original RC/SC identifier.
Arguments:
- Pipe RC or SC identifier (8 bytes)
- Target PID (8 bytes)
- Notification mode (1 byte):
0x00
to notify the process with a pipe reception signal,0x01
to skip the signal
Return value:
None
Errors:
0x30
: Notification mode is enabled but the target process does not have a handler registered for theRECV_PIPE
signal
0x22
PIPE_WRITE
Write data through a pipe.
Messages will always be sent at once when writing to message pipes.
If the data is 0-byte long, this pipe will return successfully without waiting, even if the target pipe's buffer is full or locked.
Arguments:
- Pipe SC identifier (8 bytes)
- Number of bytes to write (4 bytes)
- Pointer to a readable buffer (8 bytes)
- Mode (1 byte):
0x00
= block until there is enough space to write,0x01
= fail if there is not enough space to write or if the pipe is locked,0x02
= write as much as possible
Return value:
Encoded on 4 bytes:
- If mode is
0x00
: remaining capacity of the pipe - If mode is
0x01
:0x00
if the cause of failure was because the pipe was locked,0x01
if it was because of of a lack of space in the target buffer - If mode is
0x02
: number of bytes written
Errors:
0x10
: Invalid mode provided0x30
: The provided SC identifier does not exist0x31
: The provided SC was already closed0x32
: The provided SC refers to a message pipe but the provided size is larger than 64 KB0x33
: The provided SC refers to a message pipe but the0x02
mode was provided0x40
: There is not enough space in the pipe to write all the provided data and the mode argument was set to0x01
0x23
PIPE_READ
Read pending data or message from a pipe.
If the pipe was closed while the buffer was not empty, this syscall will still be able to read the remaining buffer's data - but the pipe will not be able to receive any additional data. Then, once the buffer is empty, the pipe will be made unavailable.
Arguments:
- Pipe RC identifier (8 byte)
- Mode (1 byte):
0x00
= block until there are enough data to read,0x01
= fail if there is not enough data to read or if the pipe is locked,0x02
= read as much as possible - Readable buffer pointer (16 bytes) (specify
0
bytes length to read as much data as possible)
Return value:
Encoded on 4 bytes:
- If mode is
0x00
: remaining bytes in the buffer - If mode is
0x01
:0x00
if the cause of failure was because the pipe was locked,0x01
if it was because of of a lack of space in the target buffer - If mode is
0x02
: number of read bytes
Errors:
0x10
: Invalid mode provided0x30
: The provided RC identifier does not exist0x31
: The provided RC was already closed0x32
: There is no pending data in the pipe and the mode argument was set to0x01
0x33
: The provided RC refers to a message pipe but the0x02
mode was provided
0x24
PIPE_INFO
Get informations on a pipe from its RC or SC identifier.
Arguments:
- Pipe RC or SC identifier (8 bytes)
Return value:
- Status (1 byte):
- Bit 0 (strongest): indicates if the pipe is opened
- Bit 1: indicates if the pipe is a message pipe
- Bit 2: indicates if the pipe's buffer is full
- Bit 3: indicates if the pipe is locked
- Bit 4: indicates if a writing request is pending (waiting for the pipe to be unlocked)
- Bit 5: indicates if a reading request is pending (waiting for the pipe to be unlocked)
- Bit 6: indicates if the provided identifier is an SC
- Pipe's buffer's capacity (8 bytes)
- Remaining data before the pipe's buffer is full (8 bytes)
- Pipe's creator's PID (8 bytes)
Errors:
0x30
: The provided RC or SC identifier does not exist
0x25
CLOSE_PIPE
Close a pipe properly. The RC and SC parts will be immediatly closed.
The other process this pipe was shared with will receive the PIPE_CLOSED
signal unless this pipe was created during a service connection.
If this syscall is not performed on a pipe before the process exits, the other process will receive the same signal with a specific argument to indicate the communication was brutally interrupted.
Arguments:
- Pipe RC or SC identifier (8 bytes)
Return value:
None
Errors:
0x30
: The provided RC/SC identifier does not exist0x31
: The target process already terminated0x32
: The provided RC/SC identifier is part of a service pipe
0x26
OPEN_SERV_SOCK
Open a service socket.
Triggers the RECV_SERV_SOCK
signal on the receiver process' side.
Arguments:
- Client process PID (8 bytes)
- Buffer size multiplier by 4 KB (2 bytes) -
0
fall backs to 4KB
Return value:
- Socket identifier (8 bytes)
Errors:
0x30
: Unknown PID provided0x31
: Current process is not allowed to communicate with the provided process
0x27
SEND_SOCK_MSG
Send a message through a service socket exchange.
This syscall can also be used to create a new exchange.
Sending a non-zero error code will close the exchange.
Arguments:
- Socket identifier (8 bytes)
- Exchange identifier (8 bytes) -
0
creates a new exchange - Method or notification code (1 byte) - non-zero value if not creating an exchange to close it with a non-error message
- Error code (2 bytes)
- Buffer pointer to the message's content (16 bytes)
Return value:
- Exchange identifier (8 bytes)
- Message counter for this exchange (4 bytes)
Errors:
0x30
: Unknown socket identifier0x31
: Socket is already closed0x32
: Unknown exchange identifier0x40
: Exchange has already been concluded
0x28
READ_SOCK_MSG
Read the pending message of a service socket.
Arguments:
- Socket identifier (8 bytes)
- Address of a writable buffer (8 bytes)
Return value:
- Number of written bytes (4 bytes)
0x01
if a message was retrieved,0x00
if none was pending (1 byte)- Status (1 byte):
- Bit 0: set if this message did create a new exchange
- Bit 1: set if this message is an error message
- Bit 2: set if this message closed the socket
Errors:
0x30
: Unknown socket identifier0x31
: Socket is already closed0x32
: Unknown exchange identifier0x40
: Exchange has already been concluded
0x29
CLOSE_SERV_SOCK
Close a service socket.
Triggers the SERV_SOCK_CLOSED
signal on the receiver process' side.
Arguments:
- Socket identifier (8 bytes)
Return value:
None
Errors:
0x30
: Unknown socket identifier0x40
: Socket is already closed
0x2A
CONNECT_SERVICE
Ask a service to etablish connection. The current process is called the service's client.
If the current process already has an active connection (a connection that hasn't been closed) to the target service, it will fail unless the flexible mode argument is set.
NOTE: When this signal is sent, the service's answer will be waited, so the instructions following the sending of this signal may not be ran until several seconds in the worst scenario.
Arguments:
- Target application's ANID (4 bytes)
- Target type (1 byte):
0x00
for the main service0x01
for a scoped service0x02
for an integration service0x03
for a driver service
- Scope name - filled with zeroes to access the default service (8 bytes)
- Command code (2 bytes)
Return value:
- Unique connection ID (8 bytes)
- Service socket identifier (8 bytes)
- Flexible mode (1 byte):
0x00
by default,0x01
returns the existing connection ID if an active connection is already in place with the service instead of failing
Errors:
0x10
: Invalid flexible mode provided0x11
: Invalid target ytpe provided0x30
: Requested an integration or driver service but client is not a system services0x31
: The provided ANID does not exist0x32
: Target application does not expose the requested service0x32
: Current process already has an active connection to the target service and flexible mode is not set0x3F
: Client is thesys::fs
service but a service other than a storage driver service or a filesystem interface service was requested0x40
: Failed to send theSERVICE_CONN_REQUEST
due to a double handler fault0x41
: Service rejected the connection request
0x2B
END_SERVICE_CONN
Tell a service to properly close the connection. The associated service socke will immediatly be closed.
Arguments:
- Unique connection ID (8 bytes)
Return value:
None
Errors:
0x30
: The provided connection ID does not exist0x40
: This connection was already closed0x41
: The associated service thread already terminated
0x2C
ACCEPT_SERVICE_CONN
Confirm the current service accepts the connection with a client.
A dedicated service socket identifier will be provided to communicate with the client.
This will create a new client thread in the current process, which is meant to be dedicated to this specific client.
The client thread will not receive any SERVICE_CONN_REQUEST
signal, only dispatcher thread will.
When the associated client terminates, the SERVICE_CLIENT_CLOSED
signal is sent to this thread.
Arguments:
- Connection's unique request ID (8 bytes)
Return value:
0x00
if the current process is now the associated client's thread,0x01
else- Service socket identifier (8 bytes)
Errors:
0x30
: This request ID does not exist0x40
: The process which requested the connection already terminated0x41
: Answer was given after the delay set in the registry'ssystem.processes.service_answer_delay
key (default: 2000ms)
0x2D
REJECT_SERVICE_CONN
Reject a connection request to the current service.
Arguments:
- Connection's unique request ID (8 bytes)
Return value:
None
Errors:
0x30
: This request ID does not exist0x40
: The process which requested the connection already terminated0x41
: Answer was given after the delay set in the registry'ssystem.processes.service_answer_delay
key (default: 2000ms)
0x2E
HAS_SCOPED_SERVICE
Arguments:
- Target application's ANID (4 bytes)
- Scope name (8 bytes) - fill with zeroes to check the default service
Return value:
0x01
if the application has a service for the provided scope,0x00
otherwise
Errors:
0x30
: The provided ANID does not exist0x31
: Target application does not expose the provided service
0x30
MEM_ALLOC
Allocate a contiguous block of memory. In case of large allocations, a large memory page may be allocated instead of multiple small ones.
WARNING: Allocated memory will not be zeroes unless it previously belonged to another process. Therefore the caller process shall ensure memory is used correctly.
Arguments:
- Number of bytes to allocate (8 bytes)
- Permissions (3 bits, from the weakest):
0b001
: Read-only0b010
: Write-only0b011
: Read-write0b100
: Exec-only
0x01
to ensure the zone is zeroed before allocating,0x00
else (1 byte)
Return value:
- Pointer to the newly-allocated block of memory (8 bytes)
Errors:
0x10
: Invalid permission0x40
: The kernel could not find a contiguous block of memory of the requested size
0x31
MEM_FREE
Unallocate a contiguous block of memory.
Shared memory pages must first be unshared through the UNSHARE_AMS
syscall.
Mapped memory pages must be unmapped through the UNMAP_AMS
syscall.
WARNING: Memory will not be zeroed, therefore the caller process shall ensure critical informations are zeroed or randomized before freeing the memory.
Arguments:
- Pointer to the start address to unallocate the memory from (8 bytes)
- The number of pages to unallocate (8 bytes)
Return value:
None
Errors:
0x10
: The provided start address it not aligned with a page0x30
: The provided start address is out of the process' range0x31
: The provided size, added to the start address, would exceed the process' range0x32
: One or more of the provided pages was not allocated (e.g. unmapped page or memory-mapped page)0x33
: One or more of the provided pages are shared with another process
0x40
VIRT_MEM_AMS
Create an abstract memory segment (AMS) from a part of the current process' address space.
Arguments:
- Address of the first page to register in the AMS (8 bytes)
- Number of bytes to register (8 bytes)
Return value:
- AMS ID (8 bytes)
Errors:
0x10
: Start address is unaligned0x11
: Number of bytes is unaligned0x30
: Address is out of range
0x41
BACKED_AMS
Create an abstract memory segment (AMS) backed by the READ_BACKED_AMS
and WRITE_BACKED_AMS
signals.
Copy-on-write support can be enabled to allow the receiver process to write data in its own memory space. Written pages will be allocated by the kernel and won't be backed anymore by the READ_BACKED_AMS
signal. The backer process won't be able to see these changes, and the WRITE_BACKEND_AMS
signal won't be trigerred on its side.
Arguments:
- Length of the AMS (8 bytes)
- Copy-on-write mode (1 byte):
0x00
to disable,0x01
to enable
Errors:
0x10
: Invalid COW mode provided0x11
: Provided length is unaligned
0x42
SHARE_AMS
Share an abstract memory segment (AMS) with another process.
This will trigger in the target process the RECV_SHARED_MEM
with the provided command code, unless the notification mode states otherwise.
The mutual mode allows both processes to access the memory, with the sharer setting the permissions for the receiver to limit its access. Copy-on-write can also be enabled to allow the receiver process to write data without affecting the sharer process' memory.
The exclusive mode allows, only when sharing AMS made from existing memory pages from its original process, to unmap the original pages from the said process to let the exclusive access to the target process. This is useful when transferring temporarily large chunks of data to another process. Also, access permissions are ignored when using exclusive mode.
The returned AMS ID is common for both the sender and the receiver, allowing to use it in exchanges.
Arguments:
- Target process' PID (8 bytes)
- AMS ID (8 bytes)
- Command code (2 bytes)
- Notification mode (1 byte):
0x00
to notify the process with theRECV_SHARED_AMS
signal,0x01
to skip it - Mode (1 byte):
- Mutual:
0 b 0 0 0 0 <1 to enable copy-on-write> <1 to enable read> <1 to enable write> <1 to enable exec>
- Exclusive:
0 b 0 0 0 0 1 0 0 <1 to unmap original pages>
- Mutual:
Return value:
None
Errors:
0x10
: Invalid notification mode provided0x11
: Invalid mode provided0x12
: Invalid exclusive mode provided0x20
: Access permissions were not set but the sharing mode is set to mutual0x21
: Access permissions were provided but the sharing mode is set to exclusive0x40
: There is not enough contiguous space in the receiver process' memory space to map the shared memory
0x43
AMS_SHARING_INFO
Get informations about a shared abstract memory segment (AMS).
Arguments:
- AMS ID (8 bytes)
Return value:
- Sharer process' PID (8 bytes)
- Receiver process' PID (8 bytes)
- Sharing mode (1 byte):
0x00
for mutual mode,0x01
for exclusive mode - Pointer to the shared buffer (16 bytes)
- Command code (2 bytes)
- Access permissions (1 byte): for mutual sharings, strongest bit for read, next for write, next for exec ; for exclusive sharings,
0x00
Errors:
0x30
: Unknwon AMS ID provided
0x44
UNSHARE_AMS
Stop sharing an abstract memory segment (AMS) started by SHARE_AMS
. Note that exlusive sharings cannot be unmapped.
This will trigger in the target process the UNSHARED_AMS
signal.
Arguments:
- AMS ID (8 bytes)
- PID to stop sharing with (8 bytes) -
0
to stop sharing with all processes
Return value:
None
Errors:
0x30
: Unknown AMS ID provided0x21
: Provided AMS ID is exclusive0x32
: Provided AMS was not shared with the provided process
0x45
MAP_AMS
Map an abstract memory segment (AMS) in the current process' address space.
Arguments:
- AMS ID (8 bytes)
- Address to map the AMS from (8 bytes)
- Mapped buffer pointer (16 bytes)
Return value:
None
Errors:
0x30
: Unknown AMS ID provided0x31
: Provided mapping address or address+length is out-of-range in the AMS0x32
: Provided address to map or address+length is out-of-orange in this process' address space
0x46
UNMAP_AMS
Unmap an abstract memory segment (AMS) from the current process' address space.
If the AMS is mapped at multiple addresses of this process, only one of the mappings will be unmapped by default.
Arguments:
- AMS ID (8 bytes)
- Mapping address (8 bytes) -
0
to unmap from all addresses
Return value:
Empty
Errors:
0x10
: Unknown AMS ID provided0x30
: Provided AMS it not mapped at this address
0x50
CREATE_PROCESS
Create a child process from the current one. The new process gets a separate memory space.
Communication can be done through standard IPC.
The initialization data is joined as part of the application's execution context.
If the parent process is part of a container, the child process will be part of the same one.
Arguments:
- Initialization data (8 bytes)
Return value:
- Child process identifier (8 bytes)
- Identity (1 byte):
0x00
if the current thread is the parent,0x01
for the child - Initialization data (8 bytes) -
0
for the parent
Errors:
0x30
: The current process is not an application process0x40
: Failed to create a new process due to hardware problem (cannot allocate memory, ...)
0x51
WAIT_CHILD_PROCESS
Wait for a child process to terminate.
Arguments:
- Process identifier (8 bytes)
- Timeout in milliseconds (2 bytes)
Return value:
Empty
Errors:
0x30
: The provided PID does not exist or does not belong to the current application
0x52
KILL_CHILD_PROCESS
Kill a child process, which will first receive the WILL_TERMINATE
signal.
Arguments:
- Process identifier (8 bytes)
- Timeout in milliseconds (2 bytes) - if
0
, will use the system's global default value (registry'ssystem.processes.terminate_delay
)
Return value:
- Process' exit data (provided through
EXIT_PROCESS
,0
otherwise)
Errors:
0x40
: Failed to create a new process due to hardware problem (cannot allocate memory, ...)
0x53
GET_PID
Get the current process' PID.
Arguments:
None
Return value:
- Current process' PID (8 bytes)
Errors:
None
0x54
SUSPEND
Suspend the current process or a child process.
Arguments:
- PID to suspend (8 bytes) -
0
for the current process
Return value:
- Amount of time the process was suspended, in milliseconds (8 bytes)
Errors:
0x30
: the current process is not an application process0x31
: the current PID was not found or is not a child of the current process
0x55
UNSUSPEND
Unsuspend a child process.
Will trigger the UNSUSPENDED
signal on the child process' side.
Arguments:
- PID to unsuspend (8 bytes) -
0
for the current process
Return value:
- Amount of time the process was suspended, in milliseconds (8 bytes)
Errors:
0x30
: the current process is not an application process0x31
: the current PID was not found or is not a child of the current process
0x56
HAND_OVER
End this process' cycle.
Used to indicate to the kernel the current process has no additional work to do for now (e.g. waiting for asynchronous I/O data).
Arguments:
None
Return value:
None
Errors:
None
0x5F
EXIT
Kill the current process.
A SERVICE_CLIENT_CLOSED
signal is sent to all services connection the process has.
If the current process is a service, a SERVICE_SERVER_QUITTED
signal is sent to all active clients.
Arguments:
- Exit data (8 bytes)
Return value:
None (never returns)
Errors:
None
0x60
CREATE_THREAD
Create a thread from the current one. The new thread will share the current one's memory space.
Arguments:
- Initialization data (8 bytes)
Return value:
- Child thread identifier (8 bytes)
- Identity (1 byte):
0x00
if the current thread is the parent,0x01
for the child - Initialization data (8 bytes) -
0
for the parent
Errors:
0x40
: Failed to create a new thread due to hardware problem (cannot allocate memory, ...)
0x61
CREATE_TLS_SLOT
Create a TLS slot.
Arguments:
None
Return value:
- TLS slot identifier (8 bytes)
Errors:
0x40
: Maximum number of TLS slots was reached0x41
: Could not allocate memory for a new TLS slot
0x62
READ_TLS_SLOT
Read from a TLS slot.
Arguments:
- TLS slot identifier (8 bytes)
- Writable buffer (16 bytes) (specify
0
bytes length to read the entire data at once)
Return value:
- Number of bytes written (8 bytes)
Errors:
0x30
: Unknown TLS identifier
0x63
WRITE_TLS_SLOT
Write to a TLS slot.
Arguments:
- TLS slot identifier (8 bytes)
- Readable buffer (16 bytes)
Return value:
None
Errors:
0x30
: Unknown TLS identifier0x40
: Failed to allocate enough memory for the written data
0x64
DELETE_TLS_SLOT
Delete a TLS slot.
Arguments:
- TLS slot identifier (8 bytes)
Return value:
None
Errors:
0x30
: Unknown TLS identifier
0x6F
EXIT_THREAD
Kill the current thread and all of its children threads.
Arguments:
- Exit data (4 bytes)
Return value:
None (never returns)
Errors:
None
0x70
READ_IO_PORT
Read data from the physical I/O port a device, if authorized as a driver.
Complete access is granted to the sys::hw
service.
Arguments:
- KDI of the device to read data from (8 bytes)
- Relative I/O port number (2 bytes)
- Writable buffer pointer (16 bytes)
Return value:
- Number of bytes read from the I/O port (8 bytes)
Error codes:
0x30
: This device is not registered in this process' drivable devices attribute0x31
: The provided device KDI was not found0x32
: The provided I/O port does not exist for the provided device0x33
: The provided I/O port is an output port
0x71
WRITE_IO_PORT
Write data to the physical I/O port a device, if authorized as a driver.
Complete access is granted to the sys::hw
service.
Arguments:
- KDI of the device to read data from (8 bytes)
- Relative I/O port number (2 bytes)
- Readable buffer pointer (16 bytes)
Return value:
- Number of bytes written (8 bytes)
Error codes:
0x30
: This device is not registered in this process' drivable devices attribute0x31
: The provided device KDI was not found0x32
: The provided I/O port does not exist for the provided device0x33
: The provided I/O port is an input port
0x72
DEVICE_INTERRUPT
Trigger a hardware interruption on a device, if authorized as a driver.
Complete access is granted to the sys::hw
service.
Arguments:
- KDI of the device to trigger an interrupt on (8 bytes)
- Relative I/O port identifier (2 bytes)
- Interrupt code (1 byte)
Return value:
None
Error codes:
0x30
: This device is not registered in this process' drivable devices attribute0x31
: The provided device KDI was not found0x32
: The provided I/O port does not exist for the provided device0x33
: The provided I/O port is an input port
0x73
DEVICE_AMS
Create an abstract memory segment (AMS) from a device's memory through Mapped Memory Input/Output (MMIO).
Read data from the physical input/output port a device, if authorized as a driver.
Complete access is granted to the sys::hw
service.
Arguments:
- KDI of the device to map in memory (8 bytes)
- Start address in the device's memory (8 bytes)
- Number of bytes to map (8 bytes)
- Start address to map in this process' memory (8 bytes)
Return value:
- AMS ID (8 bytes)
Errors:
0x10
: The mapping's start address is not aligned with a page0x11
: The mapping's length is not a multiple of a page's size0x12
: The mapping's size is null (0 bytes)0x30
: This device is not registered in this process' drivable devices attribute0x31
: The provided device KDI was not found0x32
: The provided device is not compatible with MMIO
0x74
SET_DMA_MEM_ACCESS
Allow or disallow a device to access a range of addresses through Direct Memory Access (DMA) in the current process' address space.
Read data from the physical input/output port a device, if authorized as a driver.
Complete access is granted to the sys::hw
service.
Arguments:
- KDI of the device to map in memory (8 bytes)
- Start address in the current process' address space (8 bytes)
- Length (8 bytes)
- Authorization (1 byte):
0x00
to allow the device to use this range,0x01
to cancel an authorization
Return value:
None
Errors:
0x10
: The range's start address is not aligned with a page0x11
: The range's length is not a multiple of a page's size0x12
: The range's size is null (0 bytes)0x30
: This device is not registered in this process' drivable devices attribute0x31
: The provided device KDI was not found0x32
: The provided device is not compatible with DMA
0xA0
EXECUTION_CONTEXT
Get informations from the application's execution context.
Arguments:
-
Information to get (1 byte):
0x00
: all the context0x01
: startup reason0x02
: context header0x03
: command-line arguments
-
Pointer to a writable buffer (8 bytes)
Return value:
- Number of written bytes (8 bytes)
Errors:
0x10
: Invalid information number provided0x30
: Caller process is a system service
0xD0
SYS_CREATE_PROCESS
Syscall resserved to the sys::process
service.
Create a userland process.
Arguments:
- Code location token from the
sys::fs
service - Application's execution context
Return value:
- PID (8 bytes)
Errors:
0x30
: Caller process is not thesys::process
service0x31
: Code location token was not accepted by thesys::fs
service0x32
: Application context is not valid
0xD1
SYS_MANAGE_PROCESS
Syscall resserved to the sys::process
service.
Manage a userland process.
Arguments:
- PID (8 bytes)
- Action (1 byte):
0x01
: Ask for suspension0x02
: Force suspension0x03
: Unsuspend0x04
: Ask for termination0x05
: Force termination
Return value:
None
Errors:
0x10
: Invalid action code provided0x30
: Caller process is not thesys::process
service0x31
: Unknown PID0x32
: Process is already in the requested state
0xD2
SYS_PROCESS_ATTRIBUTES
Syscall resserved to the sys::process
service.
Manage a process' attributes.
Arguments:
For value-based attributes:
-
Attribute code (1 byte):
0x00
: PID0x01
: Process' priority0x02
: Running user's ID0x03
: Parent application ID0x04
: Execution context (startup reason)0x05
: Execution context (header)0x06
: Execution context (arguments)0x07
: Container ID
-
Action code:
0x00
: Read the information (followed by a writable buffer on 16 bytes)0x01
: Write the information (followed by a readable buffer on 16 bytes)
For list-based attributes:
-
Attribute code (1 byte):
0x00
: Memory mappings0x01
: Permissions0x02
: Drivable devices
-
Action code (1 byte) followed by its optional arguments:
0x00
: Get the number of elements0x01
: Get the value at a given index => index (4 bytes)0x02
: Update the value at a given index => index (4 bytes) + value (? bytes)0x03
: Insert a value at a given index => index (4 bytes) + value (? bytes)0x04
: Push a value at the end of the list => value (? bytes)0x05
: Remove a value from the list => index (4 bytes)0x06
: Remove the last value from the list0x10
: Check if the list contains a given item => value to look for (? bytes)
Return value:
- Number of written bytes (if applies) (8 bytes)
0x00
is all data was written,0x01
otherwise (1 byte)
Errors:
0x10
: Invalid action code provided0x11
: Invalid attribute number provided0x12
: Asked to write a read-only attribute0x30
: Caller process is not thesys::process
service0x31
: This system service is not allowed to access or edit this attribute0x32
: Provided index is out-of-bounds
0xD4
SYS_ENUM_DEVICES
Syscall resserved to the sys::hw
service.
List devices with a provided connection interface identifier (CII).
For each device, its raw device descriptor (RDD) (up to 260 bytes) is written to the provided address.
By prodiving a pattern with all bits set and a full CII, it is possible to retrieve informations from a single connection port.
Arguments:
- Pattern type (1 byte):
- Bit 0: match all connection types
- Bit 1: match all buses
- Bit 2: match all port numbers
- CII pattern of the devices to list (4 bytes)
- Writable buffer (16 bytes)
Return value:
- Number of devices found with the provided criterias (4 bytes)
0x00
is all devices were written,0x01
otherwise (1 byte)
Errors:
0x10
: Invalid connection type in CII0x30
: Caller process is not thesys::hw
service
0xD5
CREATE_CONTAINER
Create a container.
Arguments:
None
Return value:
- Container ID (8 bytes)
Errors:
0x30
: Caller process is not thesys::proc
service.
0xD6
LINK_CONTAINER_DEVICE
Make a hardware device available to a previously-created container.
The device may be a real hardware device or a virtual one.
Arguments:
- KDI of the device to link (8 bytes)
Return value:
None
Errors:
0x30
: Caller process is not thesys::proc
service.0x31
: Unknown KDI provided
0xD7
DESTROY_CONTAINER
Destroy a previously-created container as well as all its children.
Arguments:
- Container ID (8 bytes)
Return value:
None
Errors:
0x30
: Caller process is not thesys::proc
service.0x31
: Unknown conatiner ID provided
Integration services
This document lists all integration services, which are services whose methods and notifications follow a convention established by the system itself to permit an integration at different levels.
An application exposing such a service in its manifest will automatically be assignable to specific tasks, such as being set as the default desktop environment or file manager.
These services are not available directly to the end applications ; they can only be used through system services.
Error codes
Error codes follow a specific convention, but all may not be returned by the services in case of errors.
Some error codes are only reserved to services supporting the additional check tied to the said error code.
List of integration services
Desktop Environments
The Desktop Environment (DE) is an application that acts as the main graphical interface of NightOS.
It is launched with the system, does never stop until the system itself stops, and handles most of the user interface.
The exposed service for desktop environments is SYS_DENV
.
User interface concepts
User interface is technically free of any rule ; but it should usually follow the conventions described in the user experience document.
Window's state
In this document, a window's state is a data structure describing a window.
It is used both when getting informations about the window, or when changing them.
Custom elements are non-conventional elements handled by this specific desktop environment.
Note that each window has an identifier, used to access it or identify from notifications. These identifiers must either be different or scoped for each client process, as different clients should not be able to access one another's windows.
Header and content
The window's state is made of a header, containing 1 bit for each conventional element to get/update (listed below). Each bit must be set if the element is provided, unset otherwise.
It is followed by the number of custom elements to get/update (2 bytes), and the list of custom elements' identifiers (4 bytes per identifier).
The header is followed by the value of each element to update, only if the element must be updated.
Header bit | Description | Type / Size in bytes | Content |
---|---|---|---|
0 | Title | String | |
1 | Buttons | Buttons | |
2 | Icon | Icon | |
3 | Width | 2 bytes | In pixels |
4 | Height | 2 bytes | In pixels |
5 | X coordinate | 2 bytes | In pixels |
6 | Y coordinate | 2 bytes | In pixels |
7 | Active attribute | 1 byte | 0x01 to set the attribute |
8 | Interactive attribute | 1 byte | 0x01 to set the attribute |
9 | Fixed-size attribute | 1 byte | 0x01 to set the attribute |
10 | Display layer | 1 byte | Layer number |
11 | Display state | Display state | |
12-31 | Future-proof |
Delimited title strings
Delimited title strings are encoded as delimited strings.
Icons
Icons are encoded as bitmap images.
Buttons
- Buttons count (1 byte)
Then, for each button:
- Label's delimited title string
- Label's icon (1 byte):
- Bit 0: does the button have an icon?
- Bit 1: is the button placed on the left? (else will be placed on the right)
- Bit 2: should the icon be displayed in monochrome?
- Label's icon
- State (1 byte):
- Bit 0: Is disabled?
- Bit 1: Does it use a custom color?
- Color (4 bytes for RGB and transparency) - filled with zeroes if custom color not set in state
- Callback code (1 byte) - for internal use in the client
Display state
The display state is one value among:
0x00
: Restored (default)0x01
: Maximized0x02
: Minimized0x03
: Fullscreenized0x04
: Kiosk mode
Methods
0x0001
SUBSCRIBE_CAPABILITIES
Subscribe to change of capabilities listed by the CAPABILITIES
method.
Arguments:
None
Answer:
None
Errors:
None
0x0002
CAPABILITIES
List elements which can be handled by the desktop environment.
For more informations about such elements, see the user experience document
Arguments:
None
Answer:
Bits set to 1
if the element is handled (supported) by the DE, 0
else. In each byte below, bits are listed from the strongest to the weakest.
Note that the bit should only be set if the desktop environment is currently able to handle the said element, which means that its display settings change, this answer's content will most probably change.
-
Global mechanisms (4 bytes)
- Bit 0: Windows
- Bit 1: Moving windows
- Bit 2: Display layers
- Bit 3: Pointing devices
- Bit 4: Notifications
- Bit 5: Notification center
- Bit 6: Quick settings
- Bit 7: Lockscreen
- Bit 8: Taskbar
- Bit 9: Dock
- Bit 10: Desktop icons
- Bit 11: Wallpaper
- Bit 12: Customizable wallpaper
- Bit 13: Popups
- Bit 14: Is the interface customizable?
- Remaining bits: Future-proof
-
Windows' outer interface (4 bytes)
- Bit 0: Titlebar for windows
- Bit 1: Custom title in titlebars
- Bit 2: Custom icon in titlebars
- Bit 3: Close button in titlebars
- Bit 4: Customizable buttons in titlebars
- Bit 5: Disabled buttons in titlebars
- Bit 6: Custom colors for buttons in titlebars
- Bit 7: Can windows be made non-interactive
- Bit 8: Can windows be made fixed-size
- Remaining bits: Future-proof
-
Windows' dynamic changes (4 bytes)
- Bit 0: Windows' focus
- Bit 1: Ability to move windows
- Bit 2: Ability to resize windows
- Bit 3: Ability to reorder windows in layers (superposition)
- Bit 4: Ability to hide or minimize windows
- Bit 5: Ability to restore and maximize windows
- Bit 6: Ability to set the window to fullscreen
- Bit 7: Ability to set the window to kiosk mode
- Remaining bits: Future-proof
-
Windows' dynamic changes by the user (4 bytes)
- Same as Windows' dynamic changes, but set only if the user can achieve the specified operation themselves
-
Popups' dynamic changes (4 bytes)
- Bit 0: Popups' focus
- Bit 1: Ability to move popups
- Bit 2: Ability to resize popups
- Bit 3: Ability to reorder popups in layers (superposition)
- Bit 4: Ability to hide or minimize popups
- Bit 5: Ability to restore and maximize popups
- Bit 6: Ability to set the window to fullscreen
- Bit 7: Ability to set the window to kiosk mode
- Remaining bits: Future-proof
-
Popups' dynamic changes by the user (4 bytes)
- Same as Popups' dynamic changes, but set only if the user can achieve the specified operation themselves
-
Notifications (4 bytes) - filled with zeroes if notifications are not supported
- Basic notifications (4 bytes):
- Bit 0: Support for custom icons
- Remaining bits: Future-proof
- Music player notifications (4 bytes):
- Bit 0: Support for music player notifications
- Bit 1: Support for track's title
- Bit 2: Support for album's title
- Bit 3: Support for artist's name
- Bit 4: Support for album art
- Bit 5: Support for artist art
- Bit 6: Support for track's year
- Bit 7: Support for track's day/month/year
- Bit 8: Support for track's genre
- Bit 9: Support for playlist's name
- Bit 10: Support for progress bar
- Bit 11: Support for codec
- Bit 12: Support for frequency
- Bit 13: Support for resolution
- Bit 14: Support for bitrate
- Bit 15: Support for visualizer
- Bit 16: Support for displaying next title in queue
- Bit 17: Support for
current track/queue size
display - Bit 18: Support for pause/resume button
- Bit 19: Support for stop button
- Bit 20: Support for rewind/fast-forward button
- Bit 21: Support for previous/next buttons
- Bit 22: Support for like/dislike button
- Bit 23: Support for shuffle button
- Remaining bits: Future-proof
- Video player notifications (4 bytes):
- Bit 0: Support for video player notifications
- Bit 1: Support for video's title
- Bit 2: Support for video's author
- Bit 3: Support for video's source
- Bit 4: Support for episode's number
- Bit 5: Support for episode's serie name
- Bit 6: Support for codec
- Bit 7: Support for bitrate
- Bit 8: Support for video's
current track/queue size
display - Bit 9: Support for pause/resume button
- Bit 10: Support for stop button
- Bit 11: Support for rewind/fast-forward button
- Bit 12: Support for previous/next buttons
- Bit 13: Support for like/dislike button
- Bit 14: Support for shuffle button
- Remaining bits: Future-proof
- Basic notifications (4 bytes):
-
Integrations (4 bytes)
- Bit 0: Integration with the default file manager
- Bit 1: Thumbnails generation through the default file manager
- Remaining bits: Future-proof
0x1000
CREATE_WINDOW
Create a new window.
Unset elements in the provided window's state will be replaced by default values or behaviours.
Desktop environment-dependant errors can only be used if the error does not fit any other error code.
If the window is successfully create, the client is automatically subscribed to WINDOW_CHANGED
notifications for this specific window.
Arguments:
Answer:
- Window identifier (8 bytes)
- Window's state
Errors:
0x1000
: Invalid list of elements to create/update provided0x1010
: Title must be provided0x1011
: Title cannot be empty0x1012
: Title is not a valid UTF-8 string0x1013
: Unspecified error with the title0x1020
: Custom buttons provided but it is not customizable0x1021
: Too many custom buttons were provided0x1022
: A custom button's label is not a valid UTF-8 string0x1023
: A custom button's label cannot be empty0x1024
: Custom button's icon provided but it is not customizable0x1025
: Invalid custom button's icon buffer0x1026
: A custom button's icon is too small0x1027
: A custom button's icon is too large0x1028
: Unspecified error with a custom button's icon0x1029
: Unspecified error with a custom button's label0x102A
: Custom button's color provided but it is not customizable0x102B
: Custom button's color uses transparency but it is not customizable0x102C
: Duplicate custom button's callback code0x1030
: Custom icon provided but it is not customizable0x1031
: Invalid custom icon buffer0x1032
: Custom icon is too small0x1033
: Custom icon is too large0x1034
: Unspecified error with the provided custom icon0x1040
: Window's width provided but it is not customizable0x1041
: Window's width is too small0x1042
: Window's width is too large0x1043
: Window's height provided but it is not customizable0x1044
: Window's height is too small0x1045
: Window's height is too large0x1046
: Window's X coordinate provided but it is not customizable0x1047
: Window's X coordinate is too small0x1048
: Window's X coordinate is too large0x1049
: Forbidden window's X coordinate0x104A
: Window's Y coordinate provided but it is not customizable0x104B
: Window's Y coordinate is too small0x104C
: Window's Y coordinate is too large0x104D
: Forbidden window's Y coordinate0x104E
: Invalid window's width/height ratio0x104F
: Invalid window's X/Y coordinates couple0x1050
: Active attribute provided but it is not customizable0x1051
: Invalid active attribute provided0x1052
: Cannot apply the custom active attribute for unspecified reasons0x1053
: Interactive attribute provided but it is not customizable0x1054
: Invalid interactive attribute provided0x1055
: Cannot apply the custom interactive attribute for unspecified reasons0x1056
: Display layer provided but it is not customizable0x1057
: Display layer is too low0x1058
: Display layer is too high0x1059
: Forbidden display layer0x105A
: Display state provided but it is not customizable0x105B
: Invalid display state provided0x105C
: Cannot set this specific display state0x1060
: Provided custom elements but this DE does not have any custom one0x1100
to0x11FF
: Errors for custom elements0x3000
: Client is not allowed to create/update a window0x3100
to0x31FF
: Errors for custom elements0x3200
to0x32FF
: Desktop environment-dependant errors0x4000
: Failed to create/update the window0x4100
to0x41FF
: Errors for custom elements0x4200
to0x42FF
: Desktop environment-dependant errors
0x1001
UPDATE_WINDOW
Update an existing window.
Arguments:
- Window identifier (8 bytes)
- Window's state
Return value:
None
Errors:
Same errors as the CREATE_WINDOW
method, plus 0x3FFF
for unknown window identifier.
0x1002
GET_WINDOW_STATE
Get informations about an existing window.
Arguments:
- Window identifier (8 bytes)
- Window's state header
Return value:
Errors:
0x2000
: Invalid list of elements to create/update provided0x3000
: Unknown window identifier
0x10FF
DESTROY_WINDOW
Destroy an existing window.
Arguments:
- Window identifier (8 bytes)
- Skip the
WINDOW_CLOSED
(1 byte):0x01
to skip the notification,0x00
else
Return value:
None
Errors:
0x1000
: Invalid skip mode provided0x3000
: Unknown window identifier0x3001
: Failed to destroy the window0x3100
to0x31FF
: Desktop environment-dependant errors0x4100
to0x41FF
: Desktop environment-dependant errors
0x2000
CREATE_POPUP
Create a popup.
Arguments:
Same structure than for a window's state, but with the following common elements:
Return value:
- Popup identifier (8 bytes)
Errors:
0x1000
: Invalid list of elements provided0x1010
: Title must be provided0x1012
: Title cannot be empty0x1013
: Title is not a valid UTF-8 string0x1014
: Unspecified error with the title0x1020
: Custom buttons provided but it is not customizable0x1021
: Too many custom buttons were provided0x1022
: A custom button's label is not a valid UTF-8 string0x1023
: A custom button's label cannot be empty0x1024
: Custom button's icon provided but it is not customizable0x1025
: Invalid custom button's icon buffer0x1026
: A custom button's icon is too small0x1027
: A custom button's icon is too large0x1028
: Unspecified error with a custom button's icon0x1029
: Unspecified error with a custom button's label0x102A
: Custom button's color provided but it is not customizable0x102B
: Custom button's color uses transparency but it is not customizable0x102C
: Duplicate custom button's callback code0x1030
: Custom icon provided but it is not customizable0x1031
: Invalid custom icon buffer0x1032
: Custom icon is too small0x1033
: Custom icon is too large0x1034
: Unspecified error with the provided custom icon0x3000
: Client is not allowed to create a popup0x4000
: Failed to create/update the popup
0x20FF
DESTROY_POPUP
Destroy an existing popup.
Arguments:
- Popup identifier (8 bytes)
- Skip the
POPUP_CLOSED
(1 byte):0x01
to skip the notification,0x00
else
Return value:
None
Errors:
0x1000
: Invalid skip mode provided0x3000
: Unknown popup identifier0x4000
: Failed to destroy the popup
0x3000
SEND_BASIC_NOTIFICATION
Send a basic notification, "basic" meaning a non-special notification.
Arguments:
Subset of a window's state: title, icon, buttons.
Return value:
- Notification identifier (8 bytes)
Errors:
Relevant subset of the CREATE_WINDOW
method's errors.
0x3001
SEND_MUSIC_PLAYER_NOTIFICATION
Send a music player notificaiton.
Arguments:
Same structure than for a window's state, but with the following common elements:
Header bit | Description | Type / length in bytes | Content |
---|---|---|---|
0 | Track's title | String | |
1 | Album's title | String | |
2 | Artist's name | String | |
3 | Album art | Icon | |
4 | Artist art | Icon | |
3 | Track's year | 2 bytes | |
4 | Track's date | 4 bytes | Day (1 byte) ; Month (1 byte); Year (2 bytes) |
5 | Track's genre | String | |
6 | Playlist's name | String | |
7 | Progress time | 8 bytes | Elapsed time in seconds (4 bytes) ; Total time in seconds (4 bytes) |
8 | Pause state | 1 byte | 0x01 for active playback, 0x02 for paused, 0x03 for stopped |
9 | Like state | 1 byte | 0x01 if liked, 0x02 if disliked, 0x00 if not liked nor disliked |
10 | Codec | String | |
11 | Frequency | 4 bytes | Value in hertz (Hz) |
12 | Resolution | 2 bytes | Value in bits |
13 | Bitrate | 4 bytes | Value in bits/second |
14 | Visualizer informations | 24 bytes | One byte per frequency range (see below) |
18 | Next track's name | String | |
19 | Track position | 8 bytes | Track index (4 bytes) ; Tracks in the queue (4 bytes) |
20 | Buttons availibility | 1 byte | Pause/resume ; Rewind/Fast-forward ; Previous track ; Next track ; Shuffle |
The visualizer shows a value from 0 to 255 to indicate the bar eight. Each bar value corresponds to a specific frequency range. The lower frequency bound of each bar can be found with: exponential(i / 1.88) - 1
, where i
is the bar number, from 0 to 23.
Return value:
- Notification identifier (8 bytes)
Errors:
0x1000
: Invalid list of elements to create/update provided0x1001
: Track's title is not a valid UTF-8 string0x1002
: Album's title is not a valid UTF-8 string0x1003
: Artist's name is not a valid UTF-8 string0x1004
: Invalid pause state0x1005
: Invalid like state0x1006
: Next track's title cannot be empty0x1007
: Next track's title is not a valid UTF-8 string0x1008
: Button avaibility bit provided for unknown button0x1100
to0x11FF
: Errors for custom elements0x3000
: Audio player notifications are not supported0x3001
: Client is not allowed to create/update an audio player notification0x3100
to0x31FF
: Errors for custom elements0x3200
to0x32FF
: Desktop environment-dependant errors0x4000
: Failed to create/update the notification0x4100
to0x41FF
: Errors for custom elements0x4200
to0x42FF
: Desktop environment-dependant errors
0x3002
SEND_VIDEO_PLAYER_NOTIFICATION
Send a video player notificaiton.
Arguments:
Same structure than for a window's state, but with the following common elements:
Header bit | Description | Type / length in bytes | Content |
---|---|---|---|
0 | Video's title | String | |
1 | Video's author | String | |
2 | Video's source | String | |
3 | Episode's number | 4 bytes | Current episode (2 bytes) ; Total episodes in the season (2 bytes) |
4 | Episode's serie name | String | |
5 | Codec | String | |
6 | Bitrate | 4 bytes | Value in bits/second |
7 | Video position | 8 bytes | Video index (4 bytes) ; Videos in the queue (4 bytes) |
8 | Buttons availibility | 1 byte | Pause/resume ; Rewind/Fast-forward ; Previous track ; Next track ; Shuffle |
Return value:
- Notification identifier (8 bytes)
Errors:
0x1000
: Invalid list of elements to create/update provided0x1001
: Video's title is not a valid UTF-8 string0x1002
: Video's author is not a valid UTF-8 string0x1003
: Video's source is not a valid UTF-8 string0x1004
: Codec is not a valid UTF-8 string0x1100
to0x11FF
: Errors for custom elements0x3000
: Audio player notifications are not supported0x3001
: Client is not allowed to create/update an audio player notification0x3100
to0x31FF
: Errors for custom elements0x3200
to0x32FF
: Desktop environment-dependant errors0x4000
: Failed to create/update the notification0x4100
to0x41FF
: Errors for custom elements0x4200
to0x42FF
: Desktop environment-dependant errors
Notifications
0x0001
CAPABILITIES_CHANGED
Sent to clients that subscribed through the SUBSCRIBE_CAPABILITIES
method when the elements the desktop environment can handle changed (for instance when the taskbar is hidden, when the windows' layers become fixed, ...).
Datafield:
Equivalent content to the CAPABILITIES
' answer.
0x1000
WINDOW_CHANGED
Notification sent when a window's state changed.
Only the elements that changed since the last notification will be set in the sent window's state.
Datafield:
- Window identifier (8 bytes)
- Window's state
0x1001
WINDOW_INTERACTION
Sent when the user interacts with a window. These events should not occur if the window is not active
Datafield:
- Window identifier (8 bytes)
This is followed by the interaction type on 1 byte, then by the interaction's data, which can be either:
-
0x00
: Pointing device event- Type (1 byte):
0x00
: Pointer moved0x01
: Left click pressed0x02
: Left click released0x03
: Middle click pressed0x04
: Middle click released0x05
: Right click pressed0x06
: Right click released
- X coordinate in pixels (2 bytes) (relative to the window's drawing zone left side)
- Y coordinate in pixels (2 bytes) (relative to the window's drawing zone top side)
- Type (1 byte):
-
0x01
: Scrolling- Direction (1 byte):
0x00
: Up0x01
: Down0x02
: Left0x03
: Right0x04
: Shake
- Direction (1 byte):
-
0x02
: Keyboard event- Hold type (1 byte):
0x00
: Key was pressed0x11
: Key was released
- Key code (1 byte), normalized by the
sys::hw
service
- Hold type (1 byte):
0x10FF
WINDOW_CLOSED
Datafield:
- Window identifier (8 bytes)
0x2000
POPUP_INTERACTION
Sent when the user selects a button in the list provided by the popup.
Datafield:
- Popup identifier (8 bytes)
- Callback code (1 byte)
0x20FF
POPUP_CLOSED
Sent when a popup was closed by the end user.
Datafield:
- Popup identifier (8 bytes)
0x3000
BASIC_NOTIFICATION_INTERACTION
Sent when the user interacts with a basic notification.
Datafield:
- Callback code (1 byte)
0x3001
AUDIO_PLAYER_NOTIFICATION_INTERACTION
Sent when the user interacts with an audio player notification.
Datafield:
- Button code (1 byte):
0x00
: Pause/Resume0x01
: Stop0x02
: Rewind0x03
: Fast-forward0x04
: Previous0x05
: Next0x06
: Like0x07
: Dislike0x08
: Like/dislike toggle0x09
: Shuffle
0x3002
VIDEO_PLAYER_NOTIFICATION_INTERACTION
Sent when the user interacts with a video player notification.
Datafield:
- Button code (1 byte):
0x00
: Pause/Resume0x01
: Stop0x02
: Rewind0x03
: Fast-forward0x04
: Previous0x05
: Next0x06
: Like0x07
: Dislike0x08
: Like/dislike toggle0x09
: Shuffle
0x30FF
NOTIFICATION_CLOSED
Sent a notification is closed.
Datafield:
- Notification identifier (8 bytes)
File Manager
A file manager is an application which can manage the filesystem's content. It does not have any restriction on its user interface, but needs to expose an integration service with the standardized methods and notifications described in this document.
Applications can indicate themselves as file manager by specifying an SYS_FMAN
service in their manifest. They rely on file openers to open files.
The end user chooses a single file manager (called the default file manager) between all available ones, whose service will be used by other applications.
Methods
0x1000
OPEN_ITEM
Open a filesystem item.
Arguments:
Answer:
None
Errors:
0x3000
: Invalid FMP0x3001
: Could not find the provided item0x4000
: User cancelled the opening0x4001
: Could not find an application to open the provided item0x4002
: Failed to open the provided item due to an I/O error0x4FFF
: Unspecified error
0x1001
IS_ITEM_OPENABLE
Check if a filesystem item could be opened without user interaction.
Arguments:
Answer:
0x02
if the file could be opened without user interaction,0x01
if it couldn't,0x00
if the file couldn't be opened at all
Errors:
0x3000
: Invalid FMP0x3001
: Could not find the provided item0x4000
: Unspecified error
0x2000
GET_THUMBNAIL
Get the thumbnail for a specific item.
The thumbnail should be generated using the sys::fsh
system service, which will provide the cached thumbnail (if any) and else ask for a thumbnail buffer, which will be put in the cache if relevant.
The thumbnail can be requested to have a specific width and height. The returned thumbnail may not have the correct size, but this will require the system to resize the picture.
Arguments:
- Refresh mode (1 byte):
0x00
to get the current thumbnail or a cached one,0x01
to force generating a new thumbnail for the item - Filesystem path
- Requested thumbnail width (2 bytes) and height (2 bytes) in pixels, or
0
if no request
Answer:
- Thumbnail as a bitmap image
Errors:
0x3000
: Invalid FMP0x3001
: Could not find the provided item0x4000
: The thumbnail generator failed0x4FFF
: Unspecified error
0x2100
GET_VIDEO_PREVIEW
Get the video preview for a specific item.
The preview should be generated using the sys::fsh
system service, which will provide the cached preview (if any) and else ask for a preview buffer, which will be put in the cache if relevant.
The preview can be requested to have a specific width and height. The returned preview may not have the correct size, but this will require the system to resize the video.
Arguments:
- Refresh mode (1 byte):
0x00
to get the current preview or a cached one,0x01
to force generating a new preview for the item - Filesystem path
- Requested preview width (2 bytes) and height (2 bytes) in pixels, or
0
if no request
Answer:
- Preview as a bitmap video
Errors:
0x3000
: Invalid FMP0x3001
: Could not find the provided item0x4000
: The preview generator failed0x4FFF
: Unspecified error
0xA000
CONTEXT_MENU
Generate a context menu for a specific filesystem item. Used by the DEA.
Arguments:
Answer:
- Interface window identifier (8 bytes)
Errors:
0x3000
: Invalid FMP0x3001
: Could not find the provided item0x4000
: User cancelled the opening0x3001
: Could not find an application to open the provided item0x4002
: Failed to open the provided item due to an I/O error0x4FFF
: Unspecified error
File openers
A file opener is an application which can associate open specific items. It exposes an integration service with the standardized methods and notifications described in this document.
The list of openers can be viewed and modified through the sys::fsh
.
They are essentially used by the default file manager.
Methods
0x1000
OPEN_ITEM
Open a filesystem item.
Arguments:
Answer:
None
Errors:
0x3000
: Invalid FMP0x3001
: Could not find the provided item0x3002
: User cancelled the opening0x3003
: Could not find an application to open the provided item0x3004
: Failed to open the provided item due to an I/O error0x3FFF
: Unspecified error
0x2000
GENERATE_THUMBNAIL
Generate a thumbnail for a given filesystem item.
The thumbnail should be generated using the sys::fsh
system service, which will provide the cached thumbnail (if any) and else ask for a thumbnail buffer, which will be put in the cache if relevant.
Arguments:
Answer:
Errors:
0x3000
: Invalid FMP0x3001
: Could not find the provided item0x3002
: This application does not support thumbnail generation for this item type0x3003
: Thumbnail could not be generated from the item as its content is invalid0x3FFF
: Unspecified error
0x2001
CAN_GENERATE_THUMBNAIL
Check if the application can generate a thumbnail for a specific filesystem item.
If it cannot, the default file manager will be in charge of providing a placeholder or using an alternative thumbnail generator.
Arguments:
Answer:
0x01
if the application can generate a thumbnail for the provided filesystem item,0x00
else
Errors:
0x3000
: Invalid FMP0x3001
: Could not find the provided item0x3002
: Thumbnail could not be generated from the item as its content is invalid0x3FFF
: Unspecified error
Filesystem interfaces
Filesystem interfaces are services that act as a layer of indirection between a storage driver and the sys::fs
service. Their role is to translate all common filesystem operations, such as items management and modification, for filesystems that aren't natively supported. They heavily communicate with the sys::hw
service to communicate with the underlying storage device.
A filesystem is tied to only one partition, and each partition has a filesystem. A storage space is made, unless empty, of a partitions table which lists all the available partitions.
Most methods and notifications of filesystems interfaces are also available in the sys::fs
service, with first perform permissions checking and determines the device and location to perform the operation on. It also leverages many more powerful features such as items watching, and features that are our of the scope of a filesystem interface such as partitions management.
As an application can only expose one single filesystem interface service, the said service will handle all of the hardware devices driven by the said process. This is why the the SOR of the target partition and filesystem is provided for each request.
- Nomenclature
- Methods
0x0001
HANDLED_FS_LIST0x0002
IS_VALID_PARTITION0x0003
FS_METADATA0x1000
ITEM_EXISTS0x1001
FEID_TO_SPLIT0x1002
ITEM_METADATA0x1003
RENAME_ITEM0x1004
MOVE_ITEM0x1005
DELETE_ITEM0x2000
CREATE_DIRECTORY0x2001
READ_DIRECTORY0x3000
CREATE_FILE0x3001
READ_FILE_SYNC0x3002
READ_FILE_ASYNC0x3003
WRITE_FILE_SYNC0x3004
WRITE_FILE_ASYNC0x4000
CREATE_SYMLINK0x4001
UPDATE_SYMLINK0x4002
READ_SYMLINK0xF000
FORMAT_ASYNC
- Notifications
Nomenclature
Storage operating range
The storage operating range (SOR) is a 40-byte information transmitted to all operation methods of the filesystem interface to indicate where the requested operation should be performed and on which devices.
It is composed of:
- The device's UDI (8 bytes)
- The partition's first byte address on the device (8 bytes)
- The partition's last byte address on the device (8 bytes)
- Authorization token (8 bytes)
- The filesystem's FSID (8 bytes)
The FSID is only useful to identify partitions more easily, and to inform clients in notifications.
All SOR sent to the interface service are guaranteed to follow this specification, but may be invalid in case of a bad timing (e.g. authorization is revoked just before a request is performed).
Filesystem paths
Filesystem paths are used to refer to specific filesystem elements and are encoded as either a 0x01
byte followed by a FEID, or a 0x02
byte followed by a split path.
All filesystem paths sent to the interface service are guaranteed to follow this specification.
Split paths
All paths manipulated and returned by a filesystem interface services are encoded as split paths: delimited lists of delimited strings.
Each entry of the list must be a path's component. The entries, joined with a slash (/
), should form a valid path.
Empty and .
components are ignored, while components equal to ..
will make this component ignored and remove the previous component in the list.
The slash (/
) character is forbidden in components.
An empty path refers to the root directory (when applicable).
Filenames
Filenames are a delimited strings with a few restrictions:
- Slash
/
characters are forbidden NULL
characters (U+0000
) are forbidden
Moreover, filenames cannot be:
- Empty
- Be made only of spaces or insecable spaces
- Strictly equal to
.
- Strictly equal to
..
- Longer than 65535 characters
Additional limitations may apply for some filesystems. Refer to each filesystem's specification to see the eventual limitations on filenames.
All filenames sent to the interface service are guaranteed to be follow this specification.
Capabilities list
The capabitilies list is a 2-byte long value which indicate the capabitilies of a given filesystem:
- Bit 0: set if the filesystem is writable
- Bit 1: set if the filesystem handles symbolic links
- Bit 2: set if the filesystem allows symbolic links to non-existing items
- Bit 3: set if the filesystem allows symbolic links to cross-filesystem items (need to store the target's FSID)
- Bit 3: set if the filesystem supports hidden flag
- Bit 4: set if the filesystem supports readonly flag
- Bit 5: set if the filesystem can store creation dates
- Bit 6: set if the filesystem can store modification dates
- Bit 7: set if the filesystem can store last access dates
- Bit 8: set if the filesystem can store an owner UID on 8 bytes
- Bit 9: set if the filesystem can natively store a storage permissions map
- Bit 10: set if the interface can store a storage permissions map in the filesystem
- Bit 11: set if the filesystem supports ahead space reservation
In case the filesystem can't natively handle a storage permissions map (SPM), the interface is allowed to store, query and update the map in the filesystem by itself, using non-native ways such as extended attributes. In such case, the relevant bit must be set to indicate a SPM can be stored on this filesystem.
This list acts as a contract between the service and the sys::fs
service ; if a capability bit is not set, the related methods are guaranteed to never be called. If it is set, the "incompatibility" error code present in such methods cannot be returned or it will be considered a bug.
Filesystem metadata
A filesystem metadata is a structure describing the a single filesystem:
- Capabilities list (2 bytes)
- Cluster size in bytes (4 bytes)
- Partition's total size in bytes (8 bytes)
- Partition's used space in bytes (8 bytes)
- Partition's free space in bytes (8 bytes) - used in case a part of the partition could not be used for any reason
- Optional partition's name as a delimited string
- Optional partition's icon
Item type byte
An item type byte is a single-byte value describing the type of a single item in a filesystem:
0x01
: directory0x02
: file0x03
: symbolic link0xFF
: unknown item type
Item flags
Each item has associated flags:
- Bit 0: hidden flag
- Bit 1: read-only flag
Item metadata
An item metadata is a structure describing the content of a single item in a filesystem:
- FEID (8 bytes)
- Item type byte (1 byte)
- Item flags (1 byte)
- Size in bytes (8 bytes) -
0
for directories - Optional owner UID (8 bytes)
- Optional creation date timestamp (1 + 8 bytes)
- Optional modification date timestamp (1 + 8 bytes)
- Optional last access date timestamp (1 + 8 bytes)
- Item's filename
Methods
0x0001
HANDLED_FS_LIST
Get informations about each filesystem handled by the current interface.
Arguments:
None
Answer:
Errors:
none
0x0002
IS_VALID_PARTITION
Check if a given partition is of the type of filesystem handled by the current interface.
Arguments:
- First bytes of the partition (512 bytes)
- Last bytes of the partition (512 bytes)
Answer:
- Identifier of the filesystem if it can be handled by the current interface,
0
otherwise (1 byte)
Errors:
None
0x0003
FS_METADATA
Get metadata on a given filesystem.
Arguments:
- SOR (40 bytes)
Return value:
Error codes:
0x3000
: Invalid SOR provided
0x1000
ITEM_EXISTS
Check if a given item exists.
Arguments:
- SOR (40 bytes)
- Filesystem path
- Item type byte (1 byte) to check if the item exists and is of a specific type -
0
to accept all types
Return value:
- Item type byte (1 byte)
Errors:
0x3000
: Invalid SOR provided
0x1001
FEID_TO_SPLIT
Convert a FEID to the corresponding split path.
Arguments:
Return value:
Errors:
0x3000
: Invalid SOR provided0x3001
: The provided FEID was not found in the filesystem
0x1002
ITEM_METADATA
Get the metadata of a given item.
Arguments:
- SOR (40 bytes)
- Filesystem path
Return value:
Errors:
0x3000
: Invalid SOR provided0x3001
: The provided path was not found
0x1003
RENAME_ITEM
Rename an existing item.
Arguments:
- SOR (40 bytes)
- Filesystem path
- New filename
Errors:
0x3000
: Invalid filename provided0x3001
: Invalid SOR provided0x3002
: The provided path was not foud
0x1004
MOVE_ITEM
Move an existing item.
Arguments:
- SOR (40 bytes)
- Filesystem path
- New parent directory
- Optional new filename
Errors:
0x1000
: Invalid filename provided0x3000
: Invalid SOR provided0x3001
: The provided path was not found0x3002
: Target directory was not found0x4000
: Target directory's maximum capacity has been reached0x4001
: Maximum nested items number has been reached0x4002
: Maximum path length has been reached0x4003
: Item cannot be moved for unspecified reasons0x4FFF
: Unspecified filesystem error
0x1005
DELETE_ITEM
Delete an item.
Arguments:
- SOR (40 bytes)
- Filesystem path
Return value:
None
Errors:
0x3000
: Invalid SOR provided0x3001
: Item was not found0x3002
: Cannot remove a non-empty directory0x4FFF
: Unspecified filesystem error
0x2000
CREATE_DIRECTORY
Create a directory.
Arguments:
- SOR (40 bytes)
- Filesystem path of the parent directory
- New item's filename
Return value:
None
Errors:
0x1000
: Invalid filename provided0x3000
: Invalid SOR provided0x3001
: Parent directory was not found0x4000
: Directory's maximum capacity has been reached0x4001
: Maximum nested items number has been reached0x4002
: Maximum path length has been reached0x4FFF
: Unspecified filesystem error
0x2001
READ_DIRECTORY
List all entries in a directory.
If the provided offset is larger than the number of entries in the directory, all remaining items must be returned.
If the number of items to get is larger than the number of entries in the directory less the start offset, all remaining items must be returned.
Arguments:
- SOR (40 bytes)
- Filesystem path
- Start offset (8 bytes)
- Number of items to get (8 bytes) -
0
to list all items at once - Optional total number of entries, if available (1 + 8 bytes)
- Hidden flag match (1 byte): set to
0x00
to match only non-hidden items,0x01
to match all items
Return value:
Errors:
0x3000
: Invalid SOR provided0x3001
: Directory was not found0x4FFF
: Unspecified filesystem error
0x3000
CREATE_FILE
Create a file.
The reserved size is only provided if the interface reported the filesystem as supporting this feature.
Arguments:
- SOR (40 bytes)
- Filesystem path of the parent directory
- New item's filename
- Optional reserved size (1 + 8 bytes)
- Optional length of the buffer to write (1 + 8 bytes)
- Buffer to write
Return value:
None
Errors:
0x1000
: Invalid filename provided0x3000
: Invalid SOR provided0x3001
: Parent directory was not found0x4000
: Directory's maximum capacity has been reached0x4001
: Maximum nested items number has been reached0x4002
: Maximum path length has been reached0x4003
: Storage's capacity exceeded0x4004
: Maximum individual file size exceeded0x4005
: Filesystem's free space exceeded0x4FFF
: Unspecified filesystem error
0x3001
READ_FILE_SYNC
Read a file synchronously.
If no read length is provided, the whole file must be read.
Arguments:
Return value:
- Number of read bytes (8 bytes)
- File's content
Errors:
0x3000
: Invalid SOR provided0x3001
: Start offset is out-of-range0x4FFF
: Unspecified filesystem error
0x3002
READ_FILE_ASYNC
Asynchronously read a file to a writable abstract memory segment (AMS).
THe number of bytes to read is always provided to ensure it does not accidentally exceed the AMS's size.
When the read is complete, a FILE_READ
notification must be sent to the client.
Arguments:
- SOR (40 bytes)
- File's path
- Start offset address (8 bytes)
- Number of bytes to read (8 bytes)
- AMS identifier (8 bytes)
Return value:
- Generated task identifier (8 bytes)
Errors:
0x3000
: Invalid SOR provided0x3001
: Invalid AMS ID provided0x4FFF
: Unspecified filesystem error
0x3003
WRITE_FILE_SYNC
Synchronously write a buffer to a file.
If no offset address is provided, the file's content must be completely overriden with the provided buffer.
Arguments:
- SOR (40 bytes)
- File's path
- Optional write offset address (1 + 8 bytes)
- Number of bytes to write (8 bytes)
- Buffer to write (8 bytes)
Return value:
None
Errors:
0x3000
: Invalid SOR provided0x3001
: Offset is out-of-range0x4000
: Storage's capacity exceeded0x4001
: Maximum individual file size exceeded0x4002
: Filesystem's free space exceeded0x4FFF
: Unspecified filesystem error
0x3004
WRITE_FILE_ASYNC
Asynchronously write a readable abstract memory segment (AMS) to a file.
If no offset address is provided, the file's content must be completely overriden with the provided buffer.
When the writing is complete, a FILE_WRITTEN
notification must be sent to the client. The notification must not be sent before the current method returned successfully.
Arguments:
- SOR (40 bytes)
- File's path
- Optional write offset address (1 + 8 bytes)
- Number of bytes to write (8 bytes)
- AMS identifier (8 bytes)
Return value:
- Generated task identifier (8 bytes)
Errors:
0x3000
: Invalid SOR provided0x3001
: Invalid AMS ID provided0x4FFF
: Unspecified filesystem error
0x4000
CREATE_SYMLINK
Create a symbolic link.
Arguments:
- SOR (40 bytes)
- Filesystem path of the parent directory
- New item's filename
- Symlink's target FSID (8 bytes)
- Symlink's target path (always a split path for other filesystems)
Return value:
None
Errors:
0x1000
: Invalid filename provided0x3000
: Invalid SOR provided0x3001
: Parent directory was not found0x3002
: Cannot create symbolic links to cross-filesystem items0x3003
: Cannot create symbolic links to non-existing items0x4000
: Directory's maximum capacity has been reached0x4001
: Maximum nested items number has been reached0x4002
: Maximum path length has been reached0x4003
: Storage's capacity exceeded
0x4001
UPDATE_SYMLINK
Create a symbolic link.
Arguments:
- SOR (40 bytes)
- Symlink's path
- Symlink's target FSID (8 bytes)
- Symlink's target path
Return value:
None
Errors:
0x3000
: Invalid SOR provided0x3001
: Provided path was not found0x3002
: Cannot crate symbolic links to cross-filesystem items0x3003
: Cannot crate symbolic links to non-existing items
0x4002
READ_SYMLINK
Read a symbolic link's target.
Arguments:
Return value:
- Symlink's target FSID (8 bytes)
- Symlink's target path
Errors:
0x3000
: Invalid SOR provided0x3001
: Provided path was not found0x4000
: Symbolic link is cyclic
0xF000
FORMAT_ASYNC
Asynchronously format the partition to get an empty filesystem. Once the formatting is complete,
Arguments:
Return value:
- Generated task identifier (8 bytes)
Error codes:
0x1000
: Invalid sector size provided0x3000
: Invalid SOR provided
Notifications
0x3002
FILE_READ
Sent to a client after an asynchronous file reading requested using the READ_FILE_ASYNC
method completed.
Datafield:
- Task identifier (8 bytes)
- Fallible result with:
- Success data: number of bytes read (8 bytes)
- Error code (1 byte)
0x20
: Start offset is out-of-range0x40
: Unspecified filesystem error
0x3004
FILE_WRITTEN
Sent to a client after an asynchronous file writing requested using the WRITE_FILE_ASYNC
method completed.
Datafield:
- Task identifier (8 bytes)
- Fallible result with:
- Success data: None
- Error code (1 byte):
0x20
: Start offset is out-of-range0x31
: Maximum individual file size exceeded0x32
: Filesystem's free space exceeded0x40
: Unspecified filesystem error
0xF000
FORMATTED
Sent to a client after an formatting requested using the FORMAT_ASYC
method completed.
Datafield:
- Task identifier (8 bytes)
- Fallible result with:
- Success data: None
- Error code (1 byte):
0x40
: Unspecified filesystem error
Driver services
This document lists all driver services, which are services whose methods and notifications follow a convention established by the sys::hw
service to permit interaction with hardware devices.
An application exposing such a service in its manifest will gain the permission to register itself as a driver through the sys::hw
service.
These services are not available directly to the end applications ; they can only be used through system services.
Error codes
Error codes follow a specific convention, but all may not be returned by the services in case of errors.
Some error codes are only reserved to services supporting the additional check tied to the said error code.
Also, all drivers can use:
- The
0x30
error code to indicate the provided UDI is invalid or not driven by the current driver service - The
0x5F
error code to indicate an unspecified hardware error occurred.
List of driver services
The list below indicates the service as well as the DDT it is tied to.
Storage drivers
0x0001
Storage driver service
Storage driver services
Storage driver services handle storage devices. They are used to perform basic I/O operations.
As an application can only expose one single storage driver service, the said service will handle all of the hardware devices it drives. This is why the the UDI of the target device is provided for each request.
Methods
0x1001
STORAGE_INFOS
Get the storage's informations.
Arguments:
- Device's UDI (8 bytes)
Return value:
- Storage's size in bytes (8 bytes)
- Sector's size in bytes (8 bytes)
- Writability (1 byte):
0x00
if the storage is writable,0x01
else
Errors:
None
0x1100
READ_SYNC
Synchronously read a suite of sectors.
Arguments:
- Start sector number (8 bytes)
- Number of sectors to read (8 bytes)
Return value:
- Number of bytes read (8 bytes)
- Content of the read sectors
Errors:
0x3000
: Start sector is out-of-bounds0x3001
: End sector is out-of-bounds
0x1101
READ_ASYNC
Asynchronously read a suite of sectors to a writable abstract memory segment (AMS).
When the task completes, the SECTORS_READ
notification must be sent to the client.
Arguments:
- Start sector number (8 bytes)
- Number of sectors to read (8 bytes)
- AMS identifier (8 bytes)
Return value:
- Generated task identifier (8 bytes)
Errors:
0x3000
: Start sector is out-of-bounds0x3001
: End sector is out-of-bounds
0x1200
WRITE_SYNC
Synchronously write a suite of sectors.
The content to write is guaranteed to be aligned with the sectors' size.
Arguments:
- Start sector number (8 bytes)
- Number of bytes to write (8 bytes)
- Content to write
Return value:
None
Errors:
0x3000
: Start sector is out-of-bounds0x3001
: End sector is out-of-bounds
0x1201
WRITE_ASYNC
Asynchronously write a suite of sectors from an abstract memory segment (AMS).
When the task completes, the SECTORS_WRITTEN
notification must be sent to the client.
The content to write is guaranteed to be aligned with the sectors' size.
Arguments:
- Start sector number (8 bytes)
- Number of bytes to write (8 bytes)
- AMS identifier (8 bytes)
Return value:
- Generated task identifier (8 bytes)
Errors:
0x3000
: Start sector is out-of-bounds0x3001
: End sector is out-of-bounds
Notifications
0x1101
SECTORS_READ
Sent to a client after an asynchronous sectors reading requested using the READ_ASYNC
method completed.
Datafield:
- Task identifier (8 bytes)
- Fallible result with:
- Success data: number of bytes read (8 bytes)
- Error code (2 bytes): None
0x1201
SECTORS_WRITTEN
Sent to a client after an asynchronous sectors writing requested using the WRITE_ASYNC
method completed.
Datafield:
- Task identifier (8 bytes)
- Fallible result with:
- Success data: None
- Error code (2 bytes): None
System services
As NightOS' kernel is not monolithic but a microkernel, it only handles the most fundamental tasks of the system, like memory and processes management, as well as direct hardware communication.
The vast majority of its features can be found in system services, run by the system itself under the sys
DID.
This splitting implies that most low-level features of the system are documented in the individual services' specifications documents, which you will find here.
Nomenclature
System services are referred to as the sys::
services.
All methods and notifications describe the required permissions to use them, their arguments.
They also use common error codes:
0x00
: cannot read syscall's code or arguments (error while reading memory)0x01
: the requested syscall does not exist0x02
: at least one argument is invalid (e.g. providing a pointer to the0
address)0x03
: unmapped memory pointer (e.g. provided a pointer to a memory location that is not mapped yet)0x04
: memory permission error (e.g. provided a writable buffer to an allocated but non-writable memory address)0x05
: insufficient permissions0x06
: driver error0x10
to0x1F
: invalid arguments provided (e.g. value is too high)0x20
to0x2F
: arguments are not valid in the current context (e.g. provided ID does not exist)0x30
to0x3F
: resource errors (e.g. file not found)0x40
to0x4F
: driver errors0x50
to0x5F
: hardware errors0x60
to0xFF
: other types of errors
All methods return an answer, though it may be empty (indicated by a None). System services' answers always conclude the exchange.
List of system services
- PID 10:
sys::fs
: filesystem management - PID 11:
sys::fsh
: high-level filesystem management - PID 12:
sys::hw
: hardware communication - PID 13:
sys::perm
: permissions management - PID 14:
sys::net
: network communications - PID 15:
sys::crypto
: cryptography utilities - PID 16:
sys::crashsave
: crash saves management - PID 17:
sys::hydre
: Hydre shell service - PID 18:
sys::ui
: user interface service - PID 19:
sys::app
: applications management service - PID 20:
sys::process
: processes management service
sys::fs
service
The sys::fs
service is in charge of operations related to the filesystems.
sys::fs
service- Behaviour
- Methods
0x0001
IS_FS_MOUNTED0x0002
ENUM_FS0x0003
FS_METADATA0x0004
FS_MOUNT0x0005
FS_UNMOUNT0x0006
FS_WATCH0x0007
FS_UNWATCH0x1000
ITEM_EXISTS0x1001
FEID_TO_SPLIT0x1002
ITEM_METADATA0x2000
RENAME_ITEM0x2001
MOVE_ITEM0x2002
DELETE_ITEM0x3000
CREATE_DIRECTORY0x3001
READ_DIRECTORY0x4000
OPEN_FILE0x4001
CLOSE_FILE0x4002
READ_FILE0x4002
WRITE_FILE0x5000
CREATE_SYMLINK0x5001
UPDATE_SYMLINK0x5002
READ_SYMLINK0xA000
WATCH_ITEM0xA001
UNWATCH0xF000
FORMAT
- Notifications
Behaviour
Operations and latency
A single filesystem operation request from a client process up to the hardware device traverses:
- Client
sys::fs
service- filesystem interface (only if the filesystem is not supported natively)
- storage driver service (only if a filesystem interface is used, or if the storage device requires a specific driver)
- Hardware storage device
The response then goes up through all layers. Note that in all cases, the sys::hw
don't need to be contacted, thanks to direct driver access.
In the best scenario, which is for natively supported filesystems on storage devices that don't require a dedicated driver, direct storage access is possible, reducing the traversal to:
- Client
sys::fs
service- Hardware storage device
List of natively supported filesystems
The following filesystems are natively supported, meaning they don't require a filesystem interface to work properly:
- Btrfs
- Ext2 / Ext3 / Ext4
- NTFS
- FAT12 / FAT16 / FAT32
- exFAT
Extending supported filesystems
It is possible to use other filesystems that the natively supported ones, using filesystem interfaces.
This, however, creates a higher latency as direct access and operations are not permitted anymore. The typical sequence of operations becomes:
- The client calls the
sys::fs
service to perform a given operation - The operation is transmitted to the related filesystem interface...
- ...which in turns contact the
sys::hw
service... - ...which itself transmits the operation to the underlying storage driver
The information then goes up:
- From the driver to
sys::hw
- Then to the filesystem interface which translates it
- Then back to the
sys::fs
service - And finally to the client
Filesystems detection
The sys::fs
serviec is responsible for detecting filesystems. It performs this by contacting the sys::hw
service to enumerate and access the different storage devices, as well as being notified when a storage device is connected, disconnected or changes.
Filesystems are detected using a variety of methods. If all fail (which is, if the filesystem is not one that is natively supported), filesystem interfaces are used one by one to find one that can handle the said filesystem, using their IS_VALID_PARTITION
method.
Each partition then gets an identifier, the filesystem unique identifier (FSID), which is consistent across reboots but different between computers to avoid collection of informations from the FSID alone.
Methods
0x0001
IS_FS_MOUNTED
Check if a given filesystem is mounted.
Required permission: fs.filesystems.mounted
Arguments:
- FSID (8 bytes)
Return value:
0x01
if the filesystem is currently mounted,0x00
else
Errors:
None
0x0002
ENUM_FS
Enumerate all available filesystems.
Required permission: fs.filesystems.list
Arguments:
None
Return value:
- Delimited list of FSID (8 bytes)
Errors:
None
0x0003
FS_METADATA
Get informations on a filesystem.
Required permission: fs.filesystems.metadata
Arguments:
- FSID (8 bytes)
Return value:
- Mount timestamp (8 bytes)
- Mount path FEID (8 bytes)
- Option of the mounted volume file's FEID (1 + 8 bytes)
- Filesystem metadata
Errors:
0x3000
: The requested filesystem is currently not mounted
0x0004
FS_MOUNT
Mount an existing filesystem. If no mount path is provided, the filesystem will be mounted under the /mnt
directory.
New filesystems are automatically detected when storage devices are connected.
Required permission: fs.filesystems.mount
Arguments:
- FSID (8 bytes)
- Option of the mount path as a delimited string
Return value:
None
Errors:
0x3000
: Unknown FSID (8 bytes)0x3001
: This filesystem is already mounted
0x0005
FS_UNMOUNT
Unmount a mounted filesystem.
Required permission fs.filesystems.unmount
Arguments:
- FSID (8 bytes)
Return value:
None
Errors:
0x3000
: Unknown FSID (8 bytes)0x3001
: This filesystem is current not mounted
0x0006
FS_WATCH
Subscribe to FS_CHANGED
notifications when a filesystem is mounted or unmounted.
Required permission: fs.filesystems.watch
Arguments:
None
Return value:
None
Errors:
None
0x0007
FS_UNWATCH
Unsubscribe from FS_WATCH
.
Required permission: None
Arguments:
None
Return value:
None
Errors:
None
0x1000
ITEM_EXISTS
Check if a given item exists.
Required permissions:
fs.path.exists
to check any item typefs.feid.exists
to check only FEID
Arguments:
- FSID (40 bytes)
- Filesystem path
- Item type byte (1 byte) to check if the item exists and is of a specific type -
0
to accept all types
Return value:
- Item type byte (1 byte)
Errors:
0x3000
: Invalid FSID provided0x3001
: Requested filesystem is currently not mounted
0x1001
FEID_TO_SPLIT
Convert a FEID to the corresponding split path.
Required permissions:
fs.path.exists
Arguments:
Return value:
Errors:
0x3000
: Invalid FSID provided0x3001
: Requested filesystem is currently not mounted0x3002
: The provided FEID was not found in the filesystem
0x1002
ITEM_METADATA
Get the metadata of a given item.
Required permissions:
fs.items.metadata
to get symbolic links' metadata
Arguments:
- FSID (40 bytes)
- Filesystem path
Return value:
Errors:
0x3000
: Invalid FSID provided0x3001
: Requested filesystem is currently not mounted0x3002
: The provided path was not found
0x2000
RENAME_ITEM
Rename an existing item.
Required permissions:
fs.items.move
Arguments:
- FSID (40 bytes)
- Filesystem path
- New filename
Errors:
0x3000
: Invalid filename provided0x3001
: Invalid FSID provided0x3002
: Requested filesystem is currently not mounted0x3003
: The provided path was not foud
0x2001
MOVE_ITEM
Move an existing item.
fs.items.move
Arguments:
- FSID (40 bytes)
- Filesystem path
- New parent directory
- Optional new filename
Errors:
0x1000
: Invalid filename provided0x3000
: Invalid FSID provided0x3001
: Requested filesystem is currently not mounted0x3002
: The provided path was not found0x3003
: Target directory was not found0x4000
: Target directory's maximum capacity has been reached0x4001
: Maximum nested items number has been reached0x4002
: Maximum path length has been reached0x4003
: Item cannot be moved for unspecified reasons0x4FFF
: Unspecified filesystem error
0x2002
DELETE_ITEM
Delete an item.
Required permissions:
fs.items.remove.trash
to send items to the trashfs.items.remove
to delete items permanently
Arguments:
- FSID (40 bytes)
- Filesystem path
- Deletion mode (1 byte):
0x01
to send the item to the user's trash,0x02
to delete it permanently
Return value:
None
Errors:
0x3000
: Invalid FSID provided0x3001
: Requested filesystem is currently not mounted0x3002
: Item was not found0x3003
: Cannot remove a non-empty directory0x4FFF
: Unspecified filesystem error
0x3000
CREATE_DIRECTORY
Create a directory.
Required permissions:
fs.items.create
Arguments:
- FSID (40 bytes)
- Filesystem path of the parent directory
- New item's filename
Return value:
None
Errors:
0x1000
: Invalid filename provided0x3000
: Invalid FSID provided0x3001
: Requested filesystem is currently not mounted0x3002
: Parent directory was not found0x4000
: Directory's maximum capacity has been reached0x4001
: Maximum nested items number has been reached0x4002
: Maximum path length has been reached0x4FFF
: Unspecified filesystem error
0x3001
READ_DIRECTORY
List all entries in a directory.
Required permissions:
fs.dir.read
fs.dir.read.hidden
to also list hidden items
Arguments:
- FSID (40 bytes)
- Filesystem path
- Hidden flag match boolean (1 byte): match hidden items
Return value:
Errors:
0x3000
: Invalid FSID provided0x3001
: Requested filesystem is currently not mounted0x3002
: Directory was not found0x4FFF
: Unspecified filesystem error
0x4000
OPEN_FILE
Open a file with the provided options.
Opening a file with read access will prevent writes and removal while the file is open. Opening a file with write access will prevent read, writes and removal while the file is open.
Note that read and write access can be enabled together.
Required permissions:
fs.items.create
if the file must be createdfs.items.read
for read accessfs.items.write
for write access
Arguments:
- FSID (40 bytes)
- Enumeration (1 + ? bytes)
- Type ID
0x00
: Existing filesystem path to open - Type ID
0x01
: Existing filesystem path of the parent directory
- Type ID
- Enable read access (1 byte)
- Enable write access (1 byte)
Return value:
- Opened file ID (8 bytes)
Errors:
0x3000
: Invalid FSID provided0x3001
: Invalid parent directory provided0x3002
: Requested filesystem is not currently mounted0x3003
: Provided file path was not found0x3004
: Provided directory path was not found0x4000
: File is already opened0x4001
: Directory's maximum capacity has been reached0x4002
: Maximum nested items number has been reached0x4003
: Maximum path length has been reached0x4FFF
: Unspecified filesystem error
0x4001
CLOSE_FILE
Close a file.
Removes all locks on the opened file.
Arguments:
- Opened file ID (8 bytes)
Errors:
0x3000
: Invalid opened file ID provided
0x4002
READ_FILE
Read an open file.
Required permissions:
None (implicitly given by the initial OPEN_FILE call)
Arguments:
- Opened file ID (8 bytes)
- Offset to read from (8 bytes)
- Length to read (8 bytes)
- Writable buffer pointer
Errors:
0x3000
: Invalid opened file ID provided0x3001
: File is not opened in read mode0x3002
: Provided offset is out-of-range0x3003
: Provided offset + length is out-of-range0x4FFF
: Unspecified filesystem error
0x4002
WRITE_FILE
Write an open file.
File will grow if needed.
Required permissions:
None (implicitly given by the initial OPEN_FILE call)
Arguments:
- Opened file ID (8 bytes)
- Offset to write from (8 bytes)
- Length to write (8 bytes)
- Readable buffer pointer
- Truncate boolean (1 byte): truncate the file's length to the end of the written data
Errors:
0x3000
: Invalid opened file ID provided0x3001
: File is not opened in write mode0x3002
: Provided offset is out-of-range0x4000
: Filesystem capacity exceeded0x4001
: Maximum file size exceeded0x4FFF
: Unspecified filesystem error
0x5000
CREATE_SYMLINK
Create a symbolic link.
Required permissions:
fs.symlinks.create
Arguments:
- FSID (40 bytes)
- Filesystem path of the parent directory
- New item's filename
- Symlink's target FSID (8 bytes)
- Symlink's target path (always a split path for other filesystems)
Return value:
None
Errors:
0x3000
: Invalid filename provided0x3001
: Invalid FSID provided0x3002
: Requested filesystem is currently not mounted0x3003
: Parent directory was not found0x3004
: Cannot create symbolic links to cross-filesystem items0x3005
: Cannot create symbolic links to non-existing items0x4000
: Directory's maximum capacity has been reached0x4001
: Maximum nested items number has been reached0x4002
: Maximum path length has been reached0x4003
: Storage's capacity exceeded0x4FFF
: Unspecified filesystem error
0x5001
UPDATE_SYMLINK
Create a symbolic link.
Required permissions:
fs.symlinks.update
Arguments:
- FSID (40 bytes)
- Symlink's path
- Symlink's target FSID (8 bytes)
- Symlink's target path
Return value:
None
Errors:
0x3000
: Invalid FSID provided0x3001
: Requested filesystem is currently not mounted0x3002
: Provided path was not found0x3003
: Cannot crate symbolic links to cross-filesystem items0x3004
: Cannot crate symbolic links to non-existing items0x4FFF
: Unspecified filesystem error
0x5002
READ_SYMLINK
Read a symbolic link's target.
Required permissions:
fs.symlinks.read
Arguments:
Return value:
- Symlink's target FSID (8 bytes)
- Symlink's target path
Errors:
0x3000
: Invalid FSID provided0x3001
: Requested filesystem is currently not mounted0x3002
: Provided path was not found0x4000
: Symbolic link is cyclic
0xA000
WATCH_ITEM
Watch an item for changes on its metadata or content. Any change will trigger a ITEM_CHANGED
notification.
Required permission:
fs.items.metadata
Arguments:
Errors:
0x3000
: Invalid FSID provided0x3001
: Provided path was not found
0xA001
UNWATCH
Stop watching a content watched with WATCH_ITEM
.
Arguments:
- Generated watch identifier (8 bytes)
Errors:
0x3000
: Provided watch identifier was not found
0xF000
FORMAT
Asynchronously format the partition to get an empty filesystem.
Required permissions:
fs.filesystems.format
Arguments:
Return value:
- Generated task identifier (8 bytes)
Error codes:
0x1000
: Invalid sector size provided0x3000
: Invalid FSID provided0x3001
: Requested filesystem is currently not mounted0x4000
: Unspecified filesystem error
Notifications
0x0006
FS_CHANGED
Sent to a client which subscribed through FS_WATCH
each time a filesystem is mounted or unmounted.
Datafield:
0xA000
ITEM_CHANGED
Notification sent to clients watching an item through the WATCH_ITEM
method.
Datafield:
- FSID (8 bytes)
- FEID (8 bytes)
- Event code (1 byte):
0x01
: item's metadata changed (timestamps excluded)0x02
: item was moved0x03
: item was deleted0x04
: item was locked (only for the parent directory if case of a recursive lock)0x05
: item was unlocked (only for the parent directory if case of a recursive lock)
sys::fsh
service
The sys::fsh
service is in charge of handling high-level filesystem operations, such as file associations and thumbnails generation.
Methods
0x0001
REGISTER_OPENER
Register an application as an opener for a list of file types.
Arguments:
- File extensions to handle as a delimited list of delimited strings
Return value:
None
Errors:
0x1000
: One of the provided extensions is empty0x1001
: At least one of the provided extensions is reserved to the system0x3000
: Client does not expose a file opening service0x3001
: Client already handles at least one of the provided extensions
0x0002
UNREGISTER_OPENER
Unregister an application as an opener for a list of file types.
Argument:
- File extensions to unhandle as a delimited list of delimited strings
Return value:
None
Errors:
0x1001
: One of the provided extensions is empty0x3000
: Client does not expose a file opening service0x3001
: Client does not currently handle at least one of the provided extensions
0x0003
LIST_OPENERS
List the file openers associated to a specific type of items.
The list is not ordered, it's up to the file manager to determine the display order if multiple openers are found.
Arguments:
- Filesystem item's extension as a delimited string
Return value:
- Delimited list of ANID (8 bytes each)
Errors:
0x3000
: Client does not expose a file opening service
0x1000
CHECK_ITEM_THUMBNAIL_CACHE
Check if a cached thumbnail exists for a given filesystem item to determine if another should be generated or not.
The cache policy is determined using internal criterias.
Arguments:
Return value:
- Presence (1 byte):
0x01
if a thumbnail is present,0x00
else
If a thumbnail is present, this is followed by:
Errors:
0x3000
: Client is not a file manager service0x3001
: Unknown FSID0x3002
: Unknown FEID
0x1001
CACHE_ITEM_THUMBNAIL
Write a filesystem item's thumbnail in the cache.
If a thumbnail already exists in cache, it will be replaced by the new one.
The cache policy is determined using internal criterias.
Arguments:
- FSID (8 bytes)
- FEID (8 bytes)
- Thumbnail as a bitmap image
Return value:
- Thumbnail TFEID (8 bytes)
Errors:
0x1000
: Invalid bitmap data0x3000
: Client is not a file manager service0x3001
: Unknown FSID0x3002
: Unknown FEID
0x1100
CHECK_ITEM_PREVIEW_CACHE
Check if a cached preview exists for a given filesystem item to determine if another should be generated or not.
The cache policy is determined using internal criterias.
Arguments:
Return value:
- Presence (1 byte):
0x01
if a preview is present,0x00
else
If a preview is present, this is followed by:
Errors:
0x3000
: Client is not a file manager service0x3001
: Unknown FSID0x3002
: Unknown FEID
0x1101
CACHE_ITEM_PREVIEW
Write a filesystem item's preview in the cache.
If a preview already exists in cache, it will be replaced by the new one.
The cache policy is determined using internal criterias.
Arguments:
- FSID (8 bytes)
- FEID (8 bytes)
- Preview as a bitmap image
Return value:
- Preview TFEID (8 bytes)
Errors:
0x1000
: Invalid bitmap data0x3000
: Client is not a file manager service0x3001
: Unknown FSID0x3002
: Unknown FEID
sys::hw
service
The sys::hw
service is in charge of hardware devices. It coordinates and manages communications with the hardware.
It is known as the I/O manager, or Ion.
- Hardware detection
- Unique device identifier
- Device formats
- Normalization
- Drivers
- Latency reduction for storage devices
- Methods
- Notifications
Hardware detection
Hardware detection is handled by the kernel itself, which exposes a raw device descriptor (RDD).
Unique device identifier
Each device gets a unique device identifier (UDI) encoded on 8 bytes, which identifies a unique hardware device. It is unique across devices and consistent across reboots. Unlike the kernel's device identifier (KDI), the UDI is generated randomly to prevent from getting informations on the hardware just from the UDI.
Device formats
This section describes the multiple formats used by this service to deal with devices.
Device type descriptor
From the RDD is derived the device type descriptor (DTD), which describes the device's type. Its composition and size depends on the connection type, but it varies from empty (0 byte) if the connection type guarantees no information, up to 256 bytes.
The format remains to be determined but should be along the lines of a number-based equivalent of ModAlias, like :
- PCI-Express:
- Vendor (8 bytes)
- Sub-vendor (8 bytes)
- Type (8 bytes)
- Sub-type (8 bytes)
- ...
- ...
Drivable device raw descriptor
A drivable device raw descriptor (DDRD) is a 512-byte long data structure meant to be used by drivers. It uses the following format:
Driven device type
A driven device type (DDT), usually referred to as the device type, is generated by the driver for each device it drives from the DDRD. This is a normalized value, used by the system to determine which actions can be performed through this device.
It's a 4-byte value, the strongest two bytes describing the category and the weakest two the sub-category.
The following list contains all possible values for DDTs, but is far from being complete yet. It will also grow over time as new device types appear on the market.
0x0001
: Storage0x0001
: Hard drive0x0002
: SSD0x0003
: USB flash drive0x0004
: SD flash memory card
- ...
Normalization
When a device is driven, other processes can ask this service to use normalized methods. These are methods that allow to perform a specific action or to receive normalized notifications about specific events of a specific device.
Also, interrupts are normalized to ensure constancy across devices of the same type. They are sent to the driver processes using the DEVICE_EVENT
notification.
The normalization of methods, notifications and interrupts is performed by the driver in charge of the device.
There are several methods, depending on the device's type (DDT). Notifications differ as well.
You can find the far from being complete list in the relevant specifications directory. It will grow over time as new device types appear on the market and as existing devices evolve to provide new features.
Patterns
Several methods of this service use patterns, which allow to match devices depending on several criterias.
A pattern is a data structure whose size varies from 5 to 277 bytes made of the following:
- Pattern (1 byte)
- Bit 0: match all connection types
- Bit 2: match all buses
- Bit 3: match all ports
- Connection type (1 byte)
- Bus number (1 byte)
- Port number (1 byte)
- DTD length (1 byte) -
0
to omit DTD - DTD pattern indicator (32 bytes, only if DTD) - indicates which bytes of the DTD must be used as patterns
- DTD (up to 256 bytes)
It's possible to match only devices that use a given connection type, and more specifically on a given bus and/or port.
It's also possible to list only devices that match a specific DTD pattern. For that, the bit corresponding to the byte number in the DTD pattern indicator must be set.
For instance, providing the DTD 0x0100B2
with the DTD pattern indicator set to 0b01000000
, the second byte will match all devices.
Drivers
From a higher level point of view, drivers are services that declare their parent applications as being able to handle certain type of devices through the REGISTER_DRIVER
method, using patterns.
Registering as a driver for a pattern requires the application to expose the driver service(s) relevant to the DDT provided in the pattern.
When a device is connected, a driver is selected from the list of drivers able to handle this specific device. If the device is connected for the very first time, the selected driver process first receives an IDENTIFY_DEVICE
notification to translate the DDRD into a DDT.
Then, the driver process receives a DEVICE_EVENT
notification, which will also be sent if the status of the device changes.
From this point, the driver process can communicate with the device using its I/O ports with the READ_IO_PORT
and WRITE_IO_PORT
syscalls.
It can also map the device's memory into its own address space using an AMS with the DEVICE_AMS
syscall.
Other processes can then ask the driver to perform specific actions depending on the type of device, using normalized methods which can be sent to the driver using the ASK_DRIVER
method. The driver receives these informations through the DRIVER_METHOD_REQUEST
notification.
All methods and notifications are transmitted through this service, which performs permission checkings and validates some arguments.
The driver is also in charge of translating the interrupts of a device as well as eventual events polled from its (mapped) memory to normalized notifications which can then be sent to processes that subscribed to them using the related normalized methods.
You can see the complete list of methods and notifications for each type of driver services in the related section of the documentation.
Driver selection
A driver is selected for a specific hardware device if it matches any of the following criterias, in decreasing importance order:
- The user selected this driver for this specific hardware device ;
- The user selected this driver for this specific type of hardware devices (pattern) ;
- This driver is the one with the most specific pattern covering this hardware device ;
- This driver is the only one able to drive this specific hardware device (DTD)
If no criteria is matched, the driver isn't selected to drive the given hardware device.
A note on performances
Although hardware devices' interrupts are notified to the driver through service socket notifications, the latency is still minimal as soon as the driver listens to the RECV_SOCK_MSG
signal, which like all signals uses interrupts and so guarantees a very low latency.
Latency reduction for storage devices
Direct driver access for sys::fs
All operations related to storage devices are handled by the sys::fs
service. To avoid the cost of using sys::hw
as a relay for hardware operations, the sys::fs
service is allowed to directly communicate with all storage driver services.
Direct storage access for sys::fs
In the event a specific storage device doesn't require a dedicated driver, the sys::fs
service can directly access the said hardware device.
Methods
0x0001
ENUM_DEVICES
Enumerate connected devices, reserved to system services.
It's also possible to only count the number of devices matching the provided criterias by providing a start index and end index of 0
.
Required permission: devices.enum
Arguments:
- Start index (4 bytes)
- End index (4 bytes)
- Pattern (277 bytes)
Answer:
- Number of found devices globally (4 bytes)
- Number of devices listed in this answer (4 bytes)
- DDRD of each device (512 bytes * number of devices)
0x01
if some devices were masked due to insufficient permissions,0x00
else (1 byte)
Errors:
0x1000
: Start index is lower than the end index0x1001
: Invalid connection type0x1002
: Bus number was provided without a connection type0x1003
: Port number was provided without a connection type0x1004
: Invalid DTD0x1005
: Range is greater than the available answer size0x3000
: Client is not a system service0x3001
: Provided bus was not found0x3002
: Provided port was not found
0x0002
SUBSCRIBE_DEVICES
Subscribe to events related to devices matching a patterns, reserved to system services.
All current and future devices matching this pattern will cause a DEVICE_EVENT
notification.
Required permission: devices.subscribe
Arguments:
0x00
to subscribe, any other value to unsubscribe- Pattern (277 bytes)
Answer:
None
Errors:
0x3000
: Client is not a system service0x3001
: Asked to unsubscribe but no subscription is active for this pattern
0x1000
REGISTER_DRIVER
Set up a service as a driver for all devices matching a pattern.
If multiple drivers have colliding patterns, the final user will be prompted to choose a driver.
When a new device is connected, the driver process will receive an IDENTIFY_DEVICE
notification to translate the DDRD into a DDT.
The driver process will receive DEVICE_EVENT
notifications for drivable devices. This notification will only be sent for devices for which the system chose this driver as the main one.
Notifications are also retroactive, which means they will be sent for already-connected devices.
The driver will also have the device registered in its drivable devices attribute, allowing it to use the DEVICE_AMS
syscall to map the device's memory in its own.
Required permission: devices.register_driver
Arguments:
- Pattern of the devices to drive (up to 277 bytes)
Answer:
None
Errors:
0x3000
: Current process is not a service0x3001
: Process' parent application does not expose the relevant integration services0x3002
: Current process is already registered as a driver for this pattern
0x1001
UNREGISTER_DRIVER
Unregister a service previously registered as a driver.
Required permission: None
Arguments:
- Pattern to unsubscribe from (up to 277 bytes)
Errors:
0x3000
: Current process is not registered as a driver for this pattern
0x2000
NOTIFY_PROCESS
Send a notification to a process that registered itself for normalized methods through a normalized method.
Required permission: None
Arguments:
- Notification ID (8 bytes)
- Normalized notification's content
Answer:
Expected answer by the notified process for this method if any
Errors:
0x3000
: Unknown notification ID
0xA000
ASK_DRIVER
Ask a driver to use a normalized method on a device it drives.
Reserved to system services.
Required permission: devices.ask_driver
Arguments:
- Device's UDI (8 bytes)
- Method's code (4 bytes)
- Method's arguments (size depends on the method)
Answer:
Expected answer format for this method
Errors:
0x3000
: Client is not a system service03x001
: Unknown device UDI provided0x3002
: Provided method code is invalid for this device0x3003
: Invalid arguments provided for this method
0xD000
AUTHORIZE_FS_INTERFACE
Authorize a filesystem interface to access a specific part of a storage device.
The interface service will be allowed to perform requests on the provided storage device, only on the provided data segment.
Arguments:
- Device's UDI (8 bytes)
- Start byte (8 bytes)
- End byte (8 bytes)
Answer:
- Authorization token (8 bytes) to use with
UNAUTHORIZE_FS_INTERFACE
Errors:
0x3000
: Client is not thesys::fs
service0x3001
: Unknown device UDI provided0x3002
: Start byte is not aligned on the device's sectors0x3003
: End byte is not aligned on the device's sectors0x3004
: End byte is greater than or equal to the start byte
0xD001
UNAUTHORIZE_FS_INTERFACE
Unauthorize a filesystem interface authorization created using the AUTHORIZE_FS_INTERFACE
method.
Arguments:
- Authorization token (8 bytes)
Errors:
0x3000
: Client is not thesys::fs
service0x3001
: Unknown authorization token provided
0xD002
AUTH_PERFORM_STORAGE
Used by filesystem interfaces which received an authorization beforehand.
Perform an action just like with the ASK_DRIVER
method, but restricted to the authorization's scope.
Arguments:
- Authorization token (8 bytes)
- Method's code (4 bytes)
- Method's arguments (size depends on the method)
Answer:
Expected answer format for this method
Errors:
0x3000
: The provided authorization token is unknown or not tied to this client
Notifications
0x0002 IDENTIFY_DEVICE
Sent for a specific device the client that was selected as its driver.
Datafield:
- DDRD (512 bytes)
Expected answer:
- DTD (512 bytes)
Errors:
0x3000
: The provided DTD is invalid0x3001
: The client does not expose the driver service relevant to this type of device
0x0003 DEVICE_EVENT
Sent for a specific device to clients that either:
- Drives this specific device
- Subscribed to it using the
SUBSCRIBE_DEVICES
method
Datafield:
- DDT (512 bytes)
- Event code (1 byte):
0x10
: device was just connected0x11
: a driver was just selected for the device0x12
: the device is ready to use0x20
: device was disconnected (software)0x21
: the device is being disconnected by its driver0x22
: the device has been disconnected by the driver0x23
: the device was brutally disconnected (hardware)0x30
: device was just put to sleep0x31
: device was just awoken from sleep
- Indicator (1 byte):
- Bit 0: set if this device is connected for the first time
- Bit 1: set if this device was disconnected brutally (not by the system itself)
- Bit 2: set if this device is connected for the first time on this specific port
0x1000 DEVICE_INTERRUPT
Sent to a driver after a device it's currently driving raised an interrupt.
Datafield:
- Device's UDI (8 bytes)
- Normalized interrupt
0xA000 DRIVER_METHOD_REQUEST
Sent to a driver after receiving a valid normalized method request.
The driver is expected to answer using the relevant answer format for the provided normalized method and arguments.
The notification ID is generated by this service to allow the driver to send normalized notifications to a process that registers for it through this method without showing the caller process' PID to the driver process.
Datafield:
- DDT (4 bytes)
- Notification ID (8 bytes)
- Method's code (4 bytes)
- Method's arguments (size depends on the method)
Expected answer:
Expected answer format for this method if any
0xA001 DEVICE_NORM_NOTIF
Sent to a process that subscribed to normalized notifications of a device.
This notification is transferred by the sys::hw
service after the driver sent it its content through the NOTIFY_PROCESS
method.
Datafield:
- Device's UDI (4 bytes)
- Normalized notification's content
sys::perm
service
The sys::perm
service is used to manage the privileges of users and the permissions of processes.
The purpose of user privileges
In NightOS, executable instructions can run in three different contexts:
- In applications
- In system services
- In the kernel itself
The kernel doesn't have any limitation on what tasks it is allowed to perform, of course, as it is the one to decide.
System services communicate directly with the kernel and are trusted processes so they can do anything in their domain, which means for instance the sys::net
cannot ask to manipulate the filesystem, as it's the role of sys::fs
.
But applications, who run userland processes TODO
List of permissions
The list of permissions can be found in the dedicated specifications document.
Methods
TODO
Notifications
TODO
sys::net
service
Methods
TODO
Notifications
TODO
sys::crypto
service
WARNING: I am not a cryptography expert, so this document certainly contains mistakes or bad practices. In which case, feel free to let me know!
This service provides ways to perform secure cryptography operations from any process. Most methods do not require any permission.
Methods
0x1000
RANDOM_BUFFER
Fill a number with random values using a cryptographically-secure method.
Arguments:
- Buffer pointer (16 bytes)
Return value:
None
Errors:
None
Notifications
TODO
sys::crashsave
service
Methods
0x0100
GET_LAST_CRASH_SAVE
Get the last crash save for this application, if any. The crash saves removal can be controlled by the answer provided to the RESTORE_CRASH_SAVE
notification.
Arguments:
None
Answer:
- Optional data provided in the datafield of the
RESTORE_CRASH_SAVE
notification (none if there is no crash save for this application)
0x0101
REMOVE_CRASH_SAVE
Remove the existing crash save for this application.
Arguments:
None
Answer:
- Status (1 byte):
0x00
: the crash save was successfully deleted0x01
: no crash save was found for this application0x02
: couldn't delete the crash save as the file is currently in use
Notifications
0x0100
CRASH_SAVE_COLLECTION
Sent to a process during crash save collection.
It is only sent to applications indicating they support crash saves in their manifest.
Datafield:
- Number in seconds under which this notification must be answered (1 byte)
- Timestamp of the last crash save for this application (1 byte)
0x01
if this notification was not answered in time the last time it was sent,0x00
otherwise (1 byte)- Temporary FEID to write the crash save in
Expected answer:
- Status (1 byte):
0x00
if the crash save was written succesfully0x01
if the application didn't have enough time to fully write the crash save0x02
if the application couldn't create a crash save for whatever reason0x03
if the application couuldn't create
- Optional log message which will be written to the system's logs
0x0200
RESTORE_CRASH_SAVE
Sent to a process when a crash save is to be restored.
This notification is sent only if the correct bit is set in the application's startup reason.
Datafield:
- Temporary FEID to the crash save (8 bytes)
- Timestamp when the
CRASH_SAVE_COLLECTION
signal that resulted in this crash save was sent (8 bytes) - Timestamp when this crash save was actually written to the disk (8 bytes)
0x00
if this crash save is sent for the first time,0x01
if the application was interrupted before it answered this notification the last time (e.g. application or system crash) (1 byte)0x00
if this crash save is sent for the first time or if the application itself didn't crash while trying to restore it the last time,0x01
if the application itself crashed last time (1 byte)
Expected answer:
- Status (1 byte):
0x01
: crash save was restored successfully0x02
: crash save is corrupted and/or invalid
0x00
if the crash save can be deleted,0x01
if it should be kept (1 byte)
sys::hydre
service
Methods
TODO
Notifications
TODO
sys::app
service
Methods
TODO
Notifications
TODO
sys::process
service
Methods
TODO
Notifications
TODO
Native applications
This folder contains documentation for each native application.
WARNING: These documents act as a general list of features and technical overview of the applications. These may change at anytime, and are by no mean a final indication of what the application will consist of and what features it will have.
Below is the list of applications that are installed (by default) during the installation process.
Userland interactive applications
Basics
- Nova : Desktop environment
- BareEnv : Text-based desktop environment
- Comet : File manager
- Stellar : Application store
- Rocket : Web browser
- Pluton : Terminal
- TimeTravel : Backup and versioning program
System utilities
- Central : Settings
- Monitor : Monitor opened applications, processes, CPU/RAM usage, etc.
- Registry : View and edit the registry (for advanced users)
- Skyer : Applications manager
- Cloudy : Backup & Sync manager
Utilities
- Gravity : Text editor
- Thinker : Notes and task lists manager
- ShootingStar : Pictures viewer and simple editor
- Sonata : Music player (with Hi-Res support and options)
- Milkshake : Video player
- Blackhole : Archives manager
- Reader : A complete e-book reader with many supported formats and options
- Postal : An e-mail client
Suites
- Particle : A complete IDE to make applications
- Astral : The complete toolchain allowing to build applications
Security
- Vortex : Firewall
- Locky : System's encryption tool, with the ability to manage encrypted archives and volumes
System services
System services are listed in their own specifications directory.
Astral
Astral is the toolchain required to build NightOS applications. It's a command-line only application.
Description
Astral is made of several tools:
- A builder, which builds application packages from their manifest
- A publisher, which allows to sign the application and upload it to the Store
- A debugger, which provides tools to run and debug an application (monitor permissions, create puppet sandboxes, ...)
It relies on several external compilers (Rust, TypeScript, ...) to build applications from source. Infrastructure for the main languages is installed by default, but additional toolchains can be installed manually.
BareEnv
BareEnv, for bare environment, is text-based desktop environment meant for servers as well as fallback when no graphical output is available or when the primary desktop environment does not work for any reason.
Graphical features are disabled, and any direct application launch will fail ; they can only be launched through commands, to reduce the risk of crash due to the creation of graphical backends failing.
Tracking devices such as mouses are still supported and can be used in compatible command-line applications.
The application itself allows to manage the environment's settings, as well as to optionally change the behaviour of specific devices and system features to be more adapted for text-based usage.
Note that this environment is not meant for general use ; it's mainly aimed for troubleshooting when graphics are failing.
Blackhole
Blackhole is the default archive manager of NightOS.
It relies on Locky for encrypted archives, filesystems and files.
Features
Blackhole can open, extract and create different formats. It supports compression as well as encryption.
Supported containers
- 7-Zip archives (
.7z
) - ISO images (
.iso
) - TAR archives (
.tar
) - ZIP archives (
.zip
)
Are also supported all the archive formats specific to NightOS:
- Virtual storage files (
.vsf
,.vad
) - NightOS application file (
.nap
,.nva
) - NightOS system updates (
.nsu
) - NightOS application updates (
.nau
)
Supported compression formats
- BZip(2) (
.bz
,.bz2
) - LZMA(2) (
.lz
,.xz
) - Unix (
.z
) - GZip (
.gz
) - ZStandard (
.zstd
)
Central
The Central application delivers the Control Center which allows to configure how the system behaves. It is split into several categories.
> Current user settings
> User Account
> Set profile picture
> Set nickname
> Change security level
| Basic
| Standard
| Restricted
| Extreme
| Total
! Only available in developer mode
> Applications management
> Sideloading
> Change sideloading mode
| Disabled
| Secure
| Unsecure
> Global settings
> Date & Time
> Set date
> Set time
> Change timezone
> Synchronize time
> Encryption
> Manage storage encryption
| Enable
| Disable
> Change encryption password
! Disabled if storage is not encrypted yet
> Manage per-user encryption
| Enable
| Disable
> Users
> Create new user
> Create new administrator user
> Create new supervised user
Development-related options
This part show settings that are only available in developer mode.
> Applications
> Set application proxies
| Enable
| Disable
Cloudy
While the system and main data can be backed up using TimeTravel, it's not always handy to manage backups on an external hard drive and keep it safe.
This is where Cloudy comes: it allows to synchronize data on the cloud, using a master password which is stored nowhere but in the user's mind.
How it works
In order to work, Cloudy needs a storage account where it can store its data. Multiple providers are supported: Dropbox, Google Drive, Amazon AWS, SFTP storages, etc.
It will then store all data on this account, in a dedicated folder (the path can be customized). All data will be encrypted by a master password prompted when the application is opened for the first time, so no one will be able to access these date except the user who set it up itself.
Integration
Applications can ask to store and synchronize their own data through Cloudy. They will not be able to access any other application's data, though.
Collected data
The data that can be backup-ed and synchronized by Cloudy are:
- User's settings, computer's settings if the user is administrator
- Installed applications and their data
- Applications can ask to store additional custom data (e.g. a password manager)
Synchronization
The synchronization process has two parts:
- The up-sync process, which sends new data to the cloud ;
- The down-sync process, which reflects these changes on the local computer
Up-sync can be performed manually at anytime, or scheduled to be performed frequently. It's also possible to ask, for instance, to update the list of applications when a new one is installed or removed, while only backuping applications' data once a day.
Down-sync can be performed anytime, and a preview will show which items will be restored and what will be overridden. It's possible to only restore a few items. By default, down-sync will be performed in real-time: as soon as an up-sync process is performed, down-sync will happen on every other computer of the synchronization chain.
Synchronization chain
Data are synchronized through all computers of a single synchronization chain, which is simply a set of computers. Synchronization happens per user, which is why Cloudy doesn't backup every user of the computer, but only the one synchronization is enabled on. Of course, every user can enable synchronization for its account.
When a user enabled Cloudy for its account, it must connect with a NightOS account, and it is then added to the synchronization chain consisting of every computer having Cloudy enabled with this NightOS account.
For down-sync to happen, the username must be on the receiving computers than on the upload computer, else a specific setting will need to be changed in Cloudy.
Comet
Comet is the default file manager of NightOS.
Compatible filesystems
Comet can open and manage all filesystems natively supported by NightOS, meaning it also supports custom filesystems handled by the sys::fs
service as well as remote drives such as SSH and SFTP storages.
Third-party application can also provide support for additional storage types (e.g. cloud).
Usage
Comet is meant to be used with both a keyboard and a tracking device, but it's also possible to use only the former or the latter.
Features
- Listing
- Creation and deletion of items
- Copy and paste for all types of items
- Handles parallel operations (with queue management, pause/resume, priorization, forced throttling, ...)
- Encryption and deletion for files, folders and drives
- Items caching for remote drives
- Drag & drop from and to other applications
- Customizable interface
- Tabs
- Real-time refresh
Gravity
Gravity is a text editor featuring syntax highlighting for code.
It supports many different languages, and comes with customizable keyboard shortcuts. Mouse is supported.
Features
- Basic and semantic syntax highlighting
- Support for third-party language definitions
- Support for extensions
- Customizable themes
- Fully customizable keyboard shortcuts
- Mouse support
- Encryption and compression support
- Built-in terminal
- Can open folders
- Workspaces (single- or multi-folders)
- Memorize unsaved files, per workspace
Locky
Locky is NightOS' encryption tool. It heavily relies on the sys::crypto
service.
User interface features
- Encryption and decryption of notes
- Encryption and decryption of files, folders
- Encryption and decryption of users
- Encryption and decryption of storages (for supported filesystems)
Library features
- Encryption and decryption of data streams
- Support for third-party algorithms
- Can act as an intermediary file driver
Milkshake
Milkshake is the default video player of NightOS.
Supported containers and codecs
- WebM (
.webm
) - Matroska (
.mkv
) - Video Object (
.vob
) - Ogg Video (
.ovg
) - Multiple-Image Network Graphics (
.mng
) - Audio Video Interleave (
.avi
) - MPEG Transport Stream (
.mts
,.m2ts
,.ts
) - QuickTime File Format (
.mov
,.qt
) - Windows Media Video (
.wmv
) - RealMedia (
.rm
) - MPEG-1/-2 (
.mpg
,.mp2
,.mpeg
,.mpe
,.mpv
,.m2v
) - M4V (
.m4v
) - 3rd Generation Partnership Project (
.3gp
,.3g2
) - AV1 codec
Features
- Hardware-accelerated decoding
- Fully customizable keyboard shortcuts
- Support for third-party formats through external applications
- Memorize position in video
- Selection for audio track and subtitles
- DVD and BD (Blu-ray) support with menu
- Media library management
Monitor
Monitor allows to see and manage running applications and processes, as well as to watch resources usage like CPU percentage or used RAM.
Features
Monitor can display as an instant, real-time label or as a graph multiple informations:
- Memory usage
- CPU usage (global and per core) for each CPU
- GPU usage (by type) for each GPU
- Storage usage (HDD, SSD, USB, ...) for each storage device
- Network usage for each available connection
Usage can be expressed in percents, and when applicable in bandwidth (MB/s par default).
Additional informations can also be displayed:
- Frequency of each CPU core
- Frequency of each GPU
- Number of concurrent reading and writing to each storage device
- Temperature for each device supporting it
- Memory usage (by type)
Hardware informations can also be displayed:
- General : number of processes, number of threads, uptime, number of opened file handles
- CPU : model, frequency (total and per core), base frequency, boost frequency, physical cores, logical cores, number of used sockets, caches
- GPU : model
- Memory : capacity, in use, panigated, used slots, available slots, frequency
- Storage : model, capacity, used, locks, cached data, S.M.A.R.T. if applicable
- Network : type of connection, hardware speed, IPv4 and IPv6 address, used DNS
It also displays informations on processes, services, and sum for applications with at least one running process, separated in 3 tabs:
- Name
- Icon
- Application (+ icon)
- ANID
- CPU usage
- GPU usage
- Memory usage (percent and absolute amount)
- Storage usage (percent and absolute speed)
- Network usage (percent and absolute speed)
- Approximate battery consumption
- Process identifier
- Command-line arguments (when started as a command)
- Execution context (if applicable)
- Process priority
- Number of threads
- Number of file handles
- List of file handles
- List of permissions
- Is the process frozen? (process only)
- Number of clients (service and application only)
- Is a service active? (application only)
- Number of processes (application only)
- Number of threads (application only)
Nova
Nova is the default desktop environment of NightOS.
It has a highly-customizable user interface.
WARNING: This document is only a draft and is extremely far from being complete! Anything written in this document is subject to change at anytime.
User interface
The desktop
The desktop itself is visible in the background - it is fully visible when on window on foreground.
It shows all items from /home/[username]/desktop
.
Right-clicking on an item shows the default file manager's context menu.
A background image called wallpaper is also displayed under the desktop. The wallpaper can be replaced by a custom one.
Notification center
The notification center is a panel that slides in from one of the screen's side. The side can be customized.
It has a vertical layout and contains the list of each notification.
By default, it also shows a few quick settings as well as the date and hour of the day.
Dock
An optional dock can be shown, made of a list of icons on one side of the screen. It also shows the list of opened windows, even if they are not pinned to the dock. An indicator also shows the number of opened windows (a dot for each window).
Moving the mouse on top of one of the icon will by default show a preview of each opened window, side by side. If a high number of windows are opened (by default more than 4) the previews will be stacked on a grid. By default, previews are shown by recently used order, with a maximum of 16 windows (grid of 4 rows, 4 columns).
Clicking on an icon will, if the application already has a window, show the grid, else open the application.
For custom shortcuts, this will only trigger the selected shortcut.
User experience
Removing native applications
When removing a native applications, a popup will suggest to hide the app instead of removing it, which allows other applications and scripts to still interact with them.
Particle
Particle is a complete IDE for making NightOS applications. It uses the Astral toolchain.
Features
Its features are the same as Gravity, plus many additional ones (too many to be listed here).
It also features a strong integration with the Astral toolchain in order to build, debug and deploy NightOS applications.
Pluton
Pluton is the default terminal of NightOS. It features a strong integration of Hydre.
Postal
Postal is the default e-mail client of NightOS.
Features
- Handles multiple e-mail accounts
- Supports SMTP, POP, IMAP
- Custom arrangment rules (labels, colors, ...)
- Filters and sorts (by e-mail sender, title, ...)
- E-mail sending planification
- Offline support to consult e-mails without an internet connection
- Support for PGP encryption (end-to-end)
Reader
Reader is an e-book reader with supports for comics. It can also manage libraries of books.
Supported e-books formats
Here is the list of the supported e-book formats, which act as containers. See also the supported image formats.
- E-Pub (
.epub
,.epub3
) - PDF (
.pdf
) - MOBI (
.mobi
) - Rich Text Format (
.rtf
) - Compressed ZIPs (
.cbz
) - Compressed RARs (
.cbr
)
Supported image formats
Same as ShootingStar (uses the sysl::shootingstar
SAL to render pictures).
Registry
Registry allows to view and edit the registry. It is reserved to advanced users.
Features
- Display / edit everything in the registry
- Save current registry as a snapshot
- Restore snapshots
User Interface
The U.I. is made of a set of panes (one by default) containing a tree showing all structures and lists stored in the registry and allowing to edit them.
Rocket
Rocket is the default web browser of NightOS.
ShootingStar
ShootingStar is the default pictures viewer and editor of NightOS. Edition is limited to simple manipulations such as cropping and resizing.
Library usage
ShootingStar exposes the sys::shootingstar
library to manipulate, perform basic manipulations and most of all render different image formats.
It can also be used by file managers to generate thumbnails and video previews.
Supported formats
- Joint Photographic Experts Group (
.jpg
,.jpeg
) - Joint Photographic Experts Group 2000 (
.jp2
,.jpx
) - Tagged Image File Format (
.tiff
,.tif
) - Graphics Interchange Format (
.gif
) - Graphics Interchange Format Video (
.gifv
) - AV1 Image File Format (
.avif
) - Windows Bitmap (
.bmp
) - NightOS Bitmap Image (
.nbi
) - NightOS Bitmap Video (
.nbv
) - Portable Networks Graphics (
.png
) - Portable Pixmap Format (
.ppm
) - Portable Graymap Format (
.pgm
) - Portable Bitmap Format (
.pbm
) - Windows Icon (
.ico
)
Viewer features
- View pictures
- Zoom in/out
- Fullscreen
- Fit image to width / height / both
- Put images side by side
- View a set of pictures as a gallery
- View a set of pictures as a slideshow (multiple possible transitions)
- Display EXIF metadata
Edition features
- Resize (keep or ignore ratio)
- Crop
- Edit/remove EXIF metadata
- Convert to black and white
Skyer
Skyer is the applications manager of NightOS. It allows to install and remove applications, as well as to manage their permissions and enable application proxies in developer mode.
Sonata
Sonata is the default music player of NightOS. It features support for Hi-Res audio files and can play many different formats (e.g. 32 bits / 384 kHz FLAC, DSD, ...). It also supports bit-to-bit playing to USB DACs.
Features
- Audio library management
- Sorting and filtering by tag
- Tag edition for individual tracks, sets, albums and whole artists
- Bit-perfect playblack
- Optional fetching of informations about tracks, albums and artists (biographies, pictures, ...)
- Multiple playback queues
- Fully customizable keyboard shortcuts
- Support for themes
- Support for extensions
- Customizable tabs
- Discovery feature to play never-played tracks
- Smart playlists (auto-add and removal based on custom criterias)
- Playlists imports/exports
- Play counts for tracks
- Full database import/export
- Reduced player
- Play in background
Supported formats
- Waveform Audio File Format (
.wave
,.wav
) - Free Lossless Audio Codec (
.flac
) - Apple Lossless Audio Codec (
.alac
) - Ogg (
.ogg
,.oga
) - Audio Interchange File Format (
.aiff
) - Core Audio Format (
.caf
) - MPEG-1/2 Audio Layer 3 / mp3PRO (
.mp3
) - Windows Media Audio (
.wma
) - Advanced Audio Coding (
.aac
)
Stellar
Stellar is the default application store of NightOS.
Thinker
Thinker is a simple notes application.
TimeTravel
TimeTravel is the backup and versioning program of NightOS.
Vortex
Vortex is NightOS' built-in firewall. It can also be used in combination with parental control.