123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428 |
- // 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
- Usage Usage
- 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 "Usage":
- for _, usage := range line.Values {
- switch usage {
- case "Sync":
- curRepo.Usage |= UsageSync
- case "Search":
- curRepo.Usage |= UsageSearch
- case "Install":
- curRepo.Usage |= UsageInstall
- case "Upgrade":
- curRepo.Usage |= UsageUpgrade
- case "All":
- curRepo.Usage |= UsageAll
- default:
- err = fmt.Errorf("unknown option at line %d: %s", rdr.Lineno, line.Name)
- break 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
- }
- for n, _ := range conf.Repos {
- repo := &conf.Repos[n]
- if repo.Usage == 0 {
- repo.Usage = UsageAll
- }
- }
- 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)
- db.SetUsage(repoconf.Usage)
- }
- }
- 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
- }
|