github.com/cdoern/storage@v1.12.13/pkg/ostree/ostree.go (about)

     1  // +build ostree
     2  
     3  package ostree
     4  
     5  import (
     6  	"fmt"
     7  	"golang.org/x/sys/unix"
     8  	"os"
     9  	"path/filepath"
    10  	"runtime"
    11  	"syscall"
    12  	"time"
    13  	"unsafe"
    14  
    15  	"github.com/containers/storage/pkg/idtools"
    16  	"github.com/containers/storage/pkg/system"
    17  	glib "github.com/ostreedev/ostree-go/pkg/glibobject"
    18  	"github.com/ostreedev/ostree-go/pkg/otbuiltin"
    19  	"github.com/pkg/errors"
    20  )
    21  
    22  // #cgo pkg-config: glib-2.0 gobject-2.0 ostree-1
    23  // #include <glib.h>
    24  // #include <glib-object.h>
    25  // #include <gio/gio.h>
    26  // #include <stdlib.h>
    27  // #include <ostree.h>
    28  // #include <gio/ginputstream.h>
    29  import "C"
    30  
    31  func OstreeSupport() bool {
    32  	return true
    33  }
    34  
    35  func fixFiles(dir string, usermode bool) (bool, []string, error) {
    36  	var SkipOstree = errors.New("skip ostree deduplication")
    37  
    38  	var whiteouts []string
    39  
    40  	err := filepath.Walk(dir, func(path string, info os.FileInfo, err error) error {
    41  		if info.Mode()&(os.ModeNamedPipe|os.ModeSocket|os.ModeDevice) != 0 {
    42  			if !usermode {
    43  				stat, ok := info.Sys().(*syscall.Stat_t)
    44  				if !ok {
    45  					return errors.New("not syscall.Stat_t")
    46  				}
    47  
    48  				if stat.Rdev == 0 && (stat.Mode&unix.S_IFCHR) != 0 {
    49  					whiteouts = append(whiteouts, path)
    50  					return nil
    51  				}
    52  			}
    53  			// Skip the ostree deduplication if we encounter a file type that
    54  			// ostree does not manage.
    55  			return SkipOstree
    56  		}
    57  		if info.IsDir() {
    58  			if usermode {
    59  				if err := os.Chmod(path, info.Mode()|0700); err != nil {
    60  					return err
    61  				}
    62  			}
    63  		} else if usermode && (info.Mode().IsRegular()) {
    64  			if err := os.Chmod(path, info.Mode()|0600); err != nil {
    65  				return err
    66  			}
    67  		}
    68  		return nil
    69  	})
    70  	if err == SkipOstree {
    71  		return true, nil, nil
    72  	}
    73  	if err != nil {
    74  		return false, nil, err
    75  	}
    76  	return false, whiteouts, nil
    77  }
    78  
    79  // Create prepares the filesystem for the OSTREE driver and copies the directory for the given id under the parent.
    80  func ConvertToOSTree(repoLocation, root, id string) error {
    81  	runtime.LockOSThread()
    82  	defer runtime.UnlockOSThread()
    83  	repo, err := otbuiltin.OpenRepo(repoLocation)
    84  	if err != nil {
    85  		return errors.Wrap(err, "could not open the OSTree repository")
    86  	}
    87  
    88  	skip, whiteouts, err := fixFiles(root, os.Getuid() != 0)
    89  	if err != nil {
    90  		return errors.Wrap(err, "could not prepare the OSTree directory")
    91  	}
    92  	if skip {
    93  		return nil
    94  	}
    95  
    96  	if _, err := repo.PrepareTransaction(); err != nil {
    97  		return errors.Wrap(err, "could not prepare the OSTree transaction")
    98  	}
    99  
   100  	if skip {
   101  		return nil
   102  	}
   103  
   104  	commitOpts := otbuiltin.NewCommitOptions()
   105  	commitOpts.Timestamp = time.Now()
   106  	commitOpts.LinkCheckoutSpeedup = true
   107  	commitOpts.Parent = "0000000000000000000000000000000000000000000000000000000000000000"
   108  	branch := fmt.Sprintf("containers-storage/%s", id)
   109  
   110  	for _, w := range whiteouts {
   111  		if err := os.Remove(w); err != nil {
   112  			return errors.Wrap(err, "could not delete whiteout file")
   113  		}
   114  	}
   115  
   116  	if _, err := repo.Commit(root, branch, commitOpts); err != nil {
   117  		return errors.Wrap(err, "could not commit the layer")
   118  	}
   119  
   120  	if _, err := repo.CommitTransaction(); err != nil {
   121  		return errors.Wrap(err, "could not complete the OSTree transaction")
   122  	}
   123  
   124  	if err := system.EnsureRemoveAll(root); err != nil {
   125  		return errors.Wrap(err, "could not delete layer")
   126  	}
   127  
   128  	checkoutOpts := otbuiltin.NewCheckoutOptions()
   129  	checkoutOpts.RequireHardlinks = true
   130  	checkoutOpts.Whiteouts = false
   131  	if err := otbuiltin.Checkout(repoLocation, root, branch, checkoutOpts); err != nil {
   132  		return errors.Wrap(err, "could not checkout from OSTree")
   133  	}
   134  
   135  	for _, w := range whiteouts {
   136  		if err := unix.Mknod(w, unix.S_IFCHR, 0); err != nil {
   137  			return errors.Wrap(err, "could not recreate whiteout file")
   138  		}
   139  	}
   140  	return nil
   141  }
   142  
   143  func CreateOSTreeRepository(repoLocation string, rootUID int, rootGID int) error {
   144  	runtime.LockOSThread()
   145  	defer runtime.UnlockOSThread()
   146  
   147  	_, err := os.Stat(repoLocation)
   148  	if err != nil && !os.IsNotExist(err) {
   149  		return err
   150  	} else if err != nil {
   151  		if err := idtools.MkdirAllAs(repoLocation, 0700, rootUID, rootGID); err != nil {
   152  			return errors.Wrap(err, "could not create OSTree repository directory: %v")
   153  		}
   154  
   155  		if _, err := otbuiltin.Init(repoLocation, otbuiltin.NewInitOptions()); err != nil {
   156  			return errors.Wrap(err, "could not create OSTree repository")
   157  		}
   158  	}
   159  	return nil
   160  }
   161  
   162  func openRepo(path string) (*C.struct_OstreeRepo, error) {
   163  	var cerr *C.GError
   164  	cpath := C.CString(path)
   165  	defer C.free(unsafe.Pointer(cpath))
   166  	pathc := C.g_file_new_for_path(cpath)
   167  	defer C.g_object_unref(C.gpointer(pathc))
   168  	repo := C.ostree_repo_new(pathc)
   169  	r := glib.GoBool(glib.GBoolean(C.ostree_repo_open(repo, nil, &cerr)))
   170  	if !r {
   171  		C.g_object_unref(C.gpointer(repo))
   172  		return nil, glib.ConvertGError(glib.ToGError(unsafe.Pointer(cerr)))
   173  	}
   174  	return repo, nil
   175  }
   176  
   177  func DeleteOSTree(repoLocation, id string) error {
   178  	runtime.LockOSThread()
   179  	defer runtime.UnlockOSThread()
   180  
   181  	repo, err := openRepo(repoLocation)
   182  	if err != nil {
   183  		return err
   184  	}
   185  	defer C.g_object_unref(C.gpointer(repo))
   186  
   187  	branch := fmt.Sprintf("containers-storage/%s", id)
   188  
   189  	cbranch := C.CString(branch)
   190  	defer C.free(unsafe.Pointer(cbranch))
   191  
   192  	var cerr *C.GError
   193  	r := glib.GoBool(glib.GBoolean(C.ostree_repo_set_ref_immediate(repo, nil, cbranch, nil, nil, &cerr)))
   194  	if !r {
   195  		return glib.ConvertGError(glib.ToGError(unsafe.Pointer(cerr)))
   196  	}
   197  	return nil
   198  }