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 }