The Möbius Operating System: Driver Book
HOME DOWNLOAD DOCUMENTATION SCREENSHOTS  

File Systems

Back: Device Drivers | Next: Example: PS/2 Mouse

This document specifies the interface that the kernel uses to access file system drivers. If you write a file system driver, you need to implement the functions in this specification (or, at least the ones that make sense for your file system).

The interface is designed such that file system drivers can be written in either C or C++. The interface is centred around the fsd_t structure: each file system device (i.e. each mount point controlled by your file system driver) is represented by a memory object whose contents start with an fsd_t structure. In short, you need to substitute your own structure or class -- containing any per-instance file system data -- for fsd_t, while keeping an fsd_t structure at the beginning. C++ handles this for you automatically by allowing you to inherit a class from fsd_t; if you are using C, you need to define your own structure type whose first field is of type fsd_t.

File System/FSD Interaction

As their name suggests, file system drivers are contained within driver files, as device drivers are; the entry point to the driver is still DrvEntry (or however you name it). A file system driver is a driver where the fs_mount field of the driver_t object is valid; this points to the FSD's mount function. The file system will nearly always call the FSD; there are only a couple of calls the other way (relating to asynchronous I/O completion).

The first call the kernel makes to your driver is to the FSD mount function, which looks like this:

fsd_t *FsdMount(driver_t *drv, 
                const wchar_t *dest);

You can name the function any way you want; the FsdMount name is just a placeholder. Your FsdMount function needs to:

  • allocate an object for the file system
  • interpret the dest string appropriately (usually as a device name, which you would open using IoOpenDevice)
  • mount and check the file system
  • return a pointer to the fsd_t structure embedded in the object on success, or NULL on failure

Every file and directory maintained by the FSD is represented by a cookie. A cookie is a object which is allocated and freed by the FSD; the kernel doesn't read or modify it, but passes it unchanged to the FSD where relevant. Cookies can be anything that can be represented by a void* pointer; usually it is most convenient for the FSD to allocate a structure and use a pointer to the structure as the cookie.

The FSD needs to allocate a file cookie when a file is opened or created; it does this in create_file and lookup_file. create_file is called in response to a call to FsCreate; lookup_file is called not only from FsOpen but also functions such as FsQueryFile which need to look up a file name before manipulating it. The cookie from create_file or lookup_file gets passed in the fsd_cookie member of the file_t structure passed to read_write_file, ioctl_file and passthrough; the kernel calls free_cookie when the cookie is no longer needed.

Directory cookies are similar to file cookies, except that they are allocated in opendir, passed to readdir, and freed in free_dir_cookie.

FSD Definition

struct vtbl_fsd_t
{
    void (*dismount)(fsd_t *this);

    void (*get_fs_info)(fsd_t *this, 
                        fs_info_t *info);

    status_t (*parse_element)(fsd_t *this, 
                              const wchar_t *name, 
                              wchar_t **new_path, 
                              vnode_t *node);

    status_t (*create_file)(fsd_t *this, 
                            vnode_id_t dir, 
                            const wchar_t *name, 
                            void **cookie);

    status_t (*lookup_file)(fsd_t *this, 
                            vnode_id_t node,
                            uint32_t open_flags,
                            void **cookie);

    status_t (*get_file_info)(fsd_t *this, 
                              void *cookie, 
                              uint32_t type, 
                              void *buf);

    status_t (*set_file_info)(fsd_t *this, 
                              void *cookie, 
                              uint32_t type, 
                              const void *buf);

    void (*free_cookie)(fsd_t *this, 
                        void *cookie);

    bool (*read_write_file)(fsd_t *this, 
                            const fs_request_t *req,
                            size_t *bytes);

    bool (*ioctl_file)(fsd_t *this, 
                       file_t *file, 
                       uint32_t code, 
                       void *buf, 
                       size_t length, 
                       fs_asyncio_t *io);

    bool (*passthrough)(fsd_t *this, 
                        file_t *file, 
                        uint32_t code, 
                        void *buf, 
                        size_t length, 
                        fs_asyncio_t *io);

    status_t (*mkdir)(fsd_t *this, 
                      vnode_id_t dir, 
                      const wchar_t *name, 
                      void **dir_cookie);

