resource.go 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574
  1. package continuity
  2. import (
  3. "errors"
  4. "fmt"
  5. "os"
  6. "reflect"
  7. "sort"
  8. pb "github.com/containerd/continuity/proto"
  9. "github.com/opencontainers/go-digest"
  10. )
  11. // TODO(stevvooe): A record based model, somewhat sketched out at the bottom
  12. // of this file, will be more flexible. Another possibly is to tie the package
  13. // interface directly to the protobuf type. This will have efficiency
  14. // advantages at the cost coupling the nasty codegen types to the exported
  15. // interface.
  16. type Resource interface {
  17. // Path provides the primary resource path relative to the bundle root. In
  18. // cases where resources have more than one path, such as with hard links,
  19. // this will return the primary path, which is often just the first entry.
  20. Path() string
  21. // Mode returns the
  22. Mode() os.FileMode
  23. UID() int64
  24. GID() int64
  25. }
  26. // ByPath provides the canonical sort order for a set of resources. Use with
  27. // sort.Stable for deterministic sorting.
  28. type ByPath []Resource
  29. func (bp ByPath) Len() int { return len(bp) }
  30. func (bp ByPath) Swap(i, j int) { bp[i], bp[j] = bp[j], bp[i] }
  31. func (bp ByPath) Less(i, j int) bool { return bp[i].Path() < bp[j].Path() }
  32. type XAttrer interface {
  33. XAttrs() map[string][]byte
  34. }
  35. // Hardlinkable is an interface that a resource type satisfies if it can be a
  36. // hardlink target.
  37. type Hardlinkable interface {
  38. // Paths returns all paths of the resource, including the primary path
  39. // returned by Resource.Path. If len(Paths()) > 1, the resource is a hard
  40. // link.
  41. Paths() []string
  42. }
  43. type RegularFile interface {
  44. Resource
  45. XAttrer
  46. Hardlinkable
  47. Size() int64
  48. Digests() []digest.Digest
  49. }
  50. // Merge two or more Resources into new file. Typically, this should be
  51. // used to merge regular files as hardlinks. If the files are not identical,
  52. // other than Paths and Digests, the merge will fail and an error will be
  53. // returned.
  54. func Merge(fs ...Resource) (Resource, error) {
  55. if len(fs) < 1 {
  56. return nil, fmt.Errorf("please provide a resource to merge")
  57. }
  58. if len(fs) == 1 {
  59. return fs[0], nil
  60. }
  61. var paths []string
  62. var digests []digest.Digest
  63. bypath := map[string][]Resource{}
  64. // The attributes are all compared against the first to make sure they
  65. // agree before adding to the above collections. If any of these don't
  66. // correctly validate, the merge fails.
  67. prototype := fs[0]
  68. xattrs := make(map[string][]byte)
  69. // initialize xattrs for use below. All files must have same xattrs.
  70. if prototypeXAttrer, ok := prototype.(XAttrer); ok {
  71. for attr, value := range prototypeXAttrer.XAttrs() {
  72. xattrs[attr] = value
  73. }
  74. }
  75. for _, f := range fs {
  76. h, isHardlinkable := f.(Hardlinkable)
  77. if !isHardlinkable {
  78. return nil, errNotAHardLink
  79. }
  80. if f.Mode() != prototype.Mode() {
  81. return nil, fmt.Errorf("modes do not match: %v != %v", f.Mode(), prototype.Mode())
  82. }
  83. if f.UID() != prototype.UID() {
  84. return nil, fmt.Errorf("uid does not match: %v != %v", f.UID(), prototype.UID())
  85. }
  86. if f.GID() != prototype.GID() {
  87. return nil, fmt.Errorf("gid does not match: %v != %v", f.GID(), prototype.GID())
  88. }
  89. if xattrer, ok := f.(XAttrer); ok {
  90. fxattrs := xattrer.XAttrs()
  91. if !reflect.DeepEqual(fxattrs, xattrs) {
  92. return nil, fmt.Errorf("resource %q xattrs do not match: %v != %v", f, fxattrs, xattrs)
  93. }
  94. }
  95. for _, p := range h.Paths() {
  96. pfs, ok := bypath[p]
  97. if !ok {
  98. // ensure paths are unique by only appending on a new path.
  99. paths = append(paths, p)
  100. }
  101. bypath[p] = append(pfs, f)
  102. }
  103. if regFile, isRegFile := f.(RegularFile); isRegFile {
  104. prototypeRegFile, prototypeIsRegFile := prototype.(RegularFile)
  105. if !prototypeIsRegFile {
  106. return nil, errors.New("prototype is not a regular file")
  107. }
  108. if regFile.Size() != prototypeRegFile.Size() {
  109. return nil, fmt.Errorf("size does not match: %v != %v", regFile.Size(), prototypeRegFile.Size())
  110. }
  111. digests = append(digests, regFile.Digests()...)
  112. } else if device, isDevice := f.(Device); isDevice {
  113. prototypeDevice, prototypeIsDevice := prototype.(Device)
  114. if !prototypeIsDevice {
  115. return nil, errors.New("prototype is not a device")
  116. }
  117. if device.Major() != prototypeDevice.Major() {
  118. return nil, fmt.Errorf("major number does not match: %v != %v", device.Major(), prototypeDevice.Major())
  119. }
  120. if device.Minor() != prototypeDevice.Minor() {
  121. return nil, fmt.Errorf("minor number does not match: %v != %v", device.Minor(), prototypeDevice.Minor())
  122. }
  123. } else if _, isNamedPipe := f.(NamedPipe); isNamedPipe {
  124. _, prototypeIsNamedPipe := prototype.(NamedPipe)
  125. if !prototypeIsNamedPipe {
  126. return nil, errors.New("prototype is not a named pipe")
  127. }
  128. } else {
  129. return nil, errNotAHardLink
  130. }
  131. }
  132. sort.Stable(sort.StringSlice(paths))
  133. // Choose a "canonical" file. Really, it is just the first file to sort
  134. // against. We also effectively select the very first digest as the
  135. // "canonical" one for this file.
  136. first := bypath[paths[0]][0]
  137. resource := resource{
  138. paths: paths,
  139. mode: first.Mode(),
  140. uid: first.UID(),
  141. gid: first.GID(),
  142. xattrs: xattrs,
  143. }
  144. switch typedF := first.(type) {
  145. case RegularFile:
  146. var err error
  147. digests, err = uniqifyDigests(digests...)
  148. if err != nil {
  149. return nil, err
  150. }
  151. return &regularFile{
  152. resource: resource,
  153. size: typedF.Size(),
  154. digests: digests,
  155. }, nil
  156. case Device:
  157. return &device{
  158. resource: resource,
  159. major: typedF.Major(),
  160. minor: typedF.Minor(),
  161. }, nil
  162. case NamedPipe:
  163. return &namedPipe{
  164. resource: resource,
  165. }, nil
  166. default:
  167. return nil, errNotAHardLink
  168. }
  169. }
  170. type Directory interface {
  171. Resource
  172. XAttrer
  173. // Directory is a no-op method to identify directory objects by interface.
  174. Directory()
  175. }
  176. type SymLink interface {
  177. Resource
  178. // Target returns the target of the symlink contained in the .
  179. Target() string
  180. }
  181. type NamedPipe interface {
  182. Resource
  183. Hardlinkable
  184. XAttrer
  185. // Pipe is a no-op method to allow consistent resolution of NamedPipe
  186. // interface.
  187. Pipe()
  188. }
  189. type Device interface {
  190. Resource
  191. Hardlinkable
  192. XAttrer
  193. Major() uint64
  194. Minor() uint64
  195. }
  196. type resource struct {
  197. paths []string
  198. mode os.FileMode
  199. uid, gid int64
  200. xattrs map[string][]byte
  201. }
  202. var _ Resource = &resource{}
  203. func (r *resource) Path() string {
  204. if len(r.paths) < 1 {
  205. return ""
  206. }
  207. return r.paths[0]
  208. }
  209. func (r *resource) Mode() os.FileMode {
  210. return r.mode
  211. }
  212. func (r *resource) UID() int64 {
  213. return r.uid
  214. }
  215. func (r *resource) GID() int64 {
  216. return r.gid
  217. }
  218. type regularFile struct {
  219. resource
  220. size int64
  221. digests []digest.Digest
  222. }
  223. var _ RegularFile = &regularFile{}
  224. // newRegularFile returns the RegularFile, using the populated base resource
  225. // and one or more digests of the content.
  226. func newRegularFile(base resource, paths []string, size int64, dgsts ...digest.Digest) (RegularFile, error) {
  227. if !base.Mode().IsRegular() {
  228. return nil, fmt.Errorf("not a regular file")
  229. }
  230. base.paths = make([]string, len(paths))
  231. copy(base.paths, paths)
  232. // make our own copy of digests
  233. ds := make([]digest.Digest, len(dgsts))
  234. copy(ds, dgsts)
  235. return &regularFile{
  236. resource: base,
  237. size: size,
  238. digests: ds,
  239. }, nil
  240. }
  241. func (rf *regularFile) Paths() []string {
  242. paths := make([]string, len(rf.paths))
  243. copy(paths, rf.paths)
  244. return paths
  245. }
  246. func (rf *regularFile) Size() int64 {
  247. return rf.size
  248. }
  249. func (rf *regularFile) Digests() []digest.Digest {
  250. digests := make([]digest.Digest, len(rf.digests))
  251. copy(digests, rf.digests)
  252. return digests
  253. }
  254. func (rf *regularFile) XAttrs() map[string][]byte {
  255. xattrs := make(map[string][]byte, len(rf.xattrs))
  256. for attr, value := range rf.xattrs {
  257. xattrs[attr] = append(xattrs[attr], value...)
  258. }
  259. return xattrs
  260. }
  261. type directory struct {
  262. resource
  263. }
  264. var _ Directory = &directory{}
  265. func newDirectory(base resource) (Directory, error) {
  266. if !base.Mode().IsDir() {
  267. return nil, fmt.Errorf("not a directory")
  268. }
  269. return &directory{
  270. resource: base,
  271. }, nil
  272. }
  273. func (d *directory) Directory() {}
  274. func (d *directory) XAttrs() map[string][]byte {
  275. xattrs := make(map[string][]byte, len(d.xattrs))
  276. for attr, value := range d.xattrs {
  277. xattrs[attr] = append(xattrs[attr], value...)
  278. }
  279. return xattrs
  280. }
  281. type symLink struct {
  282. resource
  283. target string
  284. }
  285. var _ SymLink = &symLink{}
  286. func newSymLink(base resource, target string) (SymLink, error) {
  287. if base.Mode()&os.ModeSymlink == 0 {
  288. return nil, fmt.Errorf("not a symlink")
  289. }
  290. return &symLink{
  291. resource: base,
  292. target: target,
  293. }, nil
  294. }
  295. func (l *symLink) Target() string {
  296. return l.target
  297. }
  298. type namedPipe struct {
  299. resource
  300. }
  301. var _ NamedPipe = &namedPipe{}
  302. func newNamedPipe(base resource, paths []string) (NamedPipe, error) {
  303. if base.Mode()&os.ModeNamedPipe == 0 {
  304. return nil, fmt.Errorf("not a namedpipe")
  305. }
  306. base.paths = make([]string, len(paths))
  307. copy(base.paths, paths)
  308. return &namedPipe{
  309. resource: base,
  310. }, nil
  311. }
  312. func (np *namedPipe) Pipe() {}
  313. func (np *namedPipe) Paths() []string {
  314. paths := make([]string, len(np.paths))
  315. copy(paths, np.paths)
  316. return paths
  317. }
  318. func (np *namedPipe) XAttrs() map[string][]byte {
  319. xattrs := make(map[string][]byte, len(np.xattrs))
  320. for attr, value := range np.xattrs {
  321. xattrs[attr] = append(xattrs[attr], value...)
  322. }
  323. return xattrs
  324. }
  325. type device struct {
  326. resource
  327. major, minor uint64
  328. }
  329. var _ Device = &device{}
  330. func newDevice(base resource, paths []string, major, minor uint64) (Device, error) {
  331. if base.Mode()&os.ModeDevice == 0 {
  332. return nil, fmt.Errorf("not a device")
  333. }
  334. base.paths = make([]string, len(paths))
  335. copy(base.paths, paths)
  336. return &device{
  337. resource: base,
  338. major: major,
  339. minor: minor,
  340. }, nil
  341. }
  342. func (d *device) Paths() []string {
  343. paths := make([]string, len(d.paths))
  344. copy(paths, d.paths)
  345. return paths
  346. }
  347. func (d *device) XAttrs() map[string][]byte {
  348. xattrs := make(map[string][]byte, len(d.xattrs))
  349. for attr, value := range d.xattrs {
  350. xattrs[attr] = append(xattrs[attr], value...)
  351. }
  352. return xattrs
  353. }
  354. func (d device) Major() uint64 {
  355. return d.major
  356. }
  357. func (d device) Minor() uint64 {
  358. return d.minor
  359. }
  360. // toProto converts a resource to a protobuf record. We'd like to push this
  361. // the individual types but we want to keep this all together during
  362. // prototyping.
  363. func toProto(resource Resource) *pb.Resource {
  364. b := &pb.Resource{
  365. Path: []string{resource.Path()},
  366. Mode: uint32(resource.Mode()),
  367. Uid: resource.UID(),
  368. Gid: resource.GID(),
  369. }
  370. if xattrer, ok := resource.(XAttrer); ok {
  371. // Sorts the XAttrs by name for consistent ordering.
  372. keys := []string{}
  373. xattrs := xattrer.XAttrs()
  374. for k := range xattrs {
  375. keys = append(keys, k)
  376. }
  377. sort.Strings(keys)
  378. for _, k := range keys {
  379. b.Xattr = append(b.Xattr, &pb.XAttr{Name: k, Data: xattrs[k]})
  380. }
  381. }
  382. switch r := resource.(type) {
  383. case RegularFile:
  384. b.Path = r.Paths()
  385. b.Size = uint64(r.Size())
  386. for _, dgst := range r.Digests() {
  387. b.Digest = append(b.Digest, dgst.String())
  388. }
  389. case SymLink:
  390. b.Target = r.Target()
  391. case Device:
  392. b.Major, b.Minor = r.Major(), r.Minor()
  393. b.Path = r.Paths()
  394. case NamedPipe:
  395. b.Path = r.Paths()
  396. }
  397. // enforce a few stability guarantees that may not be provided by the
  398. // resource implementation.
  399. sort.Strings(b.Path)
  400. return b
  401. }
  402. // fromProto converts from a protobuf Resource to a Resource interface.
  403. func fromProto(b *pb.Resource) (Resource, error) {
  404. base := &resource{
  405. paths: b.Path,
  406. mode: os.FileMode(b.Mode),
  407. uid: b.Uid,
  408. gid: b.Gid,
  409. }
  410. base.xattrs = make(map[string][]byte, len(b.Xattr))
  411. for _, attr := range b.Xattr {
  412. base.xattrs[attr.Name] = attr.Data
  413. }
  414. switch {
  415. case base.Mode().IsRegular():
  416. dgsts := make([]digest.Digest, len(b.Digest))
  417. for i, dgst := range b.Digest {
  418. // TODO(stevvooe): Should we be validating at this point?
  419. dgsts[i] = digest.Digest(dgst)
  420. }
  421. return newRegularFile(*base, b.Path, int64(b.Size), dgsts...)
  422. case base.Mode().IsDir():
  423. return newDirectory(*base)
  424. case base.Mode()&os.ModeSymlink != 0:
  425. return newSymLink(*base, b.Target)
  426. case base.Mode()&os.ModeNamedPipe != 0:
  427. return newNamedPipe(*base, b.Path)
  428. case base.Mode()&os.ModeDevice != 0:
  429. return newDevice(*base, b.Path, b.Major, b.Minor)
  430. }
  431. return nil, fmt.Errorf("unknown resource record (%#v): %s", b, base.Mode())
  432. }
  433. // NOTE(stevvooe): An alternative model that supports inline declaration.
  434. // Convenient for unit testing where inline declarations may be desirable but
  435. // creates an awkward API for the standard use case.
  436. // type ResourceKind int
  437. // const (
  438. // ResourceRegularFile = iota + 1
  439. // ResourceDirectory
  440. // ResourceSymLink
  441. // Resource
  442. // )
  443. // type Resource struct {
  444. // Kind ResourceKind
  445. // Paths []string
  446. // Mode os.FileMode
  447. // UID string
  448. // GID string
  449. // Size int64
  450. // Digests []digest.Digest
  451. // Target string
  452. // Major, Minor int
  453. // XAttrs map[string][]byte
  454. // }
  455. // type RegularFile struct {
  456. // Paths []string
  457. // Size int64
  458. // Digests []digest.Digest
  459. // Perm os.FileMode // os.ModePerm + sticky, setuid, setgid
  460. // }