Click here to Skip to main content
15,991,072 members
Articles / Programming Languages / C
Article

An Embedded File System in Pure C

Rate me:
Please Sign up or sign in to vote.
4.89/5 (4 votes)
3 Jul 2024CPOL21 min read 4.5K   86   4  
We implement an embedded filing system to serve files to user code, in portable C.
An embedded filesystem which serves files, written in fully portable ANSI C, and suitable for any platform with a C standard library. And we define an XML file format, FileSystem XML, to represent the data, and provide a suite of supporting programs to allow generating and editing of the XML. This includes a small but functional Unix-style shell which uses the FileSystem XML as its mounted directory.

Introduction

Many programs need large amounts of data at runtime which is not provided by the user. Typical examples are images for UI elements and text for help pages. However the program could require almost anything. For instance a crossword design program needs a list of every word in the English language.

There are many ways of approaching this. One is to embed data as large arrays in the programming language. Another is to use the resource compiler packaged with the IDE used to develop the program. And another is to ship the program with a supporting directory containing the necessary files.

And the third approach is the easiest to develop, and it does not rely on third party supporting programs, and it is easy to edit and update files at all stages of development. But it suffers from the problem that it's often simply not acceptable to ship an executable with a required suporting directory. For a consumer product, and particularly a game, the data is too accessible to the customer. And even for a product aimed at technical users, the supporting directory is a nuisance to have around and can complicate installs.

And so the solution is to wrap up the supporting directory, and embed it in the executable. Then provide methods so that the program can access the files as though they were mounted. And so in other words, we mount them on an embedded, virtual file system. The system is a bit more expensive than serialising the data and embedding it as arrays, but it is easier to manage, as the same routines that read external data can read internal data, and because the supporting directory can be wrapped up at the last minute, making editing a lot easier. And it is also possible to allow writes. The internal file system will be reset to its default when the executable is re-run. However it is very simple to implement a "save settings" when all the modified data is together in the same filing system. You then reintroduce the supporting directory. But it can contain a single flat file, and whilst it is created on the user's machine, it does not have to ship with the executable, a small but often crucially important distinction.

And so the core of the system is a program to serialise a directory and place it in the executable, using the resource compiler or simply an emdedded string, then a filing system object to deserialise the directory and provide access as though to files with hierarchy, and finally a routine to serialise the entire system again, so the state can be saved and restored. And the filing system object should be written in fully portable code. There's no reason for it to fail to work on any platform, from a fairly small embedded system to a fully-featured modern PC. 

The system was written for Baby X, which is a cross-platform GUI toolkit for small programs. Whilst it is possible to use IDE resource compilers with Baby X, the programs are then not usable with other IDEs, and so there was a need to be able to easily import directories into Baby X programs. However there is nothing specific to Baby X in the system, and it can be used in any standalone program.

Background

As a bare minimum a virtual filesystem consists of two components, a program for serialising a directory so it can be embedded, and a suite of routine for restoring hierachy and accessing the embedded data as files.

There are two widely used programs which serialise directories. One is the Unix program "tar", and the other is the compression utility pkzip. They are both widely available, though "tar" does not come as standard on Windows, and pkzip is slightly disapproved of on Unix, with preference given to a program which produces .gz files. However neither .tar files nor .zip files are really suitable as a file format for serialised directories to embed.  They are both binary, and complicated to parse. They also have checksums, which maintains the integrity of the data, but makes it more difficult to write. Choosing either would entail a lot of code in the file system routines themselves.

The BBX_FileSystem embedded in an app.

Schematic of the FileSystem

The object obtains data from either an internal string or an external directory, and serves files to application code.

So I opted for an XML format. The data is uncompressed. And because XML has relatively light markup for this type of data, the text files are human-readable and human editable, with no special tools except a text editor. The text files are also robust to minor corruption. Binary files are inherently non-human readable. You could use any encoding. I opted for uuencoding as a widely-used standard. A single corrupt bit will destroy many binary files. However only the file itself is vulnerable, the XML directory is robust. The XML is also simple to parse, so complexity can be kept out of the file system at runtime. And it is almost self-documenting. On being handed an example file, a reasonably experienced programer ought to be able to write a parser, without a formal file format specification.