    status_t (*opendir)(fsd_t *this, 
                        vnode_id_t node, 
                        void **dir_cookie);

    status_t (*readdir)(fsd_t *this, 
                        void *dir_cookie,
                        dirent_t *buf);

    void (*free_dir_cookie)(fsd_t *this, 
                            void *dir_cookie);

    void (*finishio)(fsd_t *this, 
                     request_t *req);

    void (*flush_cache)(fsd_t *this, 
                        file_t *fd);
};

dismount

void dismount(fsd_t *fsd);

Called when the file system is dismounted and the last file has been closed. Your driver should free any resources it allocated for the file system here.

get_fs_info

void get_fs_info(fsd_t *fsd, fs_info_t *info);

Called to obtain information on the file system as a whole. fs_info_t looks like this:

struct fs_info_t
{
	uint32_t flags;
	uint64_t cache_block_size;
	uint64_t space_total;
	uint64_t space_free;
};
  • flags: on entry, specifies which fields the kernel wants filled. This will be a combination of:
    • FS_INFO_CACHE_BLOCK_SIZE: fill the cache_block_size field
    • FS_INFO_SPACE_TOTAL: fill the space_total field
    • FS_INFO_SPACE_FREE: fill the space_free field
  • cache_block_size: the size of a block in the cache, or zero to disable caching for this file system. If you specify a non-zero value here, the kernel will only call read_write_file with offsets and lengths which are multiples of cache_block_size; the value must be an integer power of two. If you specify zero, the kernel assumes that your file system does not require caching, or that it performs its own caching.
  • space_total: the size of the file system's data area, in bytes.
  • space_free: the amount of space on the file system available for data, in bytes

parse_element

status_t parse_element(fsd_t *fsd, const wchar_t *name, wchar_t **new_path, vnode_t *node);

Called to translate a file or directory name into a vnode ID.

On entry

  • name: the name of the file or directory requested
  • *new_path: contains NULL
  • node->fsd: identical to the fsd parameter
  • node->id: vnode ID of the directory in which to look

On exit

  • *new_path: if name specifies a symbolic link, contains the path pointed to by the link. This path string must be allocated from malloc or wcsdup. Path parsing is re-started using this new path. If name does not specify a symbolic link, contains NULL.
  • node->fsd: contains a pointer to the FSD of the file system which contains name
  • node->id: contains the vnode ID of the file or directory name
  • Return: ENOTFOUND if name was not found, zero for success, or some other error code on failure.

create_file

status_t create_file(fsd_t *fsd, vnode_id_t dir, const wchar_t *name, void **cookie);

Called to create a new file, called name, in the directory dir.

Your code should:

  • Create a new file
  • Enter it into the directory at dir
  • Allocate a cookie for the file
  • Return a pointer to the cookie through *cookie

lookup_file

status_t lookup_file(fsd_t *fsd, vnode_id_t node, uint32_t open_flags, void **cookie);

Called to open an existing file. specifies the flags originally passed to FsOpen.

Your code should:

  • Allocate a cookie for the file
  • Return a pointer to the cookie through *cookie

get_file_info

status_t get_file_info(fsd_t *fsd, void *cookie, uint32_t type, void *buf);

Called to retrieve information on a file.

type can be one of the following, or some privately-defined value:

  • FILE_QUERY_NONE: do nothing. This is used to test for the existence of a file without returning any other information.
  • FILE_QUERY_DIRENT: return a dirent_t for the file:
    struct dirent_t
    {
    	unsigned vnode;
    	wchar_t name[256];
    };
  • FILE_QUERY_STANDARD: return a dirent_standard_t for the file:
    struct dirent_standard_t
    {
    	uint64_t length;
    	uint64_t attributes;
    	wchar_t mimetype[64];
    };
    attributes is a combination of zero or more of the following flags:
    • FILE_ATTR_READ_ONLY: the file cannot be written to
    • FILE_ATTR_HIDDEN: the file is hidden from normal directory listings
    • FILE_ATTR_SYSTEM: the file is part of the operating system
    • FILE_ATTR_VOLUME_ID: the file's name specifies the file system's ID
    • FILE_ATTR_DIRECTORY: the file is a directory
    • FILE_ATTR_ARCHIVE: the file should be archived next time a backup is performed
    • FILE_ATTR_DEVICE: the file is a device
    • FILE_ATTR_LINK: the file is a symbolic link
  • FILE_QUERY_DEVICE: returns a dirent_device_t for the file:
    struct dirent_device_t
    {
    	wchar_t description[256];
    	uint32_t device_class;
    };

