Persisting Data

If a URCap container application needs to persist data between runs, then one can specify a writable location using the ‘mounts’ field in the manifest.yaml. Given that the application writes data to ‘/foo’, then one should specify the mount as “persistent:/foo”. That is you prepend the desired directory with “persistent:”. For example:

# manifest.yaml
metadata:
  vendorID: universal-robots-test
  urcapID: mount-test
  ...
artifacts:
  containers:
  - id: mounting-test
    image: image.tar
    mounts:
      - mount: "persistent:/foo"
        access: "rw"

Writing files

When writing a file with new content it is recommended to write the data to a temporary file first, and afterward rename the file to the target one. This way the likelihood of ending up with corrupt data in the target file is reduced. The steps are:

  1. create a new temp file

  2. write data to the temp file

  3. rename the temp file with the desired name

Ensuring written data reaches the disk

For performance reasons file writes are not immediately written to physical disk. Instead, they are buffered. This means that sudden power outage might lead to loss of data. If your application requires that a file write reaches disk immediately, then you can reduce the risk of data loss by adding ‘sync’ system calls following your writes. The steps are:

  1. create a new temp file (on the same file system as the target file)

  2. write data to the temp file

  3. fsync() the temp file

  4. rename the temp file with the desired name

  5. fsync() the containing directory

Read more in depth explanation on linux disk synchronization here: https://lwn.net/Articles/457667/

Synchronized writing examples

Here are a few examples of writing data to a file and subsequently asking the OS to persist the data to disk. Error handling has been left out for brevity.

Python

#!/usr/bin/env python3
import os
import uuid
import pathlib

def writeDataToFile(fileName : pathlib.Path, data):
    randomFilePath = fileName.parent / str(uuid.uuid4()) # place tmp file adjacent to target file, so they are on same FS

    with open(randomFilePath, 'w') as f:
        f.write(data)
        os.fsync(f.fileno())

        os.rename(f.name,  fileName)

        dirFd = os.open(fileName.parent, flags=os.O_RDONLY) # get file descriptor of the directory in which 'fileName' resides.
        os.fsync(dirFd)
        os.close(dirFd)

        
path = pathlib.Path("foo/bar")
writeDataToFile(path, "baz")

C++

#include <filesystem>
#include <random>
#include <unistd.h>
#include <fcntl.h>

void writeDataToFile(const std::filesystem::path &outputFile, const void* data, int sz)q
{
    auto randomFilePath = std::filesystem::path{outputFile.parent_path() / std::to_string(rand())};

    int fd = open(randomFilePath.c_str(), O_CREAT | O_WRONLY, 00644);

    write(fd, data, sz);

    fsync(fd);
    close(fd);

    std::filesystem::rename(randomFilePath, outputFile);

    int dirFd = open(outputFile.parent_path() == "" ? "." : outputFile.parent_path().c_str(), O_RDONLY);

    fsync(dirFd);
    close(dirFd);
}

template <typename T>
void writeDataToFile(const std::filesystem::path &outputFile, const std::vector<T> &data){
    writeDataToFile(outputFile, data.data(), data.size() * sizeof(T));
}

int main()
{
    auto data = std::vector<uint16_t>{0xDEAD, 0xBEEF, 0xCAFE, 0xFEED};

    writeDataToFile(std::filesystem::path{"foo/data.txt"}, data);

    return 0;
}

Java

import java.io.File;
import java.io.IOException;
import java.io.FileOutputStream;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.Files;
import java.nio.file.StandardCopyOption;
import java.util.UUID;

class Persistence {
    public static void main(String[] args) {
        writeDataToFile(Paths.get("foo/bar"), "baz");
    }

    private static void writeDataToFile(Path path, String data) {
        Path randomFilePath = Paths.get(path.getParent().toString(), UUID.randomUUID().toString());
        File tmpFile = new File(randomFilePath.toString());

        try (FileOutputStream os = new FileOutputStream(tmpFile)){
            os.write(data.getBytes(), 0, data.length());
            os.getFD().sync();
            Files.move(tmpFile.toPath(), path, StandardCopyOption.REPLACE_EXISTING, StandardCopyOption.ATOMIC_MOVE);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}