Skip to content

Commit

Permalink
feat: supporting variety of rootfs types
Browse files Browse the repository at this point in the history
- lower dir can be provided by user wihout requiring squashfs
- system can start without providint folder, single binnary can be attached and started
  • Loading branch information
ahmetozer committed Jun 27, 2024
1 parent 9d285a7 commit f1f8e2a
Show file tree
Hide file tree
Showing 6 changed files with 178 additions and 28 deletions.
67 changes: 66 additions & 1 deletion Readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,8 @@ Executing a new container image with given options.
| `-pod-ips` | | container interface ips |
| `-resolv` | cp-n | Behavior of `/etc/resolv` file. <br/>cp (copy), cp-n (copy if not exist), image (use image), 1.1.1.1;2606:4700:4700::1111 |
| `-ro` | false | read only rootfs |
| `-sq` | ./rootfs.sqfs | squashfs image location |
| `-sq` | | squashfs image location |
| `-lw` | | mount custom paths for lowerdirs (mutliple lower dir supported) |
| `-tmp` | 0 | allocate changes at memory instead of disk. unit is in MB, disk is used used by default |
| `-v` | | attach system directory paths to container <br/> `-v=/mnt/homeasistant:/config` |

Expand Down Expand Up @@ -188,3 +189,67 @@ sandal inspect minecraft
"TmpSize": 0,
...}
```
## Scenarios
At below, you can see some examples of different scenarios and combinations
If your compiled application require system lib files, you can use distro squasfile and your second layer to start container.
```bash
mkdir -p myroot/bin/
cp myBinnary myroot/bin/
sandal run -name=mybinnary -keep -sq=/mnt/sandal/images/ubuntu.sq -lw="$HOME/myroot/" /bin/myBinnary
```
In this example, alpine is used and you can get alpine with below command
```bash
wget https://dl-cdn.alpinelinux.org/alpine/v3.20/releases/aarch64/alpine-minirootfs-3.20.1-aarch64.tar.gz
mkdir alpine
tar -xvzf alpine-minirootfs-3.20.1-aarch64.tar.gz -C alpine
rm alpine-minirootfs-3.20.1-aarch64.tar.gz
```
If you want to isolat your changes, you can start your system with lowerdir only.
```bash
sandal run -name=mybinnary -keep -lw="$HOME/alpine/" /bin/ash
```
With ramdisk
```bash
sandal run -name=alpine -keep -lw="$HOME/alpine/" -tmp=100 /bin/ash
```
With your binnary as layer
```bash
ls /myApp/bin/myapp
sandal run -name=mybinnarywithdistro -keep -lw="$HOME/alpine/" -lw="/myApp/" /bin/myapp
# or locate your configs
ls /myconfigs/
/etc/
dnsmasq.conf
hostpad.conf
sandal run -name=mybinnarywithdistro -keep -lw="$HOME/alpine/" -lw="/myApp/" -lw="/myconfig/ /bin/myapp
```
If you don't want to isolate your continer file system with overlay, you can use -v to mount your system
```bash
sandal run -name=alpine -v=/root/alpine:/ /bin/ash
```
You can directly use pysical disc with high performance with this approach as well
```bash
sandal run -name=mySsd -v=/mnt/myssd:/ /bin/bash
```
You can directly attach single binnary to container enviroment as well
```bash
sandal run -name=tunnel -v=/root/testlw/bosphorus:/bosphorus /bosphorus server
```
4 changes: 3 additions & 1 deletion pkg/cmd/run.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ func run(args []string) error {
f.BoolVar(&help, "help", false, "show this help message")
f.BoolVar(&c.Background, "d", false, "run container in background")
f.StringVar(&c.Name, "name", config.GenerateContainerId(), "name of the container")
f.StringVar(&c.SquashfsFile, "sq", "./rootfs.sqfs", "squashfs image location")
f.StringVar(&c.SquashfsFile, "sq", "", "squashfs image location")
// f.StringVar(&c.RootfsDir, "rootfs", "", "rootfs directory")
f.BoolVar(&c.ReadOnly, "ro", false, "read only rootfs")

Expand Down Expand Up @@ -63,6 +63,8 @@ func run(args []string) error {

f.Var(&c.Volumes, "v", "volume mount point")

f.Var(&c.LowerDirs, "lw", "you can merge multiple lowerdirs")

if err := f.Parse(thisFlags); err != nil {
return fmt.Errorf("error parsing flags: %v", err)
}
Expand Down
15 changes: 15 additions & 0 deletions pkg/config/info.go
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,20 @@ func (f *Volumes) Set(value string) error {
return nil
}

type LowerDirs []string

func (f *LowerDirs) String() string {
b, _ := json.Marshal(*f)
return string(b)
}

func (f *LowerDirs) Set(value string) error {
for _, str := range strings.Split(value, ",") {
*f = append(*f, str)
}
return nil
}

type Config struct {
Name string

Expand All @@ -76,6 +90,7 @@ type Config struct {
Volumes Volumes
HostArgs []string
PodArgs []string
LowerDirs LowerDirs

Ifaces []NetIface
}
Expand Down
36 changes: 35 additions & 1 deletion pkg/container/mount.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"log"
"os"
"path"
"path/filepath"
"strings"

"github.com/ahmetozer/sandal/pkg/config"
Expand Down Expand Up @@ -77,7 +78,30 @@ func childSysMounts(c *config.Config) {
}

func mount(source, target, fstype string, flags uintptr, data string) {
os.MkdirAll(target, 0600)

// empty mount used for removing old root access from container
if source != "" && source[0:1] == "/" {
fileInfo, err := os.Stat(source)
if os.IsNotExist(err) {
log.Fatalf("The path %s does not exist.\n", source)
}
if err != nil {
log.Fatalf("Error checking the path %s: %v\n", source, err)
}

if !fileInfo.IsDir() {
os.MkdirAll(filepath.Dir(target), 0600)
err = Touch(target)
if err != nil {
log.Fatalf("target %s touch error: %s", target, err.Error())
}
} else {
os.MkdirAll(target, 0600)
}
} else {
os.MkdirAll(target, 0600)
}

if err := unix.Mount(source, target, fstype, flags, data); err != nil {
log.Fatalf("unable to mount %s %s %s %s", source, target, fstype, err)
}
Expand All @@ -87,6 +111,7 @@ func mountVolumes(c *config.Config) {
for _, v := range c.Volumes {

m := strings.Split(v, ":")
// if single path provided or desitionation is set different
switch len(m) {
case 1:
m = append(m, m[0], "")
Expand All @@ -97,3 +122,12 @@ func mountVolumes(c *config.Config) {
mount(m[0], path.Join("rootfs", m[1]), "", unix.MS_BIND|unix.MS_REC, m[2])
}
}

func Touch(path string) error {
file, err := os.Create(path)
if os.ErrExist != err {
return err
}
file.Close()
return nil
}
10 changes: 8 additions & 2 deletions pkg/container/resolv-host.go
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,10 @@ func createResolv(c *config.Config, d *[]byte) {
}
}

os.MkdirAll(path.Join(sandalChildWorkdir, "/etc"), 0755)
err := os.MkdirAll(path.Join(sandalChildWorkdir, "/etc"), 0755)
if err != nil {
log.Fatalf("unable to create /etc : %s", err)
}
if err := os.WriteFile(path.Join(sandalChildWorkdir, "/etc/resolv.conf"), *d, 0644); err != nil {
log.Fatalf("unable to write /etc/resolv.conf: %s", err)
}
Expand All @@ -69,7 +72,10 @@ func createHosts(c *config.Config, d *[]byte) {
}
}

os.MkdirAll(path.Join(sandalChildWorkdir, "/etc"), 0755)
err := os.MkdirAll(path.Join(sandalChildWorkdir, "/etc"), 0755)
if err != nil {
log.Fatalf("unable to create /etc : %s", err)
}
if err := os.WriteFile(path.Join(sandalChildWorkdir, "/etc/hosts"), *d, 0644); err != nil {
log.Fatalf("unable to write /etc/hosts: %s", err)
}
Expand Down
74 changes: 51 additions & 23 deletions pkg/container/rootfs.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,16 +3,22 @@ package container
import (
"fmt"
"os"
"strings"

"github.com/ahmetozer/sandal/pkg/config"
"golang.org/x/sys/unix"
)

func MountRootfs(c *config.Config) error {
// Mount overlay filesystem
squasfsMount, err := mountSquashfsFile(c)
if err != nil {
return fmt.Errorf("mounting squashfs file: %s", err)

if c.SquashfsFile != "" {
squasfsMount, err := mountSquashfsFile(c)
if err != nil {
return fmt.Errorf("mounting squashfs file: %s", err)
}
// this will last item of c.LowerDirs and lowest priority
c.LowerDirs = append(c.LowerDirs, squasfsMount)
}

changeDir, err := createChangeDir(c)
Expand All @@ -24,35 +30,54 @@ func MountRootfs(c *config.Config) error {
return fmt.Errorf("creating workdir: %s", err)
}

options := fmt.Sprintf("lowerdir=%s,upperdir=%s,workdir=%s", squasfsMount, changeDir.uppper, changeDir.work)
err = unix.Mount("overlay", c.RootfsDir, "overlay", 0, options)
if err != nil {
return fmt.Errorf("overlay: %s", err)
if len(c.LowerDirs) == 0 {
if len(c.Volumes) == 0 {
return fmt.Errorf("no lower dir is provided")
}
} else {
// check folder is exist
for _, folder := range c.LowerDirs {
if _, err := os.Stat(folder); err != nil {
return fmt.Errorf("folder %s is not exist: %e", folder, err)
}
}
}

if len(c.LowerDirs) != 0 {
options := fmt.Sprintf("lowerdir=%s,upperdir=%s,workdir=%s", strings.Join(c.LowerDirs, ":"), changeDir.uppper, changeDir.work)
err = unix.Mount("overlay", c.RootfsDir, "overlay", 0, options)
if err != nil {
return fmt.Errorf("overlay: %s", err)
}
}
return nil

}

func UmountRootfs(c *config.Config) []error {
errors := []error{}
err := umountSquashfsFile(c)
if err != nil {
errors = append(errors, err)
}

err = unix.Unmount(c.RootfsDir, 0)
if err != nil {
if !os.IsNotExist(err) {
var err error
if c.SquashfsFile != "" {
err = umountSquashfsFile(c)
if err != nil {
errors = append(errors, err)
}
}
err = os.Remove(c.RootfsDir)
if err != nil {
if !os.IsNotExist(err) {
errors = append(errors, err)

if len(c.LowerDirs) != 0 || c.SquashfsFile != "" {
err = unix.Unmount(c.RootfsDir, 0)
if err != nil {
if !os.IsNotExist(err) {
errors = append(errors, err)
}
}
err = os.Remove(c.RootfsDir)
if err != nil {
if !os.IsNotExist(err) {
errors = append(errors, err)
}
}
}

if c.ChangeDir == "" && c.TmpSize != 0 {
err = unix.Unmount(defaultChangeRoot(c), 0)
if err != nil {
Expand All @@ -62,10 +87,13 @@ func UmountRootfs(c *config.Config) []error {
}
}

err = DetachLoopDevice(c.LoopDevNo)
if err != nil {
errors = append(errors, err)
if c.SquashfsFile != "" {
err = DetachLoopDevice(c.LoopDevNo)
if err != nil {
errors = append(errors, err)
}
}

if len(errors) == 0 {
return nil
}
Expand Down

0 comments on commit f1f8e2a

Please sign in to comment.