diff --git a/pkg/glibobject/gcancellable.go b/pkg/glibobject/gcancellable.go index 537db47..44c200f 100644 --- a/pkg/glibobject/gcancellable.go +++ b/pkg/glibobject/gcancellable.go @@ -33,13 +33,20 @@ import ( // GIO types type GCancellable struct { - *GObject + *Object } func (self *GCancellable) native() *C.GCancellable { return (*C.GCancellable)(unsafe.Pointer(self)) } +func (self *GCancellable) Native() uintptr { + if self == nil || self.Object == nil { + return uintptr(unsafe.Pointer(nil)) + } + return uintptr(unsafe.Pointer(self.Object)) +} + func (self *GCancellable) Ptr() unsafe.Pointer { return unsafe.Pointer(self) } diff --git a/pkg/glibobject/gkeyfile.go b/pkg/glibobject/gkeyfile.go new file mode 100644 index 0000000..eec0eb8 --- /dev/null +++ b/pkg/glibobject/gkeyfile.go @@ -0,0 +1,62 @@ +/* + * Copyright (c) 2013 Conformal Systems + * Copyright (c) 2017 Collabora Ltd + * + * This file originated from: http://opensource.conformal.com/ + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +package glibobject + +// #cgo pkg-config: glib-2.0 +// #include +import "C" +import ( + "runtime" + "unsafe" +) + +/* + * GKeyFile + */ + +type GKeyFile struct { + keyfile *C.struct__GKeyFile +} + +func (kf *GKeyFile) Native() uintptr { + if kf == nil || kf.keyfile == nil { + return uintptr(unsafe.Pointer(nil)) + } + return uintptr(unsafe.Pointer(kf.keyfile)) +} + +func (kf *GKeyFile) Ref() { + C.g_key_file_ref(kf.keyfile) +} + +func (kf *GKeyFile) Unref() { + C.g_key_file_unref(kf.keyfile) +} + +func WrapGKeyFile(keyfile uintptr) *GKeyFile { + ckf := (*C.struct__GKeyFile)(unsafe.Pointer(keyfile)) + if ckf == nil { + return nil + } + kf := &GKeyFile{ckf} + runtime.SetFinalizer(kf, (*GKeyFile).Unref) + + return kf +} diff --git a/pkg/glibobject/glibobject.go.h b/pkg/glibobject/glibobject.go.h index a55bd24..28a2909 100644 --- a/pkg/glibobject/glibobject.go.h +++ b/pkg/glibobject/glibobject.go.h @@ -15,3 +15,9 @@ _g_variant_lookup_string (GVariant *v, const char *key) return r; return NULL; } + +static GObject * +toGObject(void *p) +{ + return (G_OBJECT(p)); +} diff --git a/pkg/glibobject/gobject.go b/pkg/glibobject/gobject.go index dedbe74..9cc4a54 100644 --- a/pkg/glibobject/gobject.go +++ b/pkg/glibobject/gobject.go @@ -33,47 +33,50 @@ import ( * GObject */ +// ToGObject type converts an unsafe.Pointer as a native C GObject. +// This function is exported for visibility in other gotk3 packages and +// is not meant to be used by applications. +func ToGObject(p unsafe.Pointer) *C.GObject { + return C.toGObject(p) +} + // IObject is an interface type implemented by Object and all types which embed // an Object. It is meant to be used as a type for function arguments which // require GObjects or any subclasses thereof. type IObject interface { toGObject() *C.GObject - ToObject() *GObject + ToObject() *Object } // GObject is a representation of GLib's GObject. -type GObject struct { - ptr unsafe.Pointer -} - -func (v *GObject) Ptr() unsafe.Pointer { - return v.ptr +type Object struct { + GObject *C.GObject } -func (v *GObject) native() *C.GObject { - if v == nil { - return nil +func (v *Object) Native() uintptr { + if v == nil || v.GObject == nil { + return uintptr(unsafe.Pointer(nil)) } - return (*C.GObject)(v.ptr) + return uintptr(unsafe.Pointer(v.GObject)) } -func (v *GObject) Ref() { - C.g_object_ref(C.gpointer(v.Ptr())) +func (v *Object) Ref() { + C.g_object_ref(C.gpointer(v.GObject)) } -func (v *GObject) Unref() { - C.g_object_unref(C.gpointer(v.Ptr())) +func (v *Object) Unref() { + C.g_object_unref(C.gpointer(v.GObject)) } -func (v *GObject) RefSink() { - C.g_object_ref_sink(C.gpointer(v.native())) +func (v *Object) RefSink() { + C.g_object_ref_sink(C.gpointer(v.GObject)) } -func (v *GObject) IsFloating() bool { - c := C.g_object_is_floating(C.gpointer(v.native())) +func (v *Object) IsFloating() bool { + c := C.g_object_is_floating(C.gpointer(v.GObject)) return GoBool(GBoolean(c)) } -func (v *GObject) ForceFloating() { - C.g_object_force_floating(v.native()) +func (v *Object) ForceFloating() { + C.g_object_force_floating(v.GObject) } diff --git a/pkg/glibobject/gvariant.go b/pkg/glibobject/gvariant.go index 30572ea..6a6acfd 100644 --- a/pkg/glibobject/gvariant.go +++ b/pkg/glibobject/gvariant.go @@ -56,6 +56,9 @@ func (v *GVariant) native() *C.GVariant { } func (v *GVariant) Ptr() unsafe.Pointer { + if v == nil { + return nil + } return v.ptr } diff --git a/pkg/otbuiltin/builtin.go b/pkg/otbuiltin/builtin.go index d3a8ae5..b5ea406 100644 --- a/pkg/otbuiltin/builtin.go +++ b/pkg/otbuiltin/builtin.go @@ -23,6 +23,10 @@ type Repo struct { ptr unsafe.Pointer } +func cCancellable(c *glib.GCancellable) *C.GCancellable { + return (*C.GCancellable)(unsafe.Pointer(c.Native())) +} + // Converts an ostree repo struct to its C equivalent func (r *Repo) native() *C.OstreeRepo { //return (*C.OstreeRepo)(r.Ptr()) @@ -48,6 +52,24 @@ func (r *Repo) isInitialized() bool { return false } +func (repo *Repo) ResolveRev(refspec string, allowNoent bool) (string, error) { + var cerr *C.GError = nil + var coutrev *C.char = nil + + crefspec := C.CString(refspec) + defer C.free(unsafe.Pointer(crefspec)) + + r := C.ostree_repo_resolve_rev(repo.native(), crefspec, (C.gboolean)(glib.GBool(allowNoent)), &coutrev, &cerr) + if !gobool(r) { + return "", generateError(cerr) + } + + outrev := C.GoString(coutrev) + C.free(unsafe.Pointer(coutrev)) + + return outrev, nil +} + // Attempts to open the repo at the given path func OpenRepo(path string) (*Repo, error) { var cerr *C.GError = nil @@ -63,6 +85,56 @@ func OpenRepo(path string) (*Repo, error) { return repo, nil } +type PullOptions struct { + OverrideRemoteName string + Refs []string +} + +func (repo *Repo) PullWithOptions(remoteName string, options PullOptions, progress *AsyncProgress, cancellable *glib.GCancellable) error { + var cerr *C.GError = nil + + cremoteName := C.CString(remoteName) + defer C.free(unsafe.Pointer(cremoteName)) + + builder := C.g_variant_builder_new(C._g_variant_type(C.CString("a{sv}"))) + if options.OverrideRemoteName != "" { + cstr := C.CString(options.OverrideRemoteName) + v := C.g_variant_new_take_string((*C.gchar)(cstr)) + k := C.CString("override-remote-name") + defer C.free(unsafe.Pointer(k)) + C._g_variant_builder_add_twoargs(builder, C.CString("{sv}"), k, v) + } + + if len(options.Refs) != 0 { + crefs := make([]*C.gchar, len(options.Refs)) + for i, s := range options.Refs { + crefs[i] = (*C.gchar)(C.CString(s)) + } + + v := C.g_variant_new_strv((**C.gchar)(&crefs[0]), (C.gssize)(len(crefs))) + + for i, s := range crefs { + crefs[i] = nil + C.free(unsafe.Pointer(s)) + } + + k := C.CString("refs") + defer C.free(unsafe.Pointer(k)) + + C._g_variant_builder_add_twoargs(builder, C.CString("{sv}"), k, v) + } + + coptions := C.g_variant_builder_end(builder) + + r := C.ostree_repo_pull_with_options(repo.native(), cremoteName, coptions, progress.native(), cCancellable(cancellable), &cerr) + + if !gobool(r) { + return generateError(cerr) + } + + return nil +} + // Enable support for tombstone commits, which allow the repo to distinguish between // commits that were intentionally deleted and commits that were removed accidentally func enableTombstoneCommits(repo *Repo) error { @@ -91,3 +163,18 @@ func generateError(err *C.GError) error { return goErr } } + +func gobool(b C.gboolean) bool { + return b != C.FALSE +} + +type AsyncProgress struct { + *glib.Object +} + +func (a *AsyncProgress) native() *C.OstreeAsyncProgress { + if a == nil { + return nil + } + return (*C.OstreeAsyncProgress)(unsafe.Pointer(a.Native())) +} diff --git a/pkg/otbuiltin/commit.go b/pkg/otbuiltin/commit.go index 9550f80..af4d448 100644 --- a/pkg/otbuiltin/commit.go +++ b/pkg/otbuiltin/commit.go @@ -59,11 +59,11 @@ func NewCommitOptions() commitOptions { } type OstreeRepoTransactionStats struct { - metadata_objects_total int32 + metadata_objects_total int32 metadata_objects_written int32 - content_objects_total int32 - content_objects_written int32 - content_bytes_written uint64 + content_objects_total int32 + content_objects_written int32 + content_bytes_written uint64 } func (repo *Repo) PrepareTransaction() (bool, error) { @@ -140,7 +140,7 @@ func (repo *Repo) Commit(commitPath, branch string, opts commitOptions) (string, var cerr *C.GError defer C.free(unsafe.Pointer(cerr)) var metadata *C.GVariant = nil - defer func(){ + defer func() { if metadata != nil { defer C.g_variant_unref(metadata) } @@ -476,7 +476,7 @@ func handleStatOverrideLine(line string, table *glib.GHashTable) error { // Handle an individual line from a Skiplist file func handleSkipListline(line string, table *glib.GHashTable) error { - C.g_hash_table_add((*C.GHashTable)(table.Ptr()), C.gpointer( C.g_strdup((*C.gchar)(C.CString(line))))) + C.g_hash_table_add((*C.GHashTable)(table.Ptr()), C.gpointer(C.g_strdup((*C.gchar)(C.CString(line))))) return nil } diff --git a/pkg/otbuiltin/deployment.go b/pkg/otbuiltin/deployment.go new file mode 100644 index 0000000..afd4681 --- /dev/null +++ b/pkg/otbuiltin/deployment.go @@ -0,0 +1,36 @@ +package otbuiltin + +import ( + "runtime" + "unsafe" + + glib "github.com/ostreedev/ostree-go/pkg/glibobject" +) + +// #cgo pkg-config: ostree-1 +// #include +// #include +// #include +// #include "builtin.go.h" +import "C" + +type Deployment struct { + *glib.Object +} + +func wrapDeployment(d *C.OstreeDeployment) *Deployment { + g := glib.ToGObject(unsafe.Pointer(d)) + obj := &glib.Object{g} + deployment := &Deployment{obj} + + runtime.SetFinalizer(deployment, (*Deployment).Unref) + + return deployment +} + +func (d *Deployment) native() *C.OstreeDeployment { + if d == nil || d.GObject == nil { + return nil + } + return (*C.OstreeDeployment)(unsafe.Pointer(d.GObject)) +} diff --git a/pkg/otbuiltin/remote.go b/pkg/otbuiltin/remote.go index d43ea07..75278c8 100644 --- a/pkg/otbuiltin/remote.go +++ b/pkg/otbuiltin/remote.go @@ -1 +1,33 @@ package otbuiltin + +import ( + glib "github.com/ostreedev/ostree-go/pkg/glibobject" + "unsafe" +) + +// #cgo pkg-config: ostree-1 +// #include +// #include +// #include +// #include "builtin.go.h" +import "C" + +func (repo *Repo) RemoteAdd(name, url string, options *glib.GVariant, + cancellable *glib.GCancellable) error { + + var cerr *C.GError = nil + + cname := C.CString(name) + defer C.free(unsafe.Pointer(cname)) + + curl := C.CString(url) + defer C.free(unsafe.Pointer(curl)) + + r := C.ostree_repo_remote_add(repo.native(), cname, curl, (*C.GVariant)(options.Ptr()), cCancellable(cancellable), &cerr) + + if !gobool(r) { + return generateError(cerr) + } + + return nil +} diff --git a/pkg/otbuiltin/sysroot.go b/pkg/otbuiltin/sysroot.go new file mode 100644 index 0000000..7a4a994 --- /dev/null +++ b/pkg/otbuiltin/sysroot.go @@ -0,0 +1,206 @@ +package otbuiltin + +import ( + "errors" + glib "github.com/ostreedev/ostree-go/pkg/glibobject" + "os" + "path" + "runtime" + "unsafe" +) + +// #cgo pkg-config: ostree-1 +// #include +// #include +// #include +// #include "builtin.go.h" +import "C" + +type Sysroot struct { + *glib.Object + path string +} + +func NewSysroot(path string) *Sysroot { + cpath := C.CString(path) + defer C.free(unsafe.Pointer(cpath)) + + cgfile := C.g_file_new_for_path(cpath) + defer C.g_object_unref(C.gpointer(cgfile)) + + s := C.ostree_sysroot_new(cgfile) + g := glib.ToGObject(unsafe.Pointer(s)) + obj := &glib.Object{g} + sysroot := &Sysroot{obj, path} + + runtime.SetFinalizer(sysroot, (*Sysroot).Unref) + + return sysroot +} + +func (s *Sysroot) native() *C.OstreeSysroot { + if s == nil || s.GObject == nil { + return nil + } + return (*C.OstreeSysroot)(unsafe.Pointer(s.GObject)) +} + +func (s Sysroot) Path() string { + return s.path +} + +func (s Sysroot) InitializeFS() error { + toplevels := []struct { + name string + perm os.FileMode + }{ + {"boot", 0777}, + {"dev", 0777}, + {"home", 0777}, + {"proc", 0777}, + {"run", 0777}, + {"sys", 0777}, + {"tmp", 01777}, + {"root", 0700}, + } + + if _, err := os.Stat(path.Join(s.path, "ostree")); err == nil { + return errors.New("Filesystem already initialized for ostree") + } + + for _, t := range toplevels { + p := path.Join(s.path, t.name) + err := os.Mkdir(p, t.perm) + if err != nil && !os.IsExist(err) { + return err + } + } + + return s.EnsureInitialized(nil) +} + +func (s *Sysroot) EnsureInitialized(cancellable *glib.GCancellable) error { + var cerr *C.GError = nil + + r := C.ostree_sysroot_ensure_initialized(s.native(), cCancellable(cancellable), &cerr) + + if !gobool(r) { + return generateError(cerr) + } + return nil +} + +func (s *Sysroot) InitOsname(name string, cancellable *glib.GCancellable) error { + var cerr *C.GError = nil + cname := C.CString(name) + defer C.free(unsafe.Pointer(cname)) + + r := C.ostree_sysroot_init_osname(s.native(), cname, cCancellable(cancellable), &cerr) + + if !gobool(r) { + return generateError(cerr) + } + return nil +} + +func (s *Sysroot) Load(cancellable *glib.GCancellable) error { + var cerr *C.GError = nil + r := C.ostree_sysroot_load(s.native(), cCancellable(cancellable), &cerr) + + if !gobool(r) { + return generateError(cerr) + } + return nil +} + +func (s *Sysroot) Repo(cancellable *glib.GCancellable) (*Repo, error) { + var repo *C.OstreeRepo + var cerr *C.GError = nil + + r := C.ostree_sysroot_get_repo(s.native(), &repo, cCancellable(cancellable), &cerr) + + if !gobool(r) { + return nil, generateError(cerr) + } + return repoFromNative(repo), nil +} + +func (s *Sysroot) OriginNewFromRefspec(refspec string) *glib.GKeyFile { + crefspec := C.CString(refspec) + defer C.free(unsafe.Pointer(crefspec)) + + kf := C.ostree_sysroot_origin_new_from_refspec(s.native(), crefspec) + return glib.WrapGKeyFile(uintptr(unsafe.Pointer(kf))) +} + +func (s *Sysroot) PrepareCleanup(cancellable *glib.GCancellable) error { + var cerr *C.GError = nil + r := C.ostree_sysroot_prepare_cleanup(s.native(), cCancellable(cancellable), &cerr) + + if !gobool(r) { + return generateError(cerr) + } + return nil +} + +func (s *Sysroot) DeployTree(osname, revision string, origin *glib.GKeyFile, providedMergeDeployment *Deployment, overrideKernelArgv []string, cancellable *glib.GCancellable) (*Deployment, error) { + var cerr *C.GError = nil + var cdeployment *C.OstreeDeployment = nil + + cosname := C.CString(osname) + defer C.free(unsafe.Pointer(cosname)) + + crevision := C.CString(revision) + defer C.free(unsafe.Pointer(crevision)) + + coverrideKernelArgv := make([]*C.char, len(overrideKernelArgv)+1) + for i, s := range overrideKernelArgv { + coverrideKernelArgv[i] = C.CString(s) + } + coverrideKernelArgv[len(overrideKernelArgv)] = nil + + r := C.ostree_sysroot_deploy_tree(s.native(), cosname, crevision, + (*C.struct__GKeyFile)(unsafe.Pointer(origin.Native())), + providedMergeDeployment.native(), + (**C.char)(unsafe.Pointer(&coverrideKernelArgv[0])), + &cdeployment, + cCancellable(cancellable), &cerr) + + for i, s := range coverrideKernelArgv { + coverrideKernelArgv[i] = nil + C.free(unsafe.Pointer(s)) + } + + if !gobool(r) { + return nil, generateError(cerr) + } + return wrapDeployment(cdeployment), nil +} + +type SysrootSimpleWriteDeploymentFlags int + +const ( + OSTREE_SYSROOT_SIMPLE_WRITE_DEPLOYMENT_FLAGS_NONE SysrootSimpleWriteDeploymentFlags = 1 << iota + OSTREE_SYSROOT_SIMPLE_WRITE_DEPLOYMENT_FLAGS_RETAIN SysrootSimpleWriteDeploymentFlags = 1 << iota + OSTREE_SYSROOT_SIMPLE_WRITE_DEPLOYMENT_FLAGS_NOT_DEFAULT SysrootSimpleWriteDeploymentFlags = 1 << iota + OSTREE_SYSROOT_SIMPLE_WRITE_DEPLOYMENT_FLAGS_NO_CLEAN SysrootSimpleWriteDeploymentFlags = 1 << iota +) + +func (s *Sysroot) SimpleWriteDeployment(osname string, newDeployment, + mergeDeployment *Deployment, flags SysrootSimpleWriteDeploymentFlags, cancellable *glib.GCancellable) error { + var cerr *C.GError = nil + cosname := C.CString(osname) + defer C.free(unsafe.Pointer(cosname)) + + r := C.ostree_sysroot_simple_write_deployment(s.native(), + cosname, + newDeployment.native(), + mergeDeployment.native(), + (C.OstreeSysrootSimpleWriteDeploymentFlags)(flags), + cCancellable(cancellable), &cerr) + + if !gobool(r) { + return generateError(cerr) + } + return nil +} diff --git a/pkg/otbuiltin/sysroot_test.go b/pkg/otbuiltin/sysroot_test.go new file mode 100644 index 0000000..7b33758 --- /dev/null +++ b/pkg/otbuiltin/sysroot_test.go @@ -0,0 +1,115 @@ +package otbuiltin + +import ( + "io/ioutil" + "os" + _ "os" + "path" + "testing" +) + +func TestSysrootDeployment(t *testing.T) { + osname := "testos" + refspec := "testbranch" + + baseDir, err := ioutil.TempDir("", "otbuiltin-test-") + if err != nil { + t.Errorf("%s", err) + return + } + defer os.RemoveAll(baseDir) + + sysrootDir := path.Join(baseDir, "sysroot") + os.Mkdir(sysrootDir, 0777) + + commitDir := path.Join(baseDir, "commit1") + os.Mkdir(commitDir, 0777) + + sysroot := NewSysroot(sysrootDir) + err = sysroot.InitializeFS() + if err != nil { + t.Errorf("%s", err) + } + + // Try again this time it should fail as it's already initialized + err = sysroot.InitializeFS() + if err == nil { + t.Errorf("Initialized succeeded a second time!", err) + } + + err = sysroot.InitOsname(osname, nil) + if err != nil { + t.Errorf("%s", err) + } + + repo, err := sysroot.Repo(nil) + if err != nil { + t.Fatalf("%s", err) + } + + /* Dummy commit */ + err = os.Mkdir(path.Join(commitDir, "boot"), 0777) + if err != nil { + t.Fatalf("%s", err) + } + + err = ioutil.WriteFile(path.Join(commitDir, "boot", "vmlinuz-00e3261a6e0d79c329445acd540fb2b07187a0dcf6017065c8814010283ac67f"), []byte("A kernel it is"), 0777) + if err != nil { + t.Fatalf("%s", err) + } + + usretcDir := path.Join(commitDir, "usr", "etc") + err = os.MkdirAll(usretcDir, 0777) + if err != nil { + t.Fatalf("%s", err) + } + + err = ioutil.WriteFile(path.Join(usretcDir, "os-release"), []byte("PRETTY_NAME=testos\n"), 0777) + if err != nil { + t.Fatalf("%s", err) + } + + opts := NewCommitOptions() + _, err = repo.PrepareTransaction() + if err != nil { + t.Fatalf("%s", err) + } + + _, err = repo.Commit(commitDir, refspec, opts) + if err != nil { + t.Fatalf("%s", err) + } + + _, err = repo.CommitTransaction() + if err != nil { + t.Fatalf("%s", err) + } + + err = repo.RemoteAdd("origin", "https://example.com", nil, nil) + if err != nil { + t.Fatalf("%s", err) + } + + /* Required by ostree to make sure a bunc of information was pulled in */ + sysroot.Load(nil) + + revision, err := repo.ResolveRev(refspec, false) + if err != nil { + t.Fatalf("%s", err) + } + + if revision == "" { + t.Fatal("Revision doesn't exist") + } + + origin := sysroot.OriginNewFromRefspec(refspec) + deployment, err := sysroot.DeployTree(osname, revision, origin, nil, nil, nil) + if err != nil { + t.Fatalf("%s", err) + } + + err = sysroot.SimpleWriteDeployment(osname, deployment, nil, 0, nil) + if err != nil { + t.Fatalf("%s", err) + } +}