set_file_info

status_t set_file_info(fsd_t *fsd, void *cookie, uint32_t type, const void *buf);

Called to update a file's information. See get_file_info for details.

free_cookie

void free_cookie(fsd_t *fsd, void *cookie);

Called to free a file's cookie, when the file is closed.

read_write_file

status_t read_write_file(fsd_t *fsd, const fs_request_t *req, size_t *bytes);

Called by the implementation of FsReadAsync and FsWriteAsync to initiate an asynchronous read or write operation.

Parameters:

  • fsd: file system driver object
  • req: contains the parameters for the request:
    struct fs_request_t
    {
        file_t *file;
        page_array_t *pages;
        uint64_t pos;
        size_t length;
        fs_asyncio_t *io;
        bool is_reading;
    };
    • file: specifies the file object to read from or write to. file->cookie contains the cookie from create_file or lookup_file.
    • pages: represents the buffer passed to FsReadAsync or FsWriteAsync.
    • pos: specifies the position within the file where the read or write is to begin. If your file system uses the kernel's cache (see get_fs_info), this will be a multiple of the cache block size. If not, then this can have any value.
    • length: specifies the number of bytes to read or write. For a cached file system, this will be a multiple of the cache block size; if not, this can have any value.
    • io: pointer to the fs_asyncio_t object which the kernel uses to keep track of file system requests. You should pass this to FsNotifyCompletion once the request completes.
    • is_reading: true if this is a read request, false if this is a write request.
  • bytes: for synchronous requests (i.e. ones which complete before read_write_file returns and for which your code returns zero), and when your code returns an error, update this with the number of bytes actually written or read. For asynchronous requests, pass the number of bytes written or read to FsNotifyCompletion when the request completes.

Synchronous or asynchronous operation

  • Validate the parameters. Return an error code on failure.

Asynchronous operation

  • Save io somewhere, as well as whatever parts of req you need
  • Start the request (this can be assumed to finish at any time, even instantaneously)
  • Return SIOPENDING
  • Do not try to dereference io once the request has been queued; the request may have completed, and, upon completion, your code will have called FsNotifyCompletion, which frees io.

Synchronous operation

  • Carry out the request
  • Call FsNotifyCompletion, passing it io, the number of bytes read, and an error code (or zero for success)
  • Return zero

ioctl_file

bool ioctl_file(fsd_t *fsd, file_t *file, uint32_t code, void *buf, size_t length, fs_asyncio_t *io);

...todo...

passthrough

bool passthrough(fsd_t *fsd, file_t *file, uint32_t code, void *buf, size_t length, fs_asyncio_t *io);

...todo...

mkdir

status_t mkdir(fsd_t *fsd, vnode_id_t dir, const wchar_t *name, void **dir_cookie);

...todo...

opendir

status_t opendir(fsd_t *fsd, vnode_id_t node, void **dir_cookie);

Called to open a directory for use with the readdir function.

Your code should:

  • Allocate a cookie for the directory
  • Return a pointer to the cookie through *dir_cookie

readdir

status_t readdir(fsd_t *fsd, void *dir_cookie, dirent_t *buf);

Called to read the next entry from the directory at dir_cookie.

Your code should return the directory entry through buf and return zero if available, or return EEOF if not.

free_dir_cookie

void free_dir_cookie(fsd_t *fsd, void *dir_cookie);

Called to free a directory cookie allocated by opendir.

finishio

void finishio(fsd_t *fsd, request_t *req);

Called when any I/O queued on a device completes.

flush_cache

void flush_cache(fsd_t *fsd, file_t *fd);

...todo...

Back: Device Drivers | Next: Example: PS/2 Mouse

Post a comment

From: