conf.go 6.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288
  1. // conf.go - Functions for pacman.conf parsing.
  2. //
  3. // Copyright (c) 2013 The go-alpm Authors
  4. //
  5. // MIT Licensed. See LICENSE for details.
  6. package alpm
  7. import (
  8. "bufio"
  9. "bytes"
  10. "fmt"
  11. "io"
  12. "os"
  13. "reflect"
  14. "strings"
  15. "syscall"
  16. )
  17. type PacmanOption uint
  18. const (
  19. ConfUseSyslog PacmanOption = 1 << iota
  20. ConfColor
  21. ConfTotalDownload
  22. ConfCheckSpace
  23. ConfVerbosePkgLists
  24. ConfILoveCandy
  25. )
  26. var optionsMap = map[string]PacmanOption{
  27. "UseSyslog": ConfUseSyslog,
  28. "Color": ConfColor,
  29. "TotalDownload": ConfTotalDownload,
  30. "CheckSpace": ConfCheckSpace,
  31. "VerbosePkgLists": ConfVerbosePkgLists,
  32. "ILoveCandy": ConfILoveCandy,
  33. }
  34. // PacmanConfig is a type for holding pacman options parsed from pacman
  35. // configuration data passed to ParseConfig.
  36. type PacmanConfig struct {
  37. RootDir string
  38. DBPath string
  39. CacheDir []string
  40. GPGDir string
  41. LogFile string
  42. HoldPkg []string
  43. IgnorePkg []string
  44. IgnoreGroup []string
  45. Include []string
  46. Architecture string
  47. XferCommand string
  48. NoUpgrade []string
  49. NoExtract []string
  50. CleanMethod string
  51. SigLevel SigLevel
  52. LocalFileSigLevel SigLevel
  53. RemoteFileSigLevel SigLevel
  54. UseDelta string
  55. Options PacmanOption
  56. Repos []RepoConfig
  57. }
  58. // RepoConfig is a type that stores the signature level of a repository
  59. // specified in the pacman config file.
  60. type RepoConfig struct {
  61. Name string
  62. SigLevel SigLevel
  63. Servers []string
  64. }
  65. // Constants for pacman configuration parsing
  66. const (
  67. tokenSection = iota
  68. tokenKey
  69. tokenComment
  70. )
  71. type iniToken struct {
  72. Type uint
  73. Name string
  74. Values []string
  75. }
  76. type confReader struct {
  77. *bufio.Reader
  78. Lineno uint
  79. }
  80. // newConfReader reads from the io.Reader if it is buffered and returns a
  81. // confReader containing the number of bytes read and 0 for the first line. If
  82. // r is not a buffered reader, a new buffered reader is created using r as its
  83. // input and returned.
  84. func newConfReader(r io.Reader) confReader {
  85. if buf, ok := r.(*bufio.Reader); ok {
  86. return confReader{buf, 0}
  87. }
  88. buf := bufio.NewReader(r)
  89. return confReader{buf, 0}
  90. }
  91. func (rdr *confReader) ParseLine() (tok iniToken, err error) {
  92. line, overflow, err := rdr.ReadLine()
  93. switch {
  94. case err != nil:
  95. return
  96. case overflow:
  97. err = fmt.Errorf("line %d too long", rdr.Lineno)
  98. return
  99. }
  100. rdr.Lineno++
  101. line = bytes.TrimSpace(line)
  102. if len(line) == 0 {
  103. tok.Type = tokenComment
  104. return
  105. }
  106. switch line[0] {
  107. case '#':
  108. tok.Type = tokenComment
  109. return
  110. case '[':
  111. closing := bytes.IndexByte(line, ']')
  112. if closing < 0 {
  113. err = fmt.Errorf("missing ']' is section name at line %d", rdr.Lineno)
  114. return
  115. }
  116. tok.Name = string(line[1:closing])
  117. if closing+1 < len(line) {
  118. err = fmt.Errorf("trailing characters %q after section name %s",
  119. line[closing+1:], tok.Name)
  120. return
  121. }
  122. return
  123. default:
  124. tok.Type = tokenKey
  125. if idx := bytes.IndexByte(line, '='); idx >= 0 {
  126. optname := bytes.TrimSpace(line[:idx])
  127. values := bytes.Split(line[idx+1:], []byte{' '})
  128. tok.Name = string(optname)
  129. tok.Values = make([]string, 0, len(values))
  130. for _, word := range values {
  131. word = bytes.TrimSpace(word)
  132. if len(word) > 0 {
  133. tok.Values = append(tok.Values, string(word))
  134. }
  135. }
  136. } else {
  137. // boolean option
  138. tok.Name = string(line)
  139. tok.Values = nil
  140. }
  141. return
  142. }
  143. }
  144. func ParseConfig(r io.Reader) (conf PacmanConfig, err error) {
  145. rdr := newConfReader(r)
  146. rdrStack := []confReader{rdr}
  147. conf.SetDefaults()
  148. confReflect := reflect.ValueOf(&conf).Elem()
  149. var currentSection string
  150. var curRepo *RepoConfig
  151. lineloop:
  152. for {
  153. line, err := rdr.ParseLine()
  154. // fmt.Printf("%+v\n", line)
  155. switch err {
  156. case io.EOF:
  157. // pop reader stack.
  158. l := len(rdrStack)
  159. if l == 1 {
  160. return conf, nil
  161. }
  162. rdr = rdrStack[l-2]
  163. rdrStack = rdrStack[:l-1]
  164. default:
  165. return conf, err
  166. case nil:
  167. // Ok.
  168. }
  169. switch line.Type {
  170. case tokenComment:
  171. case tokenSection:
  172. currentSection = line.Name
  173. if currentSection != "options" {
  174. conf.Repos = append(conf.Repos, RepoConfig{})
  175. curRepo = &conf.Repos[len(conf.Repos)-1]
  176. curRepo.Name = line.Name
  177. }
  178. case tokenKey:
  179. switch line.Name {
  180. case "SigLevel":
  181. // TODO: implement SigLevel parsing.
  182. continue lineloop
  183. case "Server":
  184. curRepo.Servers = append(curRepo.Servers, line.Values...)
  185. continue lineloop
  186. case "Include":
  187. f, err := os.Open(line.Values[0])
  188. if err != nil {
  189. err = fmt.Errorf("error while processing Include directive at line %d: %s",
  190. rdr.Lineno, err)
  191. return conf, err
  192. }
  193. rdr = newConfReader(f)
  194. rdrStack = append(rdrStack, rdr)
  195. continue lineloop
  196. }
  197. if currentSection != "options" {
  198. err = fmt.Errorf("option %s outside of [options] section, at line %d",
  199. line.Name, rdr.Lineno)
  200. return conf, err
  201. }
  202. // main options.
  203. if opt, ok := optionsMap[line.Name]; ok {
  204. // boolean option.
  205. conf.Options |= opt
  206. } else {
  207. // key-value option.
  208. fld := confReflect.FieldByName(line.Name)
  209. if !fld.IsValid() || !fld.CanAddr() {
  210. _ = fmt.Errorf("unknown option at line %d: %s", rdr.Lineno, line.Name)
  211. continue
  212. }
  213. switch fieldP := fld.Addr().Interface().(type) {
  214. case *string:
  215. // single valued option.
  216. *fieldP = strings.Join(line.Values, " ")
  217. case *[]string:
  218. //many valued option.
  219. *fieldP = append(*fieldP, line.Values...)
  220. }
  221. }
  222. }
  223. }
  224. }
  225. func (conf *PacmanConfig) SetDefaults() {
  226. conf.RootDir = "/"
  227. conf.DBPath = "/var/lib/pacman"
  228. }
  229. func getArch() (string, error) {
  230. var uname syscall.Utsname
  231. err := syscall.Uname(&uname)
  232. if err != nil {
  233. return "", err
  234. }
  235. var arch [65]byte
  236. for i, c := range uname.Machine {
  237. if c == 0 {
  238. return string(arch[:i]), nil
  239. }
  240. arch[i] = byte(c)
  241. }
  242. return string(arch[:]), nil
  243. }
  244. func (conf *PacmanConfig) CreateHandle() (*Handle, error) {
  245. h, err := Init(conf.RootDir, conf.DBPath)
  246. if err != nil {
  247. return nil, err
  248. }
  249. if conf.Architecture == "auto" {
  250. conf.Architecture, err = getArch()
  251. if err != nil {
  252. return nil, fmt.Errorf("architecture is 'auto' but couldn't uname()")
  253. }
  254. }
  255. for _, repoconf := range conf.Repos {
  256. // TODO: set SigLevel
  257. db, err := h.RegisterSyncDb(repoconf.Name, 0)
  258. if err == nil {
  259. for i, addr := range repoconf.Servers {
  260. addr = strings.Replace(addr, "$repo", repoconf.Name, -1)
  261. addr = strings.Replace(addr, "$arch", conf.Architecture, -1)
  262. repoconf.Servers[i] = addr
  263. }
  264. db.SetServers(repoconf.Servers)
  265. }
  266. }
  267. return h, nil
  268. }