yay/vendor/github.com/jguer/go-alpm/conf.go
2018-06-19 11:00:47 +01:00

402 lines
8.7 KiB
Go

// conf.go - Functions for pacman.conf parsing.
//
// Copyright (c) 2013 The go-alpm Authors
//
// MIT Licensed. See LICENSE for details.
package alpm
import (
"bufio"
"bytes"
"fmt"
"io"
"os"
"reflect"
"strconv"
"strings"
"syscall"
)
type PacmanOption uint
const (
ConfUseSyslog PacmanOption = 1 << iota
ConfColor
ConfTotalDownload
ConfCheckSpace
ConfVerbosePkgLists
ConfILoveCandy
)
var optionsMap = map[string]PacmanOption{
"UseSyslog": ConfUseSyslog,
"Color": ConfColor,
"TotalDownload": ConfTotalDownload,
"CheckSpace": ConfCheckSpace,
"VerbosePkgLists": ConfVerbosePkgLists,
"ILoveCandy": ConfILoveCandy,
}
// PacmanConfig is a type for holding pacman options parsed from pacman
// configuration data passed to ParseConfig.
type PacmanConfig struct {
RootDir string
DBPath string
CacheDir []string
HookDir []string
GPGDir string
LogFile string
HoldPkg []string
IgnorePkg []string
IgnoreGroup []string
Include []string
Architecture string
XferCommand string
NoUpgrade []string
NoExtract []string
CleanMethod []string
SigLevel SigLevel
LocalFileSigLevel SigLevel
RemoteFileSigLevel SigLevel
UseDelta float64
Options PacmanOption
Repos []RepoConfig
}
// RepoConfig is a type that stores the signature level of a repository
// specified in the pacman config file.
type RepoConfig struct {
Name string
SigLevel SigLevel
Servers []string
}
// Constants for pacman configuration parsing
const (
tokenSection = iota
tokenKey
tokenComment
)
type iniToken struct {
Type uint
Name string
Values []string
}
type confReader struct {
*bufio.Reader
Lineno uint
}
// newConfReader reads from the io.Reader if it is buffered and returns a
// confReader containing the number of bytes read and 0 for the first line. If
// r is not a buffered reader, a new buffered reader is created using r as its
// input and returned.
func newConfReader(r io.Reader) confReader {
if buf, ok := r.(*bufio.Reader); ok {
return confReader{buf, 0}
}
buf := bufio.NewReader(r)
return confReader{buf, 0}
}
func (rdr *confReader) ParseLine() (tok iniToken, err error) {
line, overflow, err := rdr.ReadLine()
switch {
case err != nil:
return
case overflow:
err = fmt.Errorf("line %d too long", rdr.Lineno)
return
}
rdr.Lineno++
line = bytes.TrimSpace(line)
if len(line) == 0 {
tok.Type = tokenComment
return
}
switch line[0] {
case '#':
tok.Type = tokenComment
return
case '[':
closing := bytes.IndexByte(line, ']')
if closing < 0 {
err = fmt.Errorf("missing ']' is section name at line %d", rdr.Lineno)
return
}
tok.Name = string(line[1:closing])
if closing+1 < len(line) {
err = fmt.Errorf("trailing characters %q after section name %s",
line[closing+1:], tok.Name)
return
}
return
default:
tok.Type = tokenKey
if idx := bytes.IndexByte(line, '='); idx >= 0 {
optname := bytes.TrimSpace(line[:idx])
values := bytes.Split(line[idx+1:], []byte{' '})
tok.Name = string(optname)
tok.Values = make([]string, 0, len(values))
for _, word := range values {
word = bytes.TrimSpace(word)
if len(word) > 0 {
tok.Values = append(tok.Values, string(word))
}
}
} else {
// boolean option
tok.Name = string(line)
tok.Values = nil
}
return
}
}
func ParseConfig(r io.Reader) (conf PacmanConfig, err error) {
rdr := newConfReader(r)
rdrStack := []confReader{rdr}
conf.SetDefaults()
confReflect := reflect.ValueOf(&conf).Elem()
var currentSection string
var curRepo *RepoConfig
lineloop:
for {
line, err := rdr.ParseLine()
// fmt.Printf("%+v\n", line)
switch err {
case io.EOF:
// pop reader stack.
l := len(rdrStack)
if l == 1 {
break lineloop
}
rdr = rdrStack[l-2]
rdrStack = rdrStack[:l-1]
default:
break lineloop
case nil:
// Ok.
}
switch line.Type {
case tokenComment:
case tokenSection:
currentSection = line.Name
if currentSection != "options" {
conf.Repos = append(conf.Repos, RepoConfig{})
curRepo = &conf.Repos[len(conf.Repos)-1]
curRepo.Name = line.Name
}
case tokenKey:
switch line.Name {
case "SigLevel":
// TODO: implement SigLevel parsing.
continue lineloop
case "Server":
curRepo.Servers = append(curRepo.Servers, line.Values...)
continue lineloop
case "Include":
conf.Include = append(conf.Include, line.Values[0])
f, err := os.Open(line.Values[0])
if err != nil {
err = fmt.Errorf("error while processing Include directive at line %d: %s",
rdr.Lineno, err)
break lineloop
}
rdr = newConfReader(f)
rdrStack = append(rdrStack, rdr)
continue lineloop
case "UseDelta":
if len(line.Values) > 0 {
deltaRatio, err := strconv.ParseFloat(line.Values[0], 64)
if err != nil {
break lineloop
}
conf.UseDelta = deltaRatio
}
continue lineloop
}
if currentSection != "options" {
err = fmt.Errorf("option %s outside of [options] section, at line %d",
line.Name, rdr.Lineno)
break lineloop
}
// main options.
if opt, ok := optionsMap[line.Name]; ok {
// boolean option.
conf.Options |= opt
} else {
// key-value option.
fld := confReflect.FieldByName(line.Name)
if !fld.IsValid() || !fld.CanAddr() {
_ = fmt.Errorf("unknown option at line %d: %s", rdr.Lineno, line.Name)
continue
}
switch fieldP := fld.Addr().Interface().(type) {
case *string:
// single valued option.
*fieldP = strings.Join(line.Values, " ")
case *[]string:
//many valued option.
*fieldP = append(*fieldP, line.Values...)
}
}
}
}
if len(conf.CleanMethod) == 0 {
conf.CleanMethod = []string{"KeepInstalled"}
}
if len(conf.CacheDir) == 0 {
conf.CacheDir = []string{"/var/cache/pacman/pkg/"} //should only be set if the config does not specify this
}
return conf, err
}
func (conf *PacmanConfig) SetDefaults() {
conf.RootDir = "/"
conf.DBPath = "/var/lib/pacman"
conf.DBPath = "/var/lib/pacman/"
conf.HookDir = []string{"/etc/pacman.d/hooks/"} //should be added to whatever the config states
conf.GPGDir = "/etc/pacman.d/gnupg/"
conf.LogFile = "/var/log/pacman.log"
conf.UseDelta = 0.7
conf.SigLevel = SigPackage | SigPackageOptional | SigDatabase | SigDatabaseOptional
conf.LocalFileSigLevel = SigUseDefault
conf.RemoteFileSigLevel = SigUseDefault
}
func getArch() (string, error) {
var uname syscall.Utsname
err := syscall.Uname(&uname)
if err != nil {
return "", err
}
var arch [65]byte
for i, c := range uname.Machine {
if c == 0 {
return string(arch[:i]), nil
}
arch[i] = byte(c)
}
return string(arch[:]), nil
}
func (conf *PacmanConfig) CreateHandle() (*Handle, error) {
h, err := Init(conf.RootDir, conf.DBPath)
if err != nil {
return nil, err
}
if conf.Architecture == "auto" {
conf.Architecture, err = getArch()
if err != nil {
return nil, fmt.Errorf("architecture is 'auto' but couldn't uname()")
}
}
for _, repoconf := range conf.Repos {
// TODO: set SigLevel
db, err := h.RegisterSyncDb(repoconf.Name, 0)
if err == nil {
for i, addr := range repoconf.Servers {
addr = strings.Replace(addr, "$repo", repoconf.Name, -1)
addr = strings.Replace(addr, "$arch", conf.Architecture, -1)
repoconf.Servers[i] = addr
}
db.SetServers(repoconf.Servers)
}
}
err = h.SetCacheDirs(conf.CacheDir...)
if err != nil {
return nil, err
}
// add hook directories 1-by-1 to avoid overwriting the system directory
for _, dir := range conf.HookDir {
err = h.AddHookDir(dir)
if err != nil {
return nil, err
}
}
err = h.SetGPGDir(conf.GPGDir)
if err != nil {
return nil, err
}
err = h.SetLogFile(conf.LogFile)
if err != nil {
return nil, err
}
err = h.SetIgnorePkgs(conf.IgnorePkg...)
if err != nil {
return nil, err
}
err = h.SetIgnoreGroups(conf.IgnoreGroup...)
if err != nil {
return nil, err
}
err = h.SetArch(conf.Architecture)
if err != nil {
return nil, err
}
h.SetNoUpgrades(conf.NoUpgrade...)
if err != nil {
return nil, err
}
h.SetNoExtracts(conf.NoExtract...)
if err != nil {
return nil, err
}
err = h.SetDefaultSigLevel(conf.SigLevel)
if err != nil {
return nil, err
}
err = h.SetLocalFileSigLevel(conf.LocalFileSigLevel)
if err != nil {
return nil, err
}
err = h.SetRemoteFileSigLevel(conf.RemoteFileSigLevel)
if err != nil {
return nil, err
}
err = h.SetDeltaRatio(conf.UseDelta)
if err != nil {
return nil, err
}
err = h.SetUseSyslog(conf.Options&ConfUseSyslog > 0)
if err != nil {
return nil, err
}
err = h.SetCheckSpace(conf.Options&ConfCheckSpace > 0)
if err != nil {
return nil, err
}
return h, nil
}