mirror of
https://github.com/Jguer/yay.git
synced 2024-11-06 17:17:22 +01:00
402 lines
8.7 KiB
Go
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
|
|
}
|