Using our own file format means that we have to write the program to serialise a directory. However because the format is simple, it is not a vey difficult program to write. Whilst we intend to embed the files in an exectable, and strictly we do not need a program to deserialise to a directory, it's a very natural thing to also provide that program. And now we have a general purpose directory archive system that an be used wherever tar is for some reason unsuitable. And in fact we also provide a suite of programs to list the files in the archive, extract them, and to insert new files. 

Then the most powerful editor of all is a little shell. So you run a Unix shell over the XML, and you have all the tools like cd and ls to navigate and list files, and cp, rm and mv to move them about. And also facilities to edit in place. And it also has facilities for adding external commands, which is currently used to provide grep. And we have a Basic interpreter.  Because, ultimately, a computer can be thought of as an editor for a filing system. 

And the filing system itself is written in C. Though C++ inheritance is made for this job, and a very neat filing system could be implemented by deriving from the iostream classes. However the C++ would be hard to integrate into non-C++ code. With C, it should be relatively easy to provide bindings for most languages which can call foreign functions written with C calling conventions.

And because of the way the C standard library works, we have to implement an fopen() and an fclose(), and then the user can use the rest of the file functions in stdio.h in the normal manner, to read and write bytes from the file and manipulate the file pointer. And we achieve this by creating temporary streams with tmpfile(). And it is not a terribly efficient way of going about it, but it is fine for small programs on modern hosted plarforms with abundant memory, and it is fine for resource-constrained embedded systems as long as the files are small.

Now during development, the programmer probaby wants to load files from an external support directory. Then, as he is developing, he has all the faciliites of the host operating system to easily edit the files in that directory, and have the results instantly available to his program. Then towards the end of development, he settles on a small and stable set of supporting files he actually needs. And at that point, the directory can be serialised and embedded, Changes are still possible, but they are more of a performance. And so it is useful if the file system has two modes. In development mode, it is just a thin layer of abstraction over stdio, and passes through requests to read from the supporting directory on the host machine. And in embedded mode, it serves files from the embedded system. And the programmer can toggle between the two modes.

So this is the system that has been developed. It consists of several components: the runtime file server, supporting programs to generate the XML and edit it, documention for the XML file format, if necessary, a program to convert the XML into an embedded C string, and a small but fully fuctional shell to have full editing control over the XML.

The FileSystem XML format

FileSystem is an XML file format designed for storing directories. It is intended for Baby X and works with the BabyXFS (Baby X FileSystem) suite of programs and functions. However it is independent of Baby X and is open to anyone for any use.

What is FileSystem XML

FileSystem XML is a format for storing hierarchical data. The root tag is <FileSystem> and then there are two descendant tags <directory> and <file> Files are leaf nodes which contain data, whilst directories can nest and contain files and other directories.

Example FileSystem XML file

XML
<FileSystem>>
    <directory name="owl_and_text">
        <directory name="owl">
            <file name="Cartoon_Owl_clip_art.png" type="binary">
<![CDATA[M)"E3'U@":H````0#)A$12!````$```@/(8````PF!UQ;````!,G4')$`NZ,'
MI#``;<02$%$5H50S;='=<6=E_W4U4ETHND5R]*N!&<PQ<=<H'@0HX0"F$(GE
M4V-+)9A-;.DE-9A`!(]$(+_1.LA`$@L`!,$P8<!;<O)9)9999)9)9-2SH13O
M7V?W/IQ:D0V28T>.[UGG^^FO7]>?W^[95X_[`-<HSFE\,I#Y:4GFRS2KV*TH
M25I)2E:!LLH4`%PZ5I"P)_>CJ5IZ8)3EZ,1B'O[0Q2T3P88(6O+6\R2"6FV!

More uuencoding removed for brevity

M+"V\!9?&L7&70/8OM,$601^1[G\T8[--DD-W#)]9Z"^TZ'P$M.L/@GXKFYLI
M,7!2BWZH1C967$-;OEPX-/2\H+LLX:V^Q#C-U0L7+44\[Y`$E!2U??NCM>3J
MCD7'DNLEFC%WJN(J_;'.,^P3$/V"*-AFW@OOC3$__B21_%V'AX-M`3WF!E%E
M:*7=)U7,^6T_<U].$>?>%8^\T3QSTY@8OU7(N-RE?BV'$/-;KX!G`Z,K%715
M%B[CGH^`\8`/0-%@O(#I_P#X%M7;!XVX-?;W=Y"/+;;W*]8:Z/_O!MW)SX-<
.9_(`````)5D3$YJ0@)(I
]]>
            </file>
        </directory>
        <file name="readme.txt" type="text">
In the beginning was the Word, and the Word was with God, and the Word was God.
The same was in the beginning with God.
All things were made by Him, and without Him was made nothing that was made:
in Him was life, and the life was the Light of men;
and the Light shineth in darkness, and the darkness did not comprehend it.

        </file>
    </directory>
</FileSystem>
image of a folder   And it is fairly simple and self explanatory. Directories have names, but are otherwise just containers. Whilst files have names, and can be binary or text. Text is stored as plain ASCII, whilst binary data is uuencoded and put in a CDATA section. So it's a very clean format.

owl_and_text.xml

The <FileSystem> tag

XML
    <FileSystem>
        <directory name="archive">
            <file name="readme.txt" type="text">
and round the neck of the bottles was a paper label, with the
words ‘DRINK ME’ beautifully printed on it in large letters
            </file>
        <directory>
    </FileSystem>

The FileSystem tag identifies the format. It should be the root element, and has one child, which is always a directory. The name of the root directory identifies the name of the FileSystem. Only directory and file tag elements are allowed within FileSystem elements.

The <directory> tag

XML
<directory name="archive">
    <file name="readme.txt" type = >
and round the neck of the bottles was a paper label, with the
words ‘DRINK ME’ beautifully printed on it in large letters
    </file>
    <directory name="subfolder">
    </directory>
<directory>

The directory tag one compulsory element, which is the name. It can contain files and directories in any order. Directories are all normal and should not have names which suggest special directories like symbolic links on common computer systems, eg "..". A directory can be empty.

The <file> tag

XML
      <file name="readme.txt" type="text">
and round the neck of the bottles was a paper label, with the
words ‘DRINK ME’ beautifully printed on it in large letters
      </file>
      <file name="rubbish.bin" type="binary">
      <![CDATA[M)"E3'U@":H````0#)A$1%
      ]]>
      </file>

The file tag is the leaf element which contains data. It has two compulsory attributes, a name and a type, which must be "text" or "binary". Text data is plain, whilst binary data is uuencoded. uuencoding is a common system and decoders are widely available, and there is of course code in the accompanying source.

The main consideration is to be extremely simple to parse, and robust. Text files are human readable. So if anything goes wrong with a file in a FileSystem archive, you don't need any special sofware to diagnose the problem and fix it. Just a text editor which can handle large files. There is no way of making binary file human-readable, but uuencoding is the one simplest widely used binary to text protocols. Almost anyone with any programming experience at all can write a decoder, uuncoding is a fairly simple system for encoding binary as ASCII. If the file has been corrupted and you have lost the data, a bedroom programmer may well have the skills to fix it.

Whitespace handling

Whitespace is difficult with XML. And most text files are robust to a bit of whitespace being added or trimmed. However ideally users want text whitespace perefectly preserved. And therefore the program babyxfs_dirtoxml, which generates the files, uses the following system. A newline is added immediately after the opening <file> tag. Then a newline is added imediately after the data. Then a series of tabs are added to indent the closing tag. So by replacing the opening and closing newlines with nulls, you obtain the original text.

The following code is used to trim text data.

C++
if (!strcmp(type, "text"))
{
    leading = 0;
    len = (int) strlen(data);
    for (i = 0; data[i]; i++)
        if (!isspace((unsigned char) data[i]) || data[i] == '\n')
            break;
    if (data[i] == '\n')
        leading = i + 1;

    trailing = 0;
    i = len - 1;
    for (i = len - 1; i > 0; i--)
        if (!isspace((unsigned char) data[i]) || data[i] == '\n')
            break;
    if (i > 0 && data[i] == '\n')
        trailing = len - i;

    if (trailing + leading >= len )
        ;
    else
    {
        if (fwrite(data + leading, 1, len - trailing - leading, fp) != len - trailing - leading)
            goto error_exit;
    }
}

You should use this algorithm to trim text data. If the whitespace has not been manipulated, the leading space is always 1 whilst the trailing space is always the nesting level of the element plus one, and is the newline which terminates the text data.

Note that XML only allows newlines, carriage returns, and tabs as control characters under 32 (space). Some text files have form feeds, backspaces, or other characters. So a "binary" tag does not necessarily mean that the data is binary on the host computer.

Motivation

There was a need to allow users to packaged directories in the Baby X resource compiler. And because the spirit of the resource compiler is extreme portability, binary formats were not acceptable. And XML was the natural choice. There was also a desire to show off the XML parser, which has stood up to the files magnificently.

Baby X is designed for running small or baby programs on large computers, so there is not much need to save memory by going for a very compressed format. Instead I chose something which is robust and simple to use, and aimed at the hobby programmer, although of course professionals who derive their living from programming are perfectly welcome to use it, and as all of Baby X, it is free to anyone for any use.

Supporting utilities

The utilities are designed to make it easy and pleasant to work with FileSystem XML. The only essential utility is BabyXFS_DirToXML which serialises a directory to XML. And you can always edit the directory using the host operating system. However often it is more convenient to be able to examine and edit the FileSystem XML files directly, and these utilities support that.

The utilities also use the embedded BBX_FileSystem object. So they are a source of example code on how to use the system.

BabyXFS_DirToXML

This program converts a directory to FileSystem XML, writing the result to standard output.

It's best to write the results to standard output becuase it is pipeline-friendly, and because it makes it easy to invoke the program correctly without creating any files, and then redirect, so you don't create any erroneous files whilst figuring out how to invoke the program.

The problem is writing this as portably as possible. The current version uses Posix, which is widely supported.

BabyXFS_XMLToDir

This program takes a FileSystem XML file and creates a directory from it.

It's the companion to BabyXFS_DirToXML. Itsslightly more complicated because it is harder to parse XML than to write it, and it uses the mini XML parser, which is lightweight and has very good error reporting capabilities.

Again, this cannot be written portably as it must create directories on the host machine, and so it uses Posix, which is widely supported. 

BabyxFS_Extract

This program extracts a file from the FileSystem XML file, given a path.

It finds the file and writes it to standard output. And so a FileSystem XML file becomes a convenient briefcase. Any time you need a file, text or binary, you simply extract it.

BabyXFS_Insert

This program inserts a file into a FileSystem XML file at the given path.

Because if the structure of XML, inserting a file is quite easy and doesn't require any changes to the rest of the document, which is useful for version control. It is one if the basic edits you might want to make, without going through the entire process of deserialising, adding the file to the directory just created, and serialising again.

BabyXFS_Rm

This program deletes a file from a FilesSystem XML file.

Removing a file is the other essential edit facility. Again, it is convenient to be able to do this without deserialising the entire directory. You can also remove a file by hand with a text editor - XML is simple enough that you can do this wthout any real risk of corrupting the rest of the file.

Together with BabyXFS_Extract and BabyXFS_Insert, you can extract a file, edit it on the host computer reinsert it, and remove the previous version, and so you've got a powerful suite of programs for editing.     

BabyXFS_Ls

This program lists the files in a given directory in a FileSystem XML file.

This is essential if handed a large, unknown FIleSystem XML file. You need to be able to list the files it contains before querying the contents. This version takes wildcards or globs.

BabyXFS_Shell

This program runs an interactive Unix-style shell, using a FileSystem XML file as the mounted file system.

Runnig a shell over a FIleSystem XML file and using it as backing store is inherently the most powerful way there is of editing FileSystem XML. Because that utlimately is what a shell is. It is an editor for a file system.

And as the author of MiniBasic, the temptation to add Basic scripting to the shell to convert it into a little computer in its own right is irresistible. And we can't do justice to the shell here.

But suffice to say that you log into the shell, and you've got cd, ls, cp, rm, mv and cat, which are the basic Unix cammands you need for navigation and moving files about. Then we have the specialities, import and export, which write files to the host operating system and upload them. And there's system, so you can invoke host operating system commands on those files. Then there is a grep, and a fairly well-developed help system

Thne other essential comand is edit, so you can edit the files in place, and this is a bit of a problem for a portable shell, as there is no way to capture arrow keys in portable C. And even if there were, writing an editor is quite a big job. So the edit command invokes the program "nano" by default, and thre is an option to set your own editor.

The BBX_FileSystem object

We create a BBX_FileSystem * as  virtual object. Normally it will be a singleton and either global, or local to main and passed down, but this isn't required and you can create as many file systems as you want.

And the raw bare bones are that we need to implemement bbx_filesystem_fopen() and bbx_filesysem_fclose(). bbx_filesystem_fopen() returns a FILE * which caller can then use with any stdio.h function, except fclose(), and exotica like freopen(). We need bbx_filesystem_fclose() because, if the stream is used for writing, we flush the data at that point to the store. Also we prevent the user from opening more than FOPEN_MAX files, as with stdio. And we warn if the BBX_FileSystem object is destroyed with any open files. Whilst it is not implemented, with a close it is also possible to provide locking, and thread safety.

Now the main issue with the BBX_FileSystem is that it is greedy. The raw data is embedded as FiileSystem XML, as an ASCII string. We then run an XML parser over the string, and load the entire document into RAM. Then as each indivdiual file is served, it is copied into a temporary file held in memory. This is unlikely to be a probem for programs running on PCs. They have many gigabytes of RAM installed, and it is a very abundant resource. But C is often used for small systems. And so future work might alter the internals of the BBX_FileSystem to make it less extravagant.

The other feature is toggling between stdio and the internal string. This should in practice greatly ease development.

Becuase all the operations are virtual, the BBX_FileSystem is extremely fast. To make it faster still, I provide slurp functions to read entire files into user-space memory, to avoid the overhead of creating a temporary stream with tmpfile().

So here would be an example program.

C++
#include <stdio.h>
#include <stdlib.h>
#include "bbx_filesystem.h"
#include "xmlparser2.h"

int main(void)
{
    FILE *fp = 0;
    XMLDOC *doc = 0;
    BBX_FileSystem *bbx_fs= 0;
    int err;

    bbx_fs = bbx_filesystem();

    err = bbx_filesystem_set(bbx_fs, xmlstring, BBX_FS_STRING);
    /* comment in for stdio 
     err = bbx_filesystem_set(bbx_fs, "C://Users/FredBloggs/Rogue", BBX_FS_STDIO);
    */ 
    if (err)
    {
        fprintf(stderr, "Can't set up XML filessystem\n");
        exit(EXIT_FAILURE);
    }  
    fp = bbx_filesystem_fopen(bbx_fs, "/Rogue/Levels/level1.xml", "r");
    if (!fp)
    {
        fprintf(stderr, "Can't open target file on bbx_filesystem\n");
        exit(EXIT_FAILURE);
    }

    /* We've got a FILE *, use in normal way, e.g. load a level in XML and play */
    doc = loadxmldocument(fp);
    play_rogue(doc);

    err = bbx_filesystem_fclose(bbx_fs, fp);
    if (err)
        fprintf(stderr, "error closing xml file\n");  
    
    bbx_filesystem_kill(bbx_fs_xml); 

    return 0; 
}

So nothing very difficult. It's easy to use and to set up.  The main rule to remember is to always write functions which do IO via stdio streams so that they can take either a path or an open stream. However if this is not done, modification is not difficult. But you need to replace all the calls to fopen() and fclose().

The other question is whether to support writing or not. If you don't support writing, it would make it much easier to write a very efficient server for a small embedded system, because you can read memory off the embedded string directly. However the system is potentially much more powerful with writing, and it now does something which embedded arrays of data can't achieve.

Using the code

The FileSystem code exposes one object, the BBX_FileSystem, and consists of three source files:

  • xmlparser2.c
  • bbx_write_source_archive.c
  • bbx_filesystem.c

They also have associated headers. You need to take these files and drop them into your application to have the filesystem.

 

The functions

These are the functions in the library.

C++
//
//  bbx_filesystem.h
//  babyxfs
//
//  Created by Malcolm McLean on 31/05/2024.
//

#ifndef bbx_filesystem_h
#define bbx_filesystem_h

#define BBX_FS_STDIO 1
#define BBX_FS_STRING 2

typedef struct bbx_filesystem BBX_FileSystem;

BBX_FileSystem *bbx_filesystem(void);
void bbx_filesystem_kill(BBX_FileSystem *bbx_fs);
int bbx_filesystem_set(BBX_FileSystem *bbx_fs, const char *pathorxml, int mode);
FILE *bbx_filesystem_fopen(BBX_FileSystem *bbx_fs, const char *path, const char *mode);
int bbx_filesystem_fclose(BBX_FileSystem *bbx_fs, FILE *fp);
char *bbx_filesystem_slurp(BBX_FileSystem *bbx_fs, const char *path, const char *mode);
unsigned char *bbx_filesystem_slurpb(BBX_FileSystem *bbx_fs, const char *path, const char *mode, int *N);
int bbx_filesystem_unlink(BBX_FileSystem *bbx_fs, const char *path);
const char *bbx_filesystem_getname(BBX_FileSystem *bbx_fs);
int bbx_filesystem_setreadir(BBX_FileSystem *bbx_fs, char **(*fptr)(const char *path, void *ptr), void *ptr);
int bbx_filesystem_dump(BBX_FileSystem *bbx_fs, FILE *fp);
char **bbx_filesystem_mkdir(BBX_FileSystem *bbx_fs, const char *path);
char **bbx_filesystem_rmdir(BBX_FileSystem *bbx_fs, const char *path);
char **bbx_filesystem_list(BBX_FileSystem *bbx_fs, const char *path);

#endif /* bbx_filesystem_h */

bbx_filesystem

Constructs an empty BBX_FileSystem object.

C++
BBX_FileSystem *bbx_filesystem(void);
Returns: the constructed BBX_FileSystem object.

bbx_filesystem_kill

Destroys a BBX_FileSystem object.

int bbx_filesystem_set(BBX_FileSystem *bbx_fs,
const char *pathorxml, int mode);
Params:
       bbx_fs - the BBX_FileSystem object.
       pathorxml - the host directory to mount, or FileSystemXML.
       mode -
              BBX_FS_STDIO  - pathorxml is a directory on the host.
              BBX_FS_STRING - pathorxml is FileSystem XML (as a string
                 in memory, not a path).

Returns: 0 on success, -1 on failure.

Pass it a string with FileSystem XML to mount the system. Or a host directory, though BBX_FS_STDIO mode isn't entirely tested. You can free the XML string after the function has returned, it doesn't use it after setting.

bbx_filesystem_fopen

Opens a file from a BBX_FileSystem object.

C++
FILE *bbx_filesystem_fopen(BBX_FileSystem *bbx_fs,
const char *path, const char *mode);
Params:
       bbx_fs - the BBX_FileSystem object.
       path - the path to the file to open
       mode - the mode, "r" to read and "w" to write.
Returns: pointer to the opened stream, 0 on failure.

This is the fopen() function for the BBX_FileSystem. The FILE *s must be closed with bbx_filesystem_fclose(), they must not be passed to stdio fclose(). But othewise, a stdio functions like fputc() and fprintf() can be called on them.

bbx_filesystem_fclose

Closes a file opened from a BBX_FileSystem object.

C++
int bbx_filesystem_fclose(BBX_FileSystem *bbx_fs, FILE *fp);
Params:
       bbx_fs - the BBX_FileSystem object.
       fp - a FILE pointer returned from bbx_filesystem_fopen.
Returns: on success, -1 on failure (which can happen).

This is the fclose() function for the BBX_FileSystem. The FILE *s must have been returned from bbx_filesystem_fopen. Every file pointer opened with bbx_filesysytem_fopen must be closed with this function, and it must never be passed a FILE * created with stio.h functions. Note that if the file was opened in "w" mode, the function might well fail. This is also true of stdio fclose(), but few programmers bother to check. With this function, it can fail.

bbx_filesystem_slurp

Load an entire text file from a BBX_FileSystem object.

C++
char *bbx_filesystem_slurp(BBX_FileSystem *bbx_fs,
const char *path, const char *mode);
Params:
       bbx_fs - the BBX_FileSystem object.
       path - the path to the file.
       mode - read mode, should be "r" but some host
              operating systems will insist on "rt".
Returns: allocated pointer to file contents, 0 on failure.

Load in an entire file. If the file in binary, you will get strange results. In BBX_FS_STRING mode, mode should be "r" or at least a short string beginning wth "r". The parameter is provided to coax stdio implementations which insist on "rt" or maybe an alternative into doing the right thing.

bbx_filesystem_slurpb

Load an entire binary file from a BBX_FileSystem object.

C++
unsigned char *bbx_filesystem_slurpb(BBX_FileSystem *bbx_fs,
const char *path, const char *mode, int *N);
Params:
       bbx_fs - the BBX_FileSystem object.
       path - the pathn to the file.
       mode - read mode, should be "r" but some host
              operating systemes will insist on "rb".
              N - return for the number of bytes read.
Returns: allocated pointer to file contents.

Load in an entire file. If the file is text, you will be given the terminating nul, but it is poor form to rely on it. The pointer needs to be freed when done.

bbx_filesystem_unlink

Delete a file from a BBX_FileSystem object.

C++
int bbx_filesystem_unlink(BBX_FileSystem *bbx_fs, const char *path);
Params:
       bbx_fs - the BBX_FileSystem object.
       path - the path to the file to delete.
Returns: 0 on success, -1 on fail.

It unlinks a file form the directory tree. In BBX_FS_STRING mode, this is the same as deletion. On stdio system, unlinking may not delete, and there may be other links to the same file.

bbx_filesystem_getname

Get the name of the FileSystem mounted in the BBX_FileSystem object.

C++
const char *bbx_filesystem_getname(BBX_FileSystem *bbx_fs);
Params:
       bbx_fs - the BBX_FileSystem object.
Returns: the name of the filessyrm mounted within it.

A FileSystem XML should have a single directory node as a child, which is the root of the data. The name is the name of that node.

bbx_filesystem_setreadir

Set a function to read a directory on host-mounted BBX_FileSystem systems.

C++
int bbx_filesystem_setreadir(BBX_FileSystem *bbx_fs,
char **(*fptr)(const char *path, void *ptr), void *ptr);
Params:
       bbx_fs - the BBX_FileSystem object.
       fptr - a function which will read a directory
                on a hosted file system.
Returns: 0 on success, -1 on failure.

ANSI C provides no function to read directories, and so the bbx_filesystem_list function can only be implemented by proving this function for BBX_FS_STDIO systems. It should return a list of all the files in the current working dieectory.

bbx_filesystem_dump

Write out the entire contents of a BBX_Filesystem object to a FileSysytem XML file.

C++
int bbx_filesystem_dump(BBX_FileSystem *bbx_fs, FILE *fp);
Params:
       bbx_fs - the BBX_FileSystem object.
       fp - stream to write XML to.
Returns: 0 on success, -1 on failure.

This is an easy way of saving the state of the system. It will only work on BBX_FS_STRING mode file systems, however.

bbx_filesystem_mkdir

Create a directory on a BBX_FileSystem object.

char **bbx_filesystem_rmdir(BBX_FileSystem *bbx_fs, const char *path);
Params:
       bbx_fs - the BBX_FileSystem object.
       pth - path to a empty directory.
Returns: a programming error, the pointer is useless.

This can only be done for BBX_FS_STRING systems. BBX_FS_STDIO systems have no way. The directory must be empty. Otherwise it is too easy to lose all your data by accidentallly deleting a senior node.

bbx_filesystem_list

List the contents of a BBX_FileSystem directory.

C++
char **bbx_filesystem_list(BBX_FileSystem *bbx_fs, const char *path);
Params:
       bbx_fs - the BBX_FileSystem object.
       path - path to a directory.
Returns: allocated pointer to allocated list of strings.

This returns the contents of a directory as a simple list of allocated strings in the form

mydirectory/
myfolder/
mymate.txt
myenemy.bin
anotherfolder/

Directories are indicated with a trailing /. The order should not be taken to have any meaning.

 

Points of Interest

There was a real need to be able to easiy import directories into Baby X programs, and this was the answer. It was a very enjoyable project to write, and it leveraged the XML Parser. The highlight was implementing the shell. Only the "serious" bits of the shell are stable. In reality people are just going to use the basic Unix commands. They won't try elaborate editing within the shell itself. However of course we must provide scripting.

And the main challenge is making eveything portable, without a single call to a function not in the standard library. And that is impossible when creating an XML file from a directory or creating a diectory from an XML file. So I had to compromise and use Posix. There's also a standard C++ "filesystem" header which provides facilities for using directories, but it is in a very early stage of development. The main issue seems to be political, and the refusal of some of the big software companies to admit that UTF-8 is the standard for Unicode that works. Someone might want to write babyxfs_dirtixml and babyxfs_xmltodir so that they use Windows native directory API (FindFirstFile, FindNextFile).

But we have a shell written in completely portable ANSI C. And it is a little computer in its own right. And that's quite a remarkable thing to have.

History

Code is maintained at

https://github.com/MalcolmMcLean/babyxrc

This is the first version. It's an ongoing project, and there will be updates.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
United Kingdom United Kingdom
I started programming when we were taught Basic on the Commodore Pet at school, and was immediately hooked. But my parents were not generous with money, and it was a while before I saved up enough money to buy a second-hand ZX81. Then a friend gave me "Machine Code on your ZX81" by Toni Baker (not "Tony", a lady), and that book changed my life, because it enabled me to master something that most adults couldn't do. And I realised the power of good textbooks and self study. I have written two books on programming in consequence.

Then I want to Oxford to study English Literature, and programming came to an end, except for a brief course on Snobol, and statistical analysis of grammar words (words like "and" and "he"). But the expected job with the Civil Service did not materialise, I needed to earn a living somehow, and so it was to games programming that I turned. But I was never entirely happy as a game programmer. Whilst I enjoy programming games, I'm not so fond of playing them, except for Dungeons and Dragons style games. And for a games programmer, that's a big handicap.

I've got other interests aside from programming, and after I had collected a big pile of cash from games programming, I decided to spend it on doing a second degree, at Leeds University, in biology. That then led naturally to a PhD in computational biochemistry, working on the protein folding problem, and that turned me into a really good programmer.

However there's only one faculty position for every 10 PhDs, and I was one of the unlucky nine, and so I found a job doing graphics programming. Which I kept until unfortunately ill health forced me to give up work. And I am now a full time hobby programmer.

And my main project is Baby X and its attendant subsystems.


Comments and Discussions

 
-- There are no messages in this forum --