1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217
|
package otbuiltin
import (
"bytes"
"errors"
"strconv"
"strings"
"time"
"unsafe"
glib "github.com/ostreedev/ostree-go/pkg/glibobject"
)
// #cgo pkg-config: ostree-1
// #include <stdlib.h>
// #include <glib.h>
// #include <ostree.h>
// #include "builtin.go.h"
import "C"
// Declare gobal variable for options
var pruneOpts pruneOptions
// Contains all of the options for pruning an ostree repo. Use
// NewPruneOptions() to initialize
type pruneOptions struct {
NoPrune bool // Only display unreachable objects; don't delete
RefsOnly bool // Only compute reachability via refs
DeleteCommit string // Specify a commit to delete
KeepYoungerThan time.Time // All commits older than this date will be pruned
Depth int // Only traverse depths (integer) parents for each commit (default: -1=infinite)
StaticDeltasOnly int // Change the behavior of --keep-younger-than and --delete-commit to prune only the static delta files
}
// Instantiates and returns a pruneOptions struct with default values set
func NewPruneOptions() pruneOptions {
po := new(pruneOptions)
po.Depth = -1
return *po
}
// Search for unreachable objects in the repository given by repoPath. Removes the
// objects unless pruneOptions.NoPrune is specified
func Prune(repoPath string, options pruneOptions) (string, error) {
pruneOpts = options
// attempt to open the repository
repo, err := OpenRepo(repoPath)
if err != nil {
return "", err
}
var pruneFlags C.OstreeRepoPruneFlags
var numObjectsTotal int
var numObjectsPruned int
var objSizeTotal uint64
var gerr = glib.NewGError()
var cerr = (*C.GError)(gerr.Ptr())
defer C.free(unsafe.Pointer(cerr))
var cancellable *glib.GCancellable
if !pruneOpts.NoPrune && !glib.GoBool(glib.GBoolean(C.ostree_repo_is_writable(repo.native(), &cerr))) {
return "", generateError(cerr)
}
cerr = nil
if strings.Compare(pruneOpts.DeleteCommit, "") != 0 {
if pruneOpts.NoPrune {
return "", errors.New("Cannot specify both pruneOptions.DeleteCommit and pruneOptions.NoPrune")
}
if pruneOpts.StaticDeltasOnly > 0 {
if glib.GoBool(glib.GBoolean(C.ostree_repo_prune_static_deltas(repo.native(), C.CString(pruneOpts.DeleteCommit), (*C.GCancellable)(cancellable.Ptr()), &cerr))) {
return "", generateError(cerr)
}
} else if err = deleteCommit(repo, pruneOpts.DeleteCommit, cancellable); err != nil {
return "", err
}
}
if !pruneOpts.KeepYoungerThan.IsZero() {
if pruneOpts.NoPrune {
return "", errors.New("Cannot specify both pruneOptions.KeepYoungerThan and pruneOptions.NoPrune")
}
if err = pruneCommitsKeepYoungerThanDate(repo, pruneOpts.KeepYoungerThan, cancellable); err != nil {
return "", err
}
}
if pruneOpts.RefsOnly {
pruneFlags |= C.OSTREE_REPO_PRUNE_FLAGS_REFS_ONLY
}
if pruneOpts.NoPrune {
pruneFlags |= C.OSTREE_REPO_PRUNE_FLAGS_NO_PRUNE
}
formattedFreedSize := C.GoString((*C.char)(C.g_format_size_full((C.guint64)(objSizeTotal), 0)))
var buffer bytes.Buffer
buffer.WriteString("Total objects: ")
buffer.WriteString(strconv.Itoa(numObjectsTotal))
if numObjectsPruned == 0 {
buffer.WriteString("\nNo unreachable objects")
} else if pruneOpts.NoPrune {
buffer.WriteString("\nWould delete: ")
buffer.WriteString(strconv.Itoa(numObjectsPruned))
buffer.WriteString(" objects, freeing ")
buffer.WriteString(formattedFreedSize)
} else {
buffer.WriteString("\nDeleted ")
buffer.WriteString(strconv.Itoa(numObjectsPruned))
buffer.WriteString(" objects, ")
buffer.WriteString(formattedFreedSize)
buffer.WriteString(" freed")
}
return buffer.String(), nil
}
// Delete an unreachable commit from the repo
func deleteCommit(repo *Repo, commitToDelete string, cancellable *glib.GCancellable) error {
var refs *glib.GHashTable
var hashIter glib.GHashTableIter
var hashkey, hashvalue C.gpointer
var gerr = glib.NewGError()
var cerr = (*C.GError)(gerr.Ptr())
defer C.free(unsafe.Pointer(cerr))
if glib.GoBool(glib.GBoolean(C.ostree_repo_list_refs(repo.native(), nil, (**C.GHashTable)(refs.Ptr()), (*C.GCancellable)(cancellable.Ptr()), &cerr))) {
return generateError(cerr)
}
C.g_hash_table_iter_init((*C.GHashTableIter)(hashIter.Ptr()), (*C.GHashTable)(refs.Ptr()))
for C.g_hash_table_iter_next((*C.GHashTableIter)(hashIter.Ptr()), &hashkey, &hashvalue) != 0 {
var ref string = C.GoString((*C.char)(hashkey))
var commit string = C.GoString((*C.char)(hashvalue))
if strings.Compare(commitToDelete, commit) == 0 {
var buffer bytes.Buffer
buffer.WriteString("Commit ")
buffer.WriteString(commitToDelete)
buffer.WriteString(" is referenced by ")
buffer.WriteString(ref)
return errors.New(buffer.String())
}
}
if err := repo.enableTombstoneCommits(); err != nil {
return err
}
if !glib.GoBool(glib.GBoolean(C.ostree_repo_delete_object(repo.native(), C.OSTREE_OBJECT_TYPE_COMMIT, C.CString(commitToDelete), (*C.GCancellable)(cancellable.Ptr()), &cerr))) {
return generateError(cerr)
}
return nil
}
// Prune commits but keep any younger than the given date regardless of whether they
// are reachable
func pruneCommitsKeepYoungerThanDate(repo *Repo, date time.Time, cancellable *glib.GCancellable) error {
var objects *glib.GHashTable
defer C.free(unsafe.Pointer(objects))
var hashIter glib.GHashTableIter
var key, value C.gpointer
defer C.free(unsafe.Pointer(key))
defer C.free(unsafe.Pointer(value))
var gerr = glib.NewGError()
var cerr = (*C.GError)(gerr.Ptr())
defer C.free(unsafe.Pointer(cerr))
if err := repo.enableTombstoneCommits(); err != nil {
return err
}
if !glib.GoBool(glib.GBoolean(C.ostree_repo_list_objects(repo.native(), C.OSTREE_REPO_LIST_OBJECTS_ALL, (**C.GHashTable)(objects.Ptr()), (*C.GCancellable)(cancellable.Ptr()), &cerr))) {
return generateError(cerr)
}
C.g_hash_table_iter_init((*C.GHashTableIter)(hashIter.Ptr()), (*C.GHashTable)(objects.Ptr()))
for C.g_hash_table_iter_next((*C.GHashTableIter)(hashIter.Ptr()), &key, &value) != 0 {
var serializedKey *glib.GVariant
defer C.free(unsafe.Pointer(serializedKey))
var checksum *C.char
defer C.free(unsafe.Pointer(checksum))
var objType C.OstreeObjectType
var commitTimestamp uint64
var commit *glib.GVariant = nil
C.ostree_object_name_deserialize((*C.GVariant)(serializedKey.Ptr()), &checksum, &objType)
if objType != C.OSTREE_OBJECT_TYPE_COMMIT {
continue
}
cerr = nil
if !glib.GoBool(glib.GBoolean(C.ostree_repo_load_variant(repo.native(), C.OSTREE_OBJECT_TYPE_COMMIT, checksum, (**C.GVariant)(commit.Ptr()), &cerr))) {
return generateError(cerr)
}
commitTimestamp = (uint64)(C.ostree_commit_get_timestamp((*C.GVariant)(commit.Ptr())))
if commitTimestamp < (uint64)(date.Unix()) {
cerr = nil
if pruneOpts.StaticDeltasOnly != 0 {
if !glib.GoBool(glib.GBoolean(C.ostree_repo_prune_static_deltas(repo.native(), checksum, (*C.GCancellable)(cancellable.Ptr()), &cerr))) {
return generateError(cerr)
}
} else {
if !glib.GoBool(glib.GBoolean(C.ostree_repo_delete_object(repo.native(), C.OSTREE_OBJECT_TYPE_COMMIT, checksum, (*C.GCancellable)(cancellable.Ptr()), &cerr))) {
return generateError(cerr)
}
}
}
}
return nil
}
|