context.go 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673
  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. "bytes"
  16. "fmt"
  17. "io"
  18. "log"
  19. "os"
  20. "path/filepath"
  21. "strings"
  22. "github.com/containerd/continuity/devices"
  23. driverpkg "github.com/containerd/continuity/driver"
  24. "github.com/containerd/continuity/pathdriver"
  25. "github.com/opencontainers/go-digest"
  26. )
  27. var (
  28. // ErrNotFound represents the resource not found
  29. ErrNotFound = fmt.Errorf("not found")
  30. // ErrNotSupported represents the resource not supported
  31. ErrNotSupported = fmt.Errorf("not supported")
  32. )
  33. // Context represents a file system context for accessing resources. The
  34. // responsibility of the context is to convert system specific resources to
  35. // generic Resource objects. Most of this is safe path manipulation, as well
  36. // as extraction of resource details.
  37. type Context interface {
  38. Apply(Resource) error
  39. Verify(Resource) error
  40. Resource(string, os.FileInfo) (Resource, error)
  41. Walk(filepath.WalkFunc) error
  42. }
  43. // SymlinkPath is intended to give the symlink target value
  44. // in a root context. Target and linkname are absolute paths
  45. // not under the given root.
  46. type SymlinkPath func(root, linkname, target string) (string, error)
  47. // ContextOptions represents options to create a new context.
  48. type ContextOptions struct {
  49. Digester Digester
  50. Driver driverpkg.Driver
  51. PathDriver pathdriver.PathDriver
  52. Provider ContentProvider
  53. }
  54. // context represents a file system context for accessing resources.
  55. // Generally, all path qualified access and system considerations should land
  56. // here.
  57. type context struct {
  58. driver driverpkg.Driver
  59. pathDriver pathdriver.PathDriver
  60. root string
  61. digester Digester
  62. provider ContentProvider
  63. }
  64. // NewContext returns a Context associated with root. The default driver will
  65. // be used, as returned by NewDriver.
  66. func NewContext(root string) (Context, error) {
  67. return NewContextWithOptions(root, ContextOptions{})
  68. }
  69. // NewContextWithOptions returns a Context associate with the root.
  70. func NewContextWithOptions(root string, options ContextOptions) (Context, error) {
  71. // normalize to absolute path
  72. pathDriver := options.PathDriver
  73. if pathDriver == nil {
  74. pathDriver = pathdriver.LocalPathDriver
  75. }
  76. root = pathDriver.FromSlash(root)
  77. root, err := pathDriver.Abs(pathDriver.Clean(root))
  78. if err != nil {
  79. return nil, err
  80. }
  81. driver := options.Driver
  82. if driver == nil {
  83. driver, err = driverpkg.NewSystemDriver()
  84. if err != nil {
  85. return nil, err
  86. }
  87. }
  88. digester := options.Digester
  89. if digester == nil {
  90. digester = simpleDigester{digest.Canonical}
  91. }
  92. // Check the root directory. Need to be a little careful here. We are
  93. // allowing a link for now, but this may have odd behavior when
  94. // canonicalizing paths. As long as all files are opened through the link
  95. // path, this should be okay.
  96. fi, err := driver.Stat(root)
  97. if err != nil {
  98. return nil, err
  99. }
  100. if !fi.IsDir() {
  101. return nil, &os.PathError{Op: "NewContext", Path: root, Err: os.ErrInvalid}
  102. }
  103. return &context{
  104. root: root,
  105. driver: driver,
  106. pathDriver: pathDriver,
  107. digester: digester,
  108. provider: options.Provider,
  109. }, nil
  110. }
  111. // Resource returns the resource as path p, populating the entry with info
  112. // from fi. The path p should be the path of the resource in the context,
  113. // typically obtained through Walk or from the value of Resource.Path(). If fi
  114. // is nil, it will be resolved.
  115. func (c *context) Resource(p string, fi os.FileInfo) (Resource, error) {
  116. fp, err := c.fullpath(p)
  117. if err != nil {
  118. return nil, err
  119. }
  120. if fi == nil {
  121. fi, err = c.driver.Lstat(fp)
  122. if err != nil {
  123. return nil, err
  124. }
  125. }
  126. base, err := newBaseResource(p, fi)
  127. if err != nil {
  128. return nil, err
  129. }
  130. base.xattrs, err = c.resolveXAttrs(fp, fi, base)
  131. if err == ErrNotSupported {
  132. log.Printf("resolving xattrs on %s not supported", fp)
  133. } else if err != nil {
  134. return nil, err
  135. }
  136. // TODO(stevvooe): Handle windows alternate data streams.
  137. if fi.Mode().IsRegular() {
  138. dgst, err := c.digest(p)
  139. if err != nil {
  140. return nil, err
  141. }
  142. return newRegularFile(*base, base.paths, fi.Size(), dgst)
  143. }
  144. if fi.Mode().IsDir() {
  145. return newDirectory(*base)
  146. }
  147. if fi.Mode()&os.ModeSymlink != 0 {
  148. // We handle relative links vs absolute links by including a
  149. // beginning slash for absolute links. Effectively, the bundle's
  150. // root is treated as the absolute link anchor.
  151. target, err := c.driver.Readlink(fp)
  152. if err != nil {
  153. return nil, err
  154. }
  155. return newSymLink(*base, target)
  156. }
  157. if fi.Mode()&os.ModeNamedPipe != 0 {
  158. return newNamedPipe(*base, base.paths)
  159. }
  160. if fi.Mode()&os.ModeDevice != 0 {
  161. deviceDriver, ok := c.driver.(driverpkg.DeviceInfoDriver)
  162. if !ok {
  163. log.Printf("device extraction not supported %s", fp)
  164. return nil, ErrNotSupported
  165. }
  166. // character and block devices merely need to recover the
  167. // major/minor device number.
  168. major, minor, err := deviceDriver.DeviceInfo(fi)
  169. if err != nil {
  170. return nil, err
  171. }
  172. return newDevice(*base, base.paths, major, minor)
  173. }
  174. log.Printf("%q (%v) is not supported", fp, fi.Mode())
  175. return nil, ErrNotFound
  176. }
  177. func (c *context) verifyMetadata(resource, target Resource) error {
  178. if target.Mode() != resource.Mode() {
  179. return fmt.Errorf("resource %q has incorrect mode: %v != %v", target.Path(), target.Mode(), resource.Mode())
  180. }
  181. if target.UID() != resource.UID() {
  182. return fmt.Errorf("unexpected uid for %q: %v != %v", target.Path(), target.UID(), resource.GID())
  183. }
  184. if target.GID() != resource.GID() {
  185. return fmt.Errorf("unexpected gid for %q: %v != %v", target.Path(), target.GID(), target.GID())
  186. }
  187. if xattrer, ok := resource.(XAttrer); ok {
  188. txattrer, tok := target.(XAttrer)
  189. if !tok {
  190. return fmt.Errorf("resource %q has xattrs but target does not support them", resource.Path())
  191. }
  192. // For xattrs, only ensure that we have those defined in the resource
  193. // and their values match. We can ignore other xattrs. In other words,
  194. // we only verify that target has the subset defined by resource.
  195. txattrs := txattrer.XAttrs()
  196. for attr, value := range xattrer.XAttrs() {
  197. tvalue, ok := txattrs[attr]
  198. if !ok {
  199. return fmt.Errorf("resource %q target missing xattr %q", resource.Path(), attr)
  200. }
  201. if !bytes.Equal(value, tvalue) {
  202. return fmt.Errorf("xattr %q value differs for resource %q", attr, resource.Path())
  203. }
  204. }
  205. }
  206. switch r := resource.(type) {
  207. case RegularFile:
  208. // TODO(stevvooe): Another reason to use a record-based approach. We
  209. // have to do another type switch to get this to work. This could be
  210. // fixed with an Equal function, but let's study this a little more to
  211. // be sure.
  212. t, ok := target.(RegularFile)
  213. if !ok {
  214. return fmt.Errorf("resource %q target not a regular file", r.Path())
  215. }
  216. if t.Size() != r.Size() {
  217. return fmt.Errorf("resource %q target has incorrect size: %v != %v", t.Path(), t.Size(), r.Size())
  218. }
  219. case Directory:
  220. t, ok := target.(Directory)
  221. if !ok {
  222. return fmt.Errorf("resource %q target not a directory", t.Path())
  223. }
  224. case SymLink:
  225. t, ok := target.(SymLink)
  226. if !ok {
  227. return fmt.Errorf("resource %q target not a symlink", t.Path())
  228. }
  229. if t.Target() != r.Target() {
  230. return fmt.Errorf("resource %q target has mismatched target: %q != %q", t.Path(), t.Target(), r.Target())
  231. }
  232. case Device:
  233. t, ok := target.(Device)
  234. if !ok {
  235. return fmt.Errorf("resource %q is not a device", t.Path())
  236. }
  237. if t.Major() != r.Major() || t.Minor() != r.Minor() {
  238. return fmt.Errorf("resource %q has mismatched major/minor numbers: %d,%d != %d,%d", t.Path(), t.Major(), t.Minor(), r.Major(), r.Minor())
  239. }
  240. case NamedPipe:
  241. t, ok := target.(NamedPipe)
  242. if !ok {
  243. return fmt.Errorf("resource %q is not a named pipe", t.Path())
  244. }
  245. default:
  246. return fmt.Errorf("cannot verify resource: %v", resource)
  247. }
  248. return nil
  249. }
  250. // Verify the resource in the context. An error will be returned a discrepancy
  251. // is found.
  252. func (c *context) Verify(resource Resource) error {
  253. fp, err := c.fullpath(resource.Path())
  254. if err != nil {
  255. return err
  256. }
  257. fi, err := c.driver.Lstat(fp)
  258. if err != nil {
  259. return err
  260. }
  261. target, err := c.Resource(resource.Path(), fi)
  262. if err != nil {
  263. return err
  264. }
  265. if target.Path() != resource.Path() {
  266. return fmt.Errorf("resource paths do not match: %q != %q", target.Path(), resource.Path())
  267. }
  268. if err := c.verifyMetadata(resource, target); err != nil {
  269. return err
  270. }
  271. if h, isHardlinkable := resource.(Hardlinkable); isHardlinkable {
  272. hardlinkKey, err := newHardlinkKey(fi)
  273. if err == errNotAHardLink {
  274. if len(h.Paths()) > 1 {
  275. return fmt.Errorf("%q is not a hardlink to %q", h.Paths()[1], resource.Path())
  276. }
  277. } else if err != nil {
  278. return err
  279. }
  280. for _, path := range h.Paths()[1:] {
  281. fpLink, err := c.fullpath(path)
  282. if err != nil {
  283. return err
  284. }
  285. fiLink, err := c.driver.Lstat(fpLink)
  286. if err != nil {
  287. return err
  288. }
  289. targetLink, err := c.Resource(path, fiLink)
  290. if err != nil {
  291. return err
  292. }
  293. hardlinkKeyLink, err := newHardlinkKey(fiLink)
  294. if err != nil {
  295. return err
  296. }
  297. if hardlinkKeyLink != hardlinkKey {
  298. return fmt.Errorf("%q is not a hardlink to %q", path, resource.Path())
  299. }
  300. if err := c.verifyMetadata(resource, targetLink); err != nil {
  301. return err
  302. }
  303. }
  304. }
  305. switch r := resource.(type) {
  306. case RegularFile:
  307. t, ok := target.(RegularFile)
  308. if !ok {
  309. return fmt.Errorf("resource %q target not a regular file", r.Path())
  310. }
  311. // TODO(stevvooe): This may need to get a little more sophisticated
  312. // for digest comparison. We may want to actually calculate the
  313. // provided digests, rather than the implementations having an
  314. // overlap.
  315. if !digestsMatch(t.Digests(), r.Digests()) {
  316. return fmt.Errorf("digests for resource %q do not match: %v != %v", t.Path(), t.Digests(), r.Digests())
  317. }
  318. }
  319. return nil
  320. }
  321. func (c *context) checkoutFile(fp string, rf RegularFile) error {
  322. if c.provider == nil {
  323. return fmt.Errorf("no file provider")
  324. }
  325. var (
  326. r io.ReadCloser
  327. err error
  328. )
  329. for _, dgst := range rf.Digests() {
  330. r, err = c.provider.Reader(dgst)
  331. if err == nil {
  332. break
  333. }
  334. }
  335. if err != nil {
  336. return fmt.Errorf("file content could not be provided: %v", err)
  337. }
  338. defer r.Close()
  339. return atomicWriteFile(fp, r, rf.Size(), rf.Mode())
  340. }
  341. // Apply the resource to the contexts. An error will be returned if the
  342. // operation fails. Depending on the resource type, the resource may be
  343. // created. For resource that cannot be resolved, an error will be returned.
  344. func (c *context) Apply(resource Resource) error {
  345. fp, err := c.fullpath(resource.Path())
  346. if err != nil {
  347. return err
  348. }
  349. if !strings.HasPrefix(fp, c.root) {
  350. return fmt.Errorf("resource %v escapes root", resource)
  351. }
  352. var chmod = true
  353. fi, err := c.driver.Lstat(fp)
  354. if err != nil {
  355. if !os.IsNotExist(err) {
  356. return err
  357. }
  358. }
  359. switch r := resource.(type) {
  360. case RegularFile:
  361. if fi == nil {
  362. if err := c.checkoutFile(fp, r); err != nil {
  363. return fmt.Errorf("error checking out file %q: %v", resource.Path(), err)
  364. }
  365. chmod = false
  366. } else {
  367. if !fi.Mode().IsRegular() {
  368. return fmt.Errorf("file %q should be a regular file, but is not", resource.Path())
  369. }
  370. if fi.Size() != r.Size() {
  371. if err := c.checkoutFile(fp, r); err != nil {
  372. return fmt.Errorf("error checking out file %q: %v", resource.Path(), err)
  373. }
  374. } else {
  375. for _, dgst := range r.Digests() {
  376. f, err := os.Open(fp)
  377. if err != nil {
  378. return fmt.Errorf("failure opening file for read %q: %v", resource.Path(), err)
  379. }
  380. compared, err := dgst.Algorithm().FromReader(f)
  381. if err == nil && dgst != compared {
  382. if err := c.checkoutFile(fp, r); err != nil {
  383. return fmt.Errorf("error checking out file %q: %v", resource.Path(), err)
  384. }
  385. break
  386. }
  387. if err1 := f.Close(); err == nil {
  388. err = err1
  389. }
  390. if err != nil {
  391. return fmt.Errorf("error checking digest for %q: %v", resource.Path(), err)
  392. }
  393. }
  394. }
  395. }
  396. case Directory:
  397. if fi == nil {
  398. if err := c.driver.Mkdir(fp, resource.Mode()); err != nil {
  399. return err
  400. }
  401. } else if !fi.Mode().IsDir() {
  402. return fmt.Errorf("%q should be a directory, but is not", resource.Path())
  403. }
  404. case SymLink:
  405. var target string // only possibly set if target resource is a symlink
  406. if fi != nil {
  407. if fi.Mode()&os.ModeSymlink != 0 {
  408. target, err = c.driver.Readlink(fp)
  409. if err != nil {
  410. return err
  411. }
  412. }
  413. }
  414. if target != r.Target() {
  415. if fi != nil {
  416. if err := c.driver.Remove(fp); err != nil { // RemoveAll in case of directory?
  417. return err
  418. }
  419. }
  420. if err := c.driver.Symlink(r.Target(), fp); err != nil {
  421. return err
  422. }
  423. }
  424. case Device:
  425. if fi == nil {
  426. if err := c.driver.Mknod(fp, resource.Mode(), int(r.Major()), int(r.Minor())); err != nil {
  427. return err
  428. }
  429. } else if (fi.Mode() & os.ModeDevice) == 0 {
  430. return fmt.Errorf("%q should be a device, but is not", resource.Path())
  431. } else {
  432. major, minor, err := devices.DeviceInfo(fi)
  433. if err != nil {
  434. return err
  435. }
  436. if major != r.Major() || minor != r.Minor() {
  437. if err := c.driver.Remove(fp); err != nil {
  438. return err
  439. }
  440. if err := c.driver.Mknod(fp, resource.Mode(), int(r.Major()), int(r.Minor())); err != nil {
  441. return err
  442. }
  443. }
  444. }
  445. case NamedPipe:
  446. if fi == nil {
  447. if err := c.driver.Mkfifo(fp, resource.Mode()); err != nil {
  448. return err
  449. }
  450. } else if (fi.Mode() & os.ModeNamedPipe) == 0 {
  451. return fmt.Errorf("%q should be a named pipe, but is not", resource.Path())
  452. }
  453. }
  454. if h, isHardlinkable := resource.(Hardlinkable); isHardlinkable {
  455. for _, path := range h.Paths() {
  456. if path == resource.Path() {
  457. continue
  458. }
  459. lp, err := c.fullpath(path)
  460. if err != nil {
  461. return err
  462. }
  463. if _, fi := c.driver.Lstat(lp); fi == nil {
  464. c.driver.Remove(lp)
  465. }
  466. if err := c.driver.Link(fp, lp); err != nil {
  467. return err
  468. }
  469. }
  470. }
  471. // Update filemode if file was not created
  472. if chmod {
  473. if err := c.driver.Lchmod(fp, resource.Mode()); err != nil {
  474. return err
  475. }
  476. }
  477. if err := c.driver.Lchown(fp, resource.UID(), resource.GID()); err != nil {
  478. return err
  479. }
  480. if xattrer, ok := resource.(XAttrer); ok {
  481. // For xattrs, only ensure that we have those defined in the resource
  482. // and their values are set. We can ignore other xattrs. In other words,
  483. // we only set xattres defined by resource but never remove.
  484. if _, ok := resource.(SymLink); ok {
  485. lxattrDriver, ok := c.driver.(driverpkg.LXAttrDriver)
  486. if !ok {
  487. return fmt.Errorf("unsupported symlink xattr for resource %q", resource.Path())
  488. }
  489. if err := lxattrDriver.LSetxattr(fp, xattrer.XAttrs()); err != nil {
  490. return err
  491. }
  492. } else {
  493. xattrDriver, ok := c.driver.(driverpkg.XAttrDriver)
  494. if !ok {
  495. return fmt.Errorf("unsupported xattr for resource %q", resource.Path())
  496. }
  497. if err := xattrDriver.Setxattr(fp, xattrer.XAttrs()); err != nil {
  498. return err
  499. }
  500. }
  501. }
  502. return nil
  503. }
  504. // Walk provides a convenience function to call filepath.Walk correctly for
  505. // the context. Otherwise identical to filepath.Walk, the path argument is
  506. // corrected to be contained within the context.
  507. func (c *context) Walk(fn filepath.WalkFunc) error {
  508. root := c.root
  509. fi, err := c.driver.Lstat(c.root)
  510. if err == nil && fi.Mode()&os.ModeSymlink != 0 {
  511. root, err = c.driver.Readlink(c.root)
  512. if err != nil {
  513. return err
  514. }
  515. }
  516. return c.pathDriver.Walk(root, func(p string, fi os.FileInfo, err error) error {
  517. contained, err := c.containWithRoot(p, root)
  518. return fn(contained, fi, err)
  519. })
  520. }
  521. // fullpath returns the system path for the resource, joined with the context
  522. // root. The path p must be a part of the context.
  523. func (c *context) fullpath(p string) (string, error) {
  524. p = c.pathDriver.Join(c.root, p)
  525. if !strings.HasPrefix(p, c.root) {
  526. return "", fmt.Errorf("invalid context path")
  527. }
  528. return p, nil
  529. }
  530. // contain cleans and santizes the filesystem path p to be an absolute path,
  531. // effectively relative to the context root.
  532. func (c *context) contain(p string) (string, error) {
  533. return c.containWithRoot(p, c.root)
  534. }
  535. // containWithRoot cleans and santizes the filesystem path p to be an absolute path,
  536. // effectively relative to the passed root. Extra care should be used when calling this
  537. // instead of contain. This is needed for Walk, as if context root is a symlink,
  538. // it must be evaluated prior to the Walk
  539. func (c *context) containWithRoot(p string, root string) (string, error) {
  540. sanitized, err := c.pathDriver.Rel(root, p)
  541. if err != nil {
  542. return "", err
  543. }
  544. // ZOMBIES(stevvooe): In certain cases, we may want to remap these to a
  545. // "containment error", so the caller can decide what to do.
  546. return c.pathDriver.Join("/", c.pathDriver.Clean(sanitized)), nil
  547. }
  548. // digest returns the digest of the file at path p, relative to the root.
  549. func (c *context) digest(p string) (digest.Digest, error) {
  550. f, err := c.driver.Open(c.pathDriver.Join(c.root, p))
  551. if err != nil {
  552. return "", err
  553. }
  554. defer f.Close()
  555. return c.digester.Digest(f)
  556. }
  557. // resolveXAttrs attempts to resolve the extended attributes for the resource
  558. // at the path fp, which is the full path to the resource. If the resource
  559. // cannot have xattrs, nil will be returned.
  560. func (c *context) resolveXAttrs(fp string, fi os.FileInfo, base *resource) (map[string][]byte, error) {
  561. if fi.Mode().IsRegular() || fi.Mode().IsDir() {
  562. xattrDriver, ok := c.driver.(driverpkg.XAttrDriver)
  563. if !ok {
  564. log.Println("xattr extraction not supported")
  565. return nil, ErrNotSupported
  566. }
  567. return xattrDriver.Getxattr(fp)
  568. }
  569. if fi.Mode()&os.ModeSymlink != 0 {
  570. lxattrDriver, ok := c.driver.(driverpkg.LXAttrDriver)
  571. if !ok {
  572. log.Println("xattr extraction for symlinks not supported")
  573. return nil, ErrNotSupported
  574. }
  575. return lxattrDriver.LGetxattr(fp)
  576. }
  577. return nil, nil
  578. }