Read only root file system

From RidgeRun Developer Wiki

Background

Mounting the root file system as read-only is a simple way to improve device security, decrease support issues caused by file system corruption, and to simplify resetting to factory defaults. One strategy to maintain compatibility with configuration files at well known locations (such as /etc, examples being /etc/network/interfaces and /etc/resolv.conf) is to have the root file system contain symbolic links in place of the configuration files with the symbolic links pointing to a file in a partition that is mounted read-write. In this way none of the Open Source applications need to change since the configuration files are accessible at their normal location, but the real contents are stored in a read/write paritition.

Mounting root file system

The root file system is mounted as the kernel is initializing based on parameters in the kernel command line. According to Documentation/kernel-parameters.txt

Parameter Parameter
Type
Description
rw kernel start-up Mount root device read-write on boot
ro kernel start-up Mount root device read-only on boot

In the RidgeRun SDK, a file system configuration parameter was added

  • File System Configuration ---> Create read only target file system

which controls the CONFIG_FS_READONLY_TARGET configuration parameter. In the fs/fstargets/Makefile, the cmdline target, which is used to constuct the kernel command line, adds either rw or ro' to the kernel command line based on the CONFIG_FS_READONLY_TARGET configuration parameter setting.

Remounting root file system

A common performance improvement for embedded Linux device is to not track file and directory access times. This can be accomplished by remounting the root file system using the following command:

mount -o remount,noatime,nodiratime,ro /dev/root /

Handling configuration files

There are multiple strategies for handling configuration files that are possible.

Option 1: The most straight forward otion is to have all applications that use configuration files make sure the path to the configuration file points to a file in the file system that is mounted read-write. When using Open Source packages, this may require changing the package source which ends up with the files being in non-standard locations.

Option 2: The strategy used in this description is to replace the configuration file with a symbolic link to the configuration file located on the read-write partition. In addition, another file with the same name, with .default is added to the directory holding the symbolic link. The use of the default file is described later.

The symbolic links and default files are added to the target file system image late in the SDK build process to overwrite any configuration files that may be added by the Open Source package.

The symbolic links and default files should be added to the fs/overlay-ro directory. The fs/Makefile contains logic to only copy fs/overlay-ro contents to the target file system if CONFIG_FS_READONLY_TARGET is set.

User interface to request reset to factory defaults

Typically a device that supports the user forcing a reset to factory defaults does so using a physical button or switch that is connected to a GIO pin. A three step process can be used to (A) detect the botton press, (B) pass the button press indication to the kernel, then (C) detect the indication as the applications are started so that the factory reset can occur before any applications read the configuration files.

Detecting button press in uboot

In the board_init() routine in the board file (e.g. u-boot-*/src/board/davinci/myboard/myboard.c) add something like:

	gpio_dir(64, GPIO_DIRECTION_IN);
	udelay(1000);
	gpio_get(64, &button1_asserted); /* GPIO64 high if button pressed */

which sets a static variable button1_asserted if the button is pressed. Then add a board_late_init() function which sets a uboot envionment variable if the button is pressed:

int board_late_init(void)
{
	if (button1_asserted) {
		printf("Button 1 pressed\n");
		setenv("button1_asserted", "true");
	}

	return 0;
}

To get board_late_init() to be called, you need to define BOARD_LATE_INIT in your configs file (e.g. u-boot-*/src/include/configs/davinci_myboard.h). Rebuild uboot and install to test if your button press detection is working. You can run the uboot command printenv to see if button1_asserted is defined when you press the button during power on.

Passing button press status via kernel command line

A simple trick to pass information from uboot to an application is via the kernel command line (KCL). uboot sets the KCL before transferring control to the kernel, and an application can access the KCL via the /proc/cmdline file. A uboot script is used to add the button press status to the kernel command line.

button_handler=if test -n ${button1_asserted} ; then setenv bootargs ${bootargs} button1_asserted=${button1_asserted} ; fi
bootcmd=run button_handler ; run loaduimage ; bootm 0x82000000

When uboot starts, it runs the bootcmd script. the bootcmd runs the button_handler script, which adds button1_asserted=true to the KCL if the board_late_init() defined the button1_asserted uboot environment variable. Then the kernel is loaded and the KCL is passed to the booting kernel.

Detecting button press status in a shell script

A shell script, like the one described below, can change its behavior based on a power on button press. The script would be included in the /etc/init.d or what every scheme you use to run the startup scripts. The following shows the logic that can be added to the script to change its behavior based on a power on button press:

if fgrep -q "button1_asserted=true" /proc/cmdline ; then echo "button pressed at power on" ; fi

Reset to factory defaults

If configuration files are handled using a symbolic link to the read-write file system, then a reset to factory default is just copying each *.default from the read-only file to overwrite the configuration file in the read-write file system. The script assumes the file /usr/local/share/config-file-list contains the names of the configuration files. An example config-file-list could be:

/etc/network/interfaces

A simplifed /usr/local/bin/reset-to-factory-defaults is show below:

RW_MOUNT=/tmp/rw

for FILE in `cat /usr/local/share/config-file-list` ; do
        if [ -r $FILE.default ] ; then
                mkdir -p $RW_MOUNT/`dirname $FILE`
                cp $FILE.default $RW_MOUNT/$FILE
        fi
done

You can add a simple /etc/init.d/check-for-factory-default-reset using somthing similar to:

#!/bin/sh -e

case "$1" in
        start)
                if fgrep -q "button1_asserted=true" /proc/cmdline ; then 
                        echo "Resetting to factory defaults"
		        /etc/init.d/check-for-factory-default-reset 
                fi
		;;
esac

Summary of RidgeRun SDK features to support read-only target file system

These features are part of a RidgeRun professional SDK add-on.

  • File System Configuration ---> Create read only target file system which sets CONFIG_FS_READONLY_TARGET
  • Adding rw or ro based on CONFIG_FS_READONLY_TARGET configuration parameter
  • Copying the contents of $DEVDIR/fs/overlay-ro to the target file system ($DEVDIR/fs/fs) if CONFIG_FS_READONLY_TARGET is set.

Summary of changes to support reset to factory defaults

These features are part of a RidgeRun professional SDK add-on.

  • uboot detect button press (enhanment to board file)
  • uboot adding environment variable if button press detected during power on (board_late_init())
  • uboot environment variable to add button press to kernel command line (button_handler)
  • init.d startup script to act on button press during power on