Arch Linux helpers and parsers
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

405 lines
10 KiB

3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
  1. package archlinux
  2. import (
  3. "encoding/base64"
  4. "encoding/hex"
  5. "encoding/json"
  6. "errors"
  7. "fmt"
  8. "io"
  9. "net/url"
  10. "reflect"
  11. "strconv"
  12. "strings"
  13. "time"
  14. "maze.io/archlinux.v0/tarball"
  15. "maze.io/archlinux.v0/version"
  16. )
  17. // Errors
  18. var (
  19. ErrPackageArchiveNameInvalid = errors.New("archlinux: invalid package archive name")
  20. ErrPackageNameInvalid = errors.New("archlinux: invalid package name")
  21. ErrPackageNoChecksums = errors.New("archlinux: package has no checksums")
  22. ErrNoPackageName = errors.New("archlinux: name is missing")
  23. ErrNoPackageVersion = errors.New("archlinux: version is missing")
  24. ErrNoPackageRelease = errors.New("archlinux: release is missing")
  25. )
  26. // Package describes an Arch Linux package
  27. type Package struct {
  28. // Filename is the name of the underlying archive or PKGBUILD
  29. Filename string
  30. // Name of the package
  31. Name string `pkginfo:"pkgname" srcinfo:"pkgname"`
  32. // Base of the package, if this package is a member of a split package
  33. Base string `pkginfo:"pkgbase" srcinfo:"pkgbase"`
  34. // Version number
  35. Version string `pkgdesc:"VERSION,evr" pkginfo:"pkgver,evr" srcinfo:"pkgver"`
  36. // Release number
  37. Release string `pkgdesc:"-" pkginfo:"-" srcinfo:"pkgrel"`
  38. // Epoch
  39. Epoch int64 `pkgdesc:"-" pkginfo:"-" srcinfo:"epoch"`
  40. // Description of the package
  41. Description string `pkgdesc:"DESC" pkginfo:"pkgdesc" srcinfo:"pkgdesc"`
  42. // Arch slice of supported architectures
  43. Arch []string
  44. // URL of the project
  45. URL *url.URL
  46. // License one or more licenses
  47. License []string
  48. // Groups are package groups
  49. Groups []string
  50. // Depends are the packages this package depends on
  51. Depends []string `pkgdesc:"DEPENDS" pkginfo:"depend" srcinfo:"depends"`
  52. // MakeDepends are the packages this package depends on for building
  53. MakeDepends []string `pkgdesc:"MAKEDEPENDS" pkginfo:"makedepend" srcinfo:"makedepends"`
  54. // CheckDepends are the packages this package depends on for running the
  55. // checks during build
  56. CheckDepends []string `pkgdesc:"CHECKDEPENDS" pkginfo:"checkdepend" srcinfo:"checkdepends"`
  57. // OptionalDepends are the packages this package recommends having
  58. // installed. Note that these values may contain comments, indicated by
  59. // "spec: comment" (the separator is ": ")
  60. OptionalDepends []string `pkgdesc:"OPTDEPENDS" pkginfo:"optdepend" srcinfo:"optdepends"`
  61. // Provides are virtual package names provided by this package
  62. Provides []string
  63. // Conflicts are packages that conflict with this package
  64. Conflicts []string
  65. // Replaces are packages that will be replaced if this package is installed
  66. Replaces []string
  67. // Backup are the files that will be backed up to .pacsave if they exist on
  68. // the file system if this package is installed
  69. Backup []string
  70. // MD5 is the MD-5 hex digest of the package file
  71. MD5 []byte `pkgdesc:"MD5SUM,hex" pkginfo:"md5sum,hex"`
  72. // SHA1 is the SHA-1 hex digest of the package file
  73. SHA1 []byte `pkgdesc:"SHA1SUM,hex" pkginfo:"sha1sum,hex"`
  74. // SHA256 is the SHA-256 hex digest of the package file
  75. SHA256 []byte `pkgdesc:"SHA256SUM,hex" pkginfo:"sha256sum,hex"`
  76. // SHA384 is the SHA-384 hex digest of the package file
  77. SHA384 []byte `pkgdesc:"SHA384SUM,hex" pkginfo:"sha384sum,hex"`
  78. // SHA512 is the SHA-512 hex digest of the package file
  79. SHA512 []byte `pkgdesc:"SHA512SUM,hex" pkginfo:"sha512sum,hex"`
  80. // PGPSignature is the deteched PGP signature base64 encoded digest of the
  81. // package file
  82. PGPSignature []byte `pkgdesc:"PGPSIG,base64" pkginfo:"pgpsig,base64"`
  83. // Size of the installed package
  84. Size int64 `pkgdesc:"ISIZE" pkginfo:"size"`
  85. // CompressedSize of the archive
  86. CompressedSize int64 `pkgdesc:"CSIZE" pkginfo:"-"`
  87. // Options are the makepkg build options used for this package
  88. Options []string
  89. // BuildEnv are the options during the build
  90. BuildEnv []string
  91. // BuildDate is the date the package was built
  92. BuildDate time.Time
  93. // Packager name and email
  94. Packager string
  95. // Maintainers for this package
  96. Maintainers []string
  97. // Contributors to this package
  98. Contributors []string
  99. // Files is a list of files provided by the package
  100. Files []string
  101. }
  102. // Common strings
  103. const (
  104. unknown = "unknown"
  105. base64Encoding = "base64"
  106. evrEncoding = "evr"
  107. hexEncoding = "hex"
  108. )
  109. func (pkg Package) String() string {
  110. var s = make([]string, 3)
  111. if pkg.Name != "" {
  112. s[0] = pkg.Name
  113. } else {
  114. s[0] = unknown
  115. }
  116. if pkg.Version != "" {
  117. s[1] = pkg.Version
  118. } else {
  119. s[1] = unknown
  120. }
  121. if pkg.Release != "" {
  122. s[2] = pkg.Release
  123. } else {
  124. s[2] = unknown
  125. }
  126. return strings.Join(s, "-")
  127. }
  128. // MarshalJSON converts a Package to JSON encoded bytes
  129. func (pkg *Package) MarshalJSON() ([]byte, error) {
  130. type Alias Package
  131. return json.Marshal(&struct {
  132. BuildDate int64
  133. URL string
  134. *Alias
  135. }{
  136. BuildDate: pkg.BuildDate.Unix(),
  137. URL: pkg.URL.String(),
  138. Alias: (*Alias)(pkg),
  139. })
  140. }
  141. // UnmarshalJSON converts JSON encoded bytes to a Package
  142. func (pkg *Package) UnmarshalJSON(data []byte) error {
  143. type Alias Package
  144. var (
  145. aux = &struct {
  146. BuildDate int64
  147. URL string
  148. *Alias
  149. }{
  150. Alias: (*Alias)(pkg),
  151. }
  152. err error
  153. )
  154. if err = json.Unmarshal(data, &aux); err != nil {
  155. return err
  156. }
  157. if pkg.URL, err = url.Parse(aux.URL); err != nil {
  158. return err
  159. }
  160. pkg.BuildDate = time.Unix(aux.BuildDate, 0)
  161. return nil
  162. }
  163. // ReadPackage reads a package description from a package tarball.
  164. func ReadPackage(r io.Reader) (*Package, error) {
  165. t, err := tarball.NewReader(r)
  166. if err != nil {
  167. return nil, err
  168. }
  169. pkg := new(Package)
  170. if n, ok := r.(namer); ok {
  171. pkg.Filename = n.Name()
  172. }
  173. // Scan the contents of the tarball
  174. for {
  175. h, err := t.Next()
  176. if err == io.EOF {
  177. return pkg, nil
  178. } else if err != nil {
  179. return nil, err
  180. }
  181. switch strings.TrimLeft(h.Name, "/") {
  182. case ".BUILDINFO":
  183. if err = pkg.readBuildInfo(t); err != nil {
  184. return nil, err
  185. }
  186. case ".PKGINFO":
  187. if err = pkg.readPackageInfo(t); err != nil {
  188. return nil, err
  189. }
  190. default:
  191. pkg.Files = append(pkg.Files, strings.TrimLeft(h.Name, "/"))
  192. }
  193. }
  194. }
  195. func parsePackageFields(parseField func(reflect.StructField) (string, string), v ...interface{}) (values map[string]reflect.Value, encodings map[string]string) {
  196. values = make(map[string]reflect.Value)
  197. encodings = make(map[string]string)
  198. for _, o := range v {
  199. var (
  200. value = reflect.Indirect(reflect.ValueOf(o))
  201. typ = value.Type()
  202. )
  203. for i := 0; i < value.NumField(); i++ {
  204. var (
  205. fieldValue = value.Field(i)
  206. name, encoding = parseField(typ.Field(i))
  207. )
  208. if name == "" {
  209. // No name means ignored field
  210. continue
  211. }
  212. values[name] = fieldValue
  213. encodings[name] = encoding
  214. }
  215. }
  216. return
  217. }
  218. // setPackageValue updates a Package-like struct field with the corresponding
  219. // value; the struct is passed as interface{} o; for evr decoding it will
  220. // iterate over the struct fields and attempts to locate "Release" string and
  221. // "Epoch" int and update them accordingly
  222. func setPackageValue(o interface{}, v reflect.Value, value, encoding string) (err error) {
  223. switch v.Interface().(type) {
  224. case string:
  225. switch encoding {
  226. case "":
  227. v.SetString(value)
  228. case "evr":
  229. p := version.Parse(value)
  230. if p.Version == "" {
  231. return ErrNoPackageVersion
  232. }
  233. v.SetString(p.Version)
  234. ov := reflect.Indirect(reflect.ValueOf(o))
  235. ot := ov.Type()
  236. for i := 0; i < ov.NumField(); i++ {
  237. switch ot.Field(i).Name {
  238. case "Release":
  239. ov.Field(i).SetString(p.Release)
  240. case "Epoch":
  241. if p.Epoch == "" || p.Epoch == "0" {
  242. ov.Field(i).SetInt(0)
  243. } else {
  244. var epoch int64
  245. if epoch, err = strconv.ParseInt(p.Epoch, 10, 64); err != nil {
  246. return fmt.Errorf("archlinux: invalid epoch %q: %v", p.Epoch, err)
  247. }
  248. ov.Field(i).SetInt(epoch)
  249. }
  250. }
  251. }
  252. }
  253. case int64:
  254. var i int64
  255. if i, err = strconv.ParseInt(value, 10, 64); err != nil {
  256. return fmt.Errorf("archlinux: invalid %s %q: %v", v.Type().Name(), value, err)
  257. }
  258. v.SetInt(i)
  259. case []string:
  260. v.Set(reflect.Append(v, reflect.ValueOf(value)))
  261. case []byte:
  262. switch encoding {
  263. case "":
  264. v.SetBytes([]byte(value))
  265. case "base64":
  266. var b []byte
  267. if b, err = base64.StdEncoding.DecodeString(value); err != nil {
  268. return fmt.Errorf("archlinux: invalid base64 value %q: %v", value, err)
  269. }
  270. v.SetBytes(b)
  271. case "hex":
  272. var b []byte
  273. if b, err = hex.DecodeString(value); err != nil {
  274. return fmt.Errorf("archlinux: invalid hex encoded value %q: %v", value, err)
  275. }
  276. v.SetBytes(b)
  277. }
  278. case [][]byte:
  279. for _, part := range strings.Fields(value) {
  280. if part == "SKIP" && (encoding == "skip" || strings.HasSuffix(encoding, ",skip")) {
  281. continue
  282. }
  283. switch strings.SplitN(encoding, ",", 2)[0] {
  284. case "":
  285. v.Set(reflect.Append(v, reflect.ValueOf([]byte(part))))
  286. case "hex":
  287. var b []byte
  288. if b, err = hex.DecodeString(part); err != nil {
  289. return fmt.Errorf("archlinux: invalid hex encoded value %q: %v", value, err)
  290. }
  291. v.Set(reflect.Append(v, reflect.ValueOf(b)))
  292. }
  293. }
  294. case time.Time:
  295. var i int64
  296. if i, err = strconv.ParseInt(value, 10, 64); err != nil {
  297. return fmt.Errorf("archlinux: invalid time %q: %v", value, err)
  298. }
  299. v.Set(reflect.ValueOf(time.Unix(i, 0).UTC()))
  300. case *url.URL:
  301. var u *url.URL
  302. if u, err = url.Parse(value); err != nil {
  303. return fmt.Errorf("archlinux: invalid URL %q: %v", value, err)
  304. }
  305. v.Set(reflect.ValueOf(u))
  306. default:
  307. return fmt.Errorf("archlinux: unsupported field type %T", v.Interface())
  308. }
  309. return
  310. }
  311. // ParsePackageArchiveName parsea an Arch Linux package archive name
  312. func ParsePackageArchiveName(arc string) (name string, v version.Version, arch, ext string, err error) {
  313. i := strings.Index(arc, ".pkg.tar.")
  314. if i == -1 {
  315. err = ErrPackageArchiveNameInvalid
  316. return
  317. }
  318. name, ext = arc[:i], arc[i:]
  319. if i = strings.LastIndexByte(name, '-'); i == -1 {
  320. // No arch
  321. err = ErrPackageArchiveNameInvalid
  322. return
  323. }
  324. name, arch = name[:i], name[i+1:]
  325. if i = strings.LastIndexByte(name, '-'); i == -1 {
  326. // No release
  327. err = ErrPackageArchiveNameInvalid
  328. return
  329. }
  330. if i = strings.LastIndexByte(name[:i], '-'); i == -1 {
  331. // No version
  332. err = ErrPackageArchiveNameInvalid
  333. return
  334. }
  335. name, v = name[:i], version.Parse(name[i+1:])
  336. return
  337. }
  338. // ParsePackageName parses an Arch Linux package name to its name and version
  339. func ParsePackageName(s string) (name string, v version.Version, err error) {
  340. i := strings.LastIndexByte(s, '-')
  341. if i == -1 {
  342. // No release
  343. err = ErrPackageNameInvalid
  344. return
  345. }
  346. if i = strings.LastIndexByte(s[:i], '-'); i == -1 {
  347. // No version
  348. err = ErrPackageArchiveNameInvalid
  349. return
  350. }
  351. name, v = s[:i], version.Parse(s[i+1:])
  352. return
  353. }