changes.go 7.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230
  1. /* {{{ Copyright (c) Paul R. Tagliamonte <[email protected]>, 2015
  2. *
  3. * Permission is hereby granted, free of charge, to any person obtaining a copy
  4. * of this software and associated documentation files (the "Software"), to deal
  5. * in the Software without restriction, including without limitation the rights
  6. * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
  7. * copies of the Software, and to permit persons to whom the Software is
  8. * furnished to do so, subject to the following conditions:
  9. *
  10. * The above copyright notice and this permission notice shall be included in
  11. * all copies or substantial portions of the Software.
  12. *
  13. * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  14. * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  15. * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  16. * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  17. * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
  18. * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
  19. * THE SOFTWARE. }}} */
  20. package control // import "pault.ag/go/debian/control"
  21. import (
  22. "bufio"
  23. "fmt"
  24. "os"
  25. "path"
  26. "path/filepath"
  27. "strconv"
  28. "strings"
  29. "pault.ag/go/debian/dependency"
  30. "pault.ag/go/debian/internal"
  31. "pault.ag/go/debian/version"
  32. )
  33. // {{{ .changes Files list entries
  34. type FileListChangesFileHash struct {
  35. FileHash
  36. Component string
  37. Priority string
  38. }
  39. func (c *FileListChangesFileHash) UnmarshalControl(data string) error {
  40. var err error
  41. c.Algorithm = "md5"
  42. vals := strings.Split(data, " ")
  43. if len(vals) < 5 {
  44. return fmt.Errorf("Error: Unknown File List Hash line: '%s'", data)
  45. }
  46. c.Hash = vals[0]
  47. c.Size, err = strconv.ParseInt(vals[1], 10, 64)
  48. if err != nil {
  49. return err
  50. }
  51. c.Component = vals[2]
  52. c.Priority = vals[3]
  53. c.Filename = vals[4]
  54. return nil
  55. }
  56. // }}}
  57. // The Changes struct is the default encapsulation of the Debian .changes
  58. // package filetype.This struct contains an anonymous member of type Paragraph,
  59. // allowing you to use the standard .Values and .Order of the Paragraph type.
  60. //
  61. // The .changes files are used by the Debian archive maintenance software to
  62. // process updates to packages. They consist of a single paragraph, possibly
  63. // surrounded by a PGP signature. That paragraph contains information from the
  64. // debian/control file and other data about the source package gathered via
  65. // debian/changelog and debian/rules.
  66. type Changes struct {
  67. Paragraph
  68. Filename string
  69. Format string
  70. Source string
  71. Binaries []string `control:"Binary" delim:" "`
  72. Architectures []dependency.Arch `control:"Architecture"`
  73. Version version.Version
  74. Origin string
  75. Distribution string
  76. Urgency string
  77. Maintainer string
  78. ChangedBy string `control:"Changed-By"`
  79. Closes []string
  80. Changes string
  81. ChecksumsSha1 []SHA1FileHash `control:"Checksums-Sha1" delim:"\n" strip:"\n\r\t "`
  82. ChecksumsSha256 []SHA256FileHash `control:"Checksums-Sha256" delim:"\n" strip:"\n\r\t "`
  83. Files []FileListChangesFileHash `control:"Files" delim:"\n" strip:"\n\r\t "`
  84. }
  85. // Given a path on the filesystem, Parse the file off the disk and return
  86. // a pointer to a brand new Changes struct, unless error is set to a value
  87. // other than nil.
  88. func ParseChangesFile(path string) (ret *Changes, err error) {
  89. path, err = filepath.Abs(path)
  90. if err != nil {
  91. return nil, err
  92. }
  93. f, err := os.Open(path)
  94. if err != nil {
  95. return nil, err
  96. }
  97. defer f.Close()
  98. return ParseChanges(bufio.NewReader(f), path)
  99. }
  100. // Given a bufio.Reader, consume the Reader, and return a Changes object
  101. // for use. The "path" argument is used to set Changes.Filename, which
  102. // is used by Changes.GetDSC, Changes.Remove, Changes.Move and Changes.Copy to
  103. // figure out where all the files on the filesystem are. This value can be set
  104. // to something invalid if you're not using those functions.
  105. func ParseChanges(reader *bufio.Reader, path string) (*Changes, error) {
  106. ret := &Changes{Filename: path}
  107. return ret, Unmarshal(ret, reader)
  108. }
  109. // Return a list of FileListChangesFileHash entries from the `changes.Files`
  110. // entry, with the exception that each `Filename` will be joined to the root
  111. // directory of the Changes file.
  112. func (changes *Changes) AbsFiles() []FileListChangesFileHash {
  113. ret := []FileListChangesFileHash{}
  114. baseDir := filepath.Dir(changes.Filename)
  115. for _, hash := range changes.Files {
  116. hash.Filename = path.Join(baseDir, hash.Filename)
  117. ret = append(ret, hash)
  118. }
  119. return ret
  120. }
  121. // Return a DSC struct for the DSC listed in the .changes file. This requires
  122. // Changes.Filename to be correctly set, and for the .dsc file to exist
  123. // in the correct place next to the .changes.
  124. //
  125. // This function may also return an error if the given .changes does not
  126. // include the .dsc (binary-only upload)
  127. func (changes *Changes) GetDSC() (*DSC, error) {
  128. for _, file := range changes.Files {
  129. if strings.HasSuffix(file.Filename, ".dsc") {
  130. // Right, now lets resolve the absolute path.
  131. baseDir := filepath.Dir(changes.Filename)
  132. dsc, err := ParseDscFile(baseDir + "/" + file.Filename)
  133. if err != nil {
  134. return nil, err
  135. }
  136. return dsc, nil
  137. }
  138. }
  139. return nil, fmt.Errorf("No .dsc file in .changes")
  140. }
  141. // Copy the .changes file and all referenced files to the directory
  142. // listed by the dest argument. This function will error out if the dest
  143. // argument is not a directory, or if there is an IO operation in transfer.
  144. //
  145. // This function will always move .changes last, making it suitable to
  146. // be used to move something into an incoming directory with an inotify
  147. // hook. This will also mutate Changes.Filename to match the new location.
  148. func (changes *Changes) Copy(dest string) error {
  149. if file, err := os.Stat(dest); err == nil && !file.IsDir() {
  150. return fmt.Errorf("Attempting to move .changes to a non-directory")
  151. }
  152. for _, file := range changes.AbsFiles() {
  153. dirname := filepath.Base(file.Filename)
  154. err := internal.Copy(file.Filename, dest+"/"+dirname)
  155. if err != nil {
  156. return err
  157. }
  158. }
  159. dirname := filepath.Base(changes.Filename)
  160. err := internal.Copy(changes.Filename, dest+"/"+dirname)
  161. changes.Filename = dest + "/" + dirname
  162. return err
  163. }
  164. // Move the .changes file and all referenced files to the directory
  165. // listed by the dest argument. This function will error out if the dest
  166. // argument is not a directory, or if there is an IO operation in transfer.
  167. //
  168. // This function will always move .changes last, making it suitable to
  169. // be used to move something into an incoming directory with an inotify
  170. // hook. This will also mutate Changes.Filename to match the new location.
  171. func (changes *Changes) Move(dest string) error {
  172. if file, err := os.Stat(dest); err == nil && !file.IsDir() {
  173. return fmt.Errorf("Attempting to move .changes to a non-directory")
  174. }
  175. for _, file := range changes.AbsFiles() {
  176. dirname := filepath.Base(file.Filename)
  177. err := os.Rename(file.Filename, dest+"/"+dirname)
  178. if err != nil {
  179. return err
  180. }
  181. }
  182. dirname := filepath.Base(changes.Filename)
  183. err := os.Rename(changes.Filename, dest+"/"+dirname)
  184. changes.Filename = dest + "/" + dirname
  185. return err
  186. }
  187. // Remove the .changes file and any associated files. This function will
  188. // always remove the .changes last, in the event there are filesystem i/o errors
  189. // on removing associated files.
  190. func (changes *Changes) Remove() error {
  191. for _, file := range changes.AbsFiles() {
  192. err := os.Remove(file.Filename)
  193. if err != nil {
  194. return err
  195. }
  196. }
  197. return os.Remove(changes.Filename)
  198. }
  199. // vim: foldmethod=marker