Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Tree: adf0f088e2
274 lines (227 sloc) 6.173 kB
package main
import (
"bufio"
"container/list"
"exec"
"flag"
"fmt"
"io"
"os"
"strings"
"syscall"
)
const (
PKGCACHE = "/var/cache/pacman/pkg"
BUFSIZ = 4096 /* Assume 4k pages */
)
type archPkg struct {
Name string
Version string
Arch string
Ext string
}
/* flags */
var (
help = flag.Bool("h", false, "display this help message")
cachePath = flag.String("c", PKGCACHE, "path to package cache")
destPath = flag.String("m", "", "move files to this location")
keep = flag.Int("k", 3, "number of packages to keep")
delete = flag.Bool("d", false, "delete packages over keep limit")
quiet = flag.Bool("q", false, "don't print deletion candidates")
)
/* global listing of package groups */
var pkgList = map[string]*list.List{}
func addPkgSorted(lst *list.List, pkg *archPkg) {
for p := lst.Front(); p != nil; p = p.Next() {
ele := (p.Value).(*archPkg)
/* vercmp returns -1, which underflows */
if vercmp(pkg.Version, ele.Version) == 255 {
lst.InsertBefore(pkg, p)
return
}
}
lst.PushBack(pkg)
}
func parsePkg(file string) *archPkg {
parts := strings.Split(file, "-", -1)
n := len(parts)
if n < 3 {
return nil
}
pkgname := strings.Join(parts[0:n-3], "-")
if pkgname == "" {
return nil
}
pkgver := strings.Join(parts[n-3:n-1], "-")
ext := strings.Split(parts[n-1], ".", 2)
return &archPkg{pkgname, pkgver, ext[0], ext[1]}
}
func copyFile(src io.Reader, dst *bufio.Writer) os.Error {
defer dst.Flush()
buf := make([]byte, BUFSIZ)
for {
nr, err := src.Read(buf)
if err != nil && err != os.EOF {
return err
}
if nr > 0 {
_, err = dst.Write(buf[0:nr])
if err != nil {
fmt.Fprintln(os.Stderr, err.String())
break
}
} else {
return nil
}
}
return nil
}
func movePkg(pkg *archPkg) os.Error {
filename := fmt.Sprintf("%s-%s-%s.%s", pkg.Name, pkg.Version, pkg.Arch, pkg.Ext)
srcPath := fmt.Sprintf("%s/%s", *cachePath, filename)
/* try to rename, first */
errno := syscall.Rename(srcPath, fmt.Sprintf("%s/%s", *destPath, filename))
switch errno {
case syscall.EXDEV:
src, err := os.Open(srcPath, os.O_RDONLY, 0666)
if err != nil {
fmt.Fprintf(os.Stderr, "%s: %s\n", os.Args[0], err.String())
os.Exit(1)
}
defer src.Close()
sfi, err := src.Stat()
dst, err := os.Open(fmt.Sprintf("%s/%s", *destPath, filename) , os.O_WRONLY|os.O_CREAT|os.O_TRUNC, sfi.Permission())
if err != nil {
fmt.Fprintf(os.Stderr, "%s: %s\n", os.Args[0], err.String())
os.Exit(1)
}
defer dst.Close()
err = copyFile(src, bufio.NewWriter(dst))
if err == nil {
return os.Remove(srcPath)
}
return err /* different from err outside if */
}
return os.Errno(errno)
}
func addPkgToMap(pkg *archPkg) {
pkgKey := fmt.Sprintf("%s$%s", pkg.Name, pkg.Arch)
if _, ok := pkgList[pkgKey]; !ok {
pkgList[pkgKey] = list.New()
pkgList[pkgKey].PushFront(pkg)
} else {
addPkgSorted(pkgList[pkgKey], pkg)
}
}
func prune() (int, int) {
pkgCount, grpCount := 0, 0
for _, group := range pkgList {
groupSz := group.Len()
if groupSz <= *keep {
continue
}
for i := 0; i < groupSz-*keep; i++ {
p := (group.Front().Value).(*archPkg)
filename := fmt.Sprintf("%s-%s-%s.%s", p.Name, p.Version, p.Arch, p.Ext)
fullpath := fmt.Sprintf("%s/%s", *cachePath, filename)
switch {
case *delete:
if !*quiet {
fmt.Println("Deleting " + filename)
}
err := os.Remove(fullpath)
if err != nil {
fmt.Fprintf(os.Stderr, "error deleting %s: %s\n", fullpath, err.String())
os.Exit(2)
}
case *destPath != "":
if !*quiet {
fmt.Println("Moving " + filename)
}
err := movePkg(p)
if err != nil {
fmt.Fprintf(os.Stderr, "error moving %s: %s\n", fullpath, err.String())
os.Exit(2)
}
default:
if !*quiet {
fmt.Println("Would prune: " + filename)
}
}
pkgCount++
group.Remove(group.Front())
}
grpCount++
}
return grpCount, pkgCount
}
/* TODO: Reimplement alpm_pkg_vercmp() */
func vercmp(a, b string) int {
p, _ := exec.Run("vercmp", []string{"vercmp", a, b}, os.Environ(), "/usr/bin", 0, exec.DevNull, exec.DevNull)
w, _ := p.Wait(0)
return w.ExitStatus()
}
func init() {
flag.Parse()
flag.Usage = func() {
fmt.Println("usage: gobble [flags]")
flag.PrintDefaults()
os.Exit(0)
}
if *help {
flag.Usage()
}
/* Sanity checks */
if *delete && *destPath != "" {
fmt.Fprintln(os.Stderr, "error: cannot delete and move in same operation")
os.Exit(5)
}
if (*delete || *destPath != "") && os.Getuid() != 0 {
fmt.Fprintln(os.Stderr, "error: cannot delete/move as non-root user")
os.Exit(5)
}
if _, err := os.Stat(*destPath); *destPath != "" && err != nil {
fmt.Fprintf(os.Stderr, "error: %s\n", err.String())
os.Exit(5)
}
if *keep <= 0 {
fmt.Fprintln(os.Stderr, "error: keep must be a positive number")
os.Exit(5)
}
}
func main() {
d, err := os.Open(*cachePath, os.O_RDONLY, 0666)
if err != nil {
fmt.Fprintln(os.Stderr, err.String())
os.Exit(1)
}
defer d.Close()
fi, err := d.Readdir(-1)
if err != nil {
fmt.Fprintln(os.Stderr, err.String())
os.Exit(1)
}
if !*delete && *destPath == "" {
fmt.Println("Performing dry-run...")
}
found := 0
for _, pkg := range fi {
if p := parsePkg(pkg.Name); p != nil {
addPkgToMap(p)
found++
}
}
switch found {
case 0:
fmt.Fprintln(os.Stderr, "error: No candidate package groups found. Are you sure this is a valid pacman cache?")
os.Exit(0)
default:
fmt.Printf("%d packages in %d groups found in %s\n", found, len(pkgList), *cachePath)
}
g, p := prune()
if *delete || *destPath != "" {
fmt.Printf("%d files pruned from %d package groups.\n", p, g)
} else {
fmt.Printf("%d files would be pruned from %d package groups.\n", p, g)
}
}
Jump to Line
Something went wrong with that request. Please try again.