mirror of
https://github.com/Jguer/yay.git
synced 2024-11-07 01:27:21 +01:00
289 lines
6.5 KiB
Go
289 lines
6.5 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"
|
||
|
"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
|
||
|
}
|