yay/vendor/github.com/jguer/go-alpm/conf.go

289 lines
6.5 KiB
Go
Raw Normal View History

2017-07-14 19:03:54 +02:00
// 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"
"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
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 string
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 {
return conf, nil
}
rdr = rdrStack[l-2]
rdrStack = rdrStack[:l-1]
default:
return conf, err
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":
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)
return conf, err
}
rdr = newConfReader(f)
rdrStack = append(rdrStack, rdr)
continue lineloop
}
if currentSection != "options" {
err = fmt.Errorf("option %s outside of [options] section, at line %d",
line.Name, rdr.Lineno)
return conf, err
}
// 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...)
}
}
}
}
}
func (conf *PacmanConfig) SetDefaults() {
conf.RootDir = "/"
conf.DBPath = "/var/lib/pacman"
}
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)
}
}
return h, nil
}