resource.go 13 KB

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