graph.go 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656
  1. package graph
  2. import (
  3. "compress/gzip"
  4. "encoding/json"
  5. "errors"
  6. "fmt"
  7. "io"
  8. "io/ioutil"
  9. "os"
  10. "path/filepath"
  11. "runtime"
  12. "strconv"
  13. "strings"
  14. "sync"
  15. "time"
  16. "github.com/Sirupsen/logrus"
  17. "github.com/docker/distribution/digest"
  18. "github.com/docker/docker/autogen/dockerversion"
  19. "github.com/docker/docker/daemon/graphdriver"
  20. "github.com/docker/docker/image"
  21. "github.com/docker/docker/pkg/archive"
  22. "github.com/docker/docker/pkg/progressreader"
  23. "github.com/docker/docker/pkg/streamformatter"
  24. "github.com/docker/docker/pkg/stringid"
  25. "github.com/docker/docker/pkg/system"
  26. "github.com/docker/docker/pkg/truncindex"
  27. "github.com/docker/docker/runconfig"
  28. "github.com/vbatts/tar-split/tar/asm"
  29. "github.com/vbatts/tar-split/tar/storage"
  30. )
  31. // The type is used to protect pulling or building related image
  32. // layers from deleteing when filtered by dangling=true
  33. // The key of layers is the images ID which is pulling or building
  34. // The value of layers is a slice which hold layer IDs referenced to
  35. // pulling or building images
  36. type retainedLayers struct {
  37. layerHolders map[string]map[string]struct{} // map[layerID]map[sessionID]
  38. sync.Mutex
  39. }
  40. func (r *retainedLayers) Add(sessionID string, layerIDs []string) {
  41. r.Lock()
  42. defer r.Unlock()
  43. for _, layerID := range layerIDs {
  44. if r.layerHolders[layerID] == nil {
  45. r.layerHolders[layerID] = map[string]struct{}{}
  46. }
  47. r.layerHolders[layerID][sessionID] = struct{}{}
  48. }
  49. }
  50. func (r *retainedLayers) Delete(sessionID string, layerIDs []string) {
  51. r.Lock()
  52. defer r.Unlock()
  53. for _, layerID := range layerIDs {
  54. holders, ok := r.layerHolders[layerID]
  55. if !ok {
  56. continue
  57. }
  58. delete(holders, sessionID)
  59. if len(holders) == 0 {
  60. delete(r.layerHolders, layerID) // Delete any empty reference set.
  61. }
  62. }
  63. }
  64. func (r *retainedLayers) Exists(layerID string) bool {
  65. r.Lock()
  66. _, exists := r.layerHolders[layerID]
  67. r.Unlock()
  68. return exists
  69. }
  70. // A Graph is a store for versioned filesystem images and the relationship between them.
  71. type Graph struct {
  72. root string
  73. idIndex *truncindex.TruncIndex
  74. driver graphdriver.Driver
  75. imageMutex imageMutex // protect images in driver.
  76. retained *retainedLayers
  77. tarSplitDisabled bool
  78. }
  79. // file names for ./graph/<ID>/
  80. const (
  81. jsonFileName = "json"
  82. layersizeFileName = "layersize"
  83. digestFileName = "checksum"
  84. tarDataFileName = "tar-data.json.gz"
  85. )
  86. var (
  87. // ErrDigestNotSet is used when request the digest for a layer
  88. // but the layer has no digest value or content to compute the
  89. // the digest.
  90. ErrDigestNotSet = errors.New("digest is not set for layer")
  91. )
  92. // NewGraph instantiates a new graph at the given root path in the filesystem.
  93. // `root` will be created if it doesn't exist.
  94. func NewGraph(root string, driver graphdriver.Driver) (*Graph, error) {
  95. abspath, err := filepath.Abs(root)
  96. if err != nil {
  97. return nil, err
  98. }
  99. // Create the root directory if it doesn't exists
  100. if err := system.MkdirAll(root, 0700); err != nil {
  101. return nil, err
  102. }
  103. graph := &Graph{
  104. root: abspath,
  105. idIndex: truncindex.NewTruncIndex([]string{}),
  106. driver: driver,
  107. retained: &retainedLayers{layerHolders: make(map[string]map[string]struct{})},
  108. }
  109. // Windows does not currently support tarsplit functionality.
  110. if runtime.GOOS == "windows" {
  111. graph.tarSplitDisabled = true
  112. }
  113. if err := graph.restore(); err != nil {
  114. return nil, err
  115. }
  116. return graph, nil
  117. }
  118. // IsHeld returns whether the given layerID is being used by an ongoing pull or build.
  119. func (graph *Graph) IsHeld(layerID string) bool {
  120. return graph.retained.Exists(layerID)
  121. }
  122. func (graph *Graph) restore() error {
  123. dir, err := ioutil.ReadDir(graph.root)
  124. if err != nil {
  125. return err
  126. }
  127. var ids = []string{}
  128. for _, v := range dir {
  129. id := v.Name()
  130. if graph.driver.Exists(id) {
  131. ids = append(ids, id)
  132. }
  133. }
  134. graph.idIndex = truncindex.NewTruncIndex(ids)
  135. logrus.Debugf("Restored %d elements", len(ids))
  136. return nil
  137. }
  138. // IsNotExist detects whether an image exists by parsing the incoming error
  139. // message.
  140. func (graph *Graph) IsNotExist(err error, id string) bool {
  141. // FIXME: Implement error subclass instead of looking at the error text
  142. // Note: This is the way golang implements os.IsNotExists on Plan9
  143. return err != nil && (strings.Contains(strings.ToLower(err.Error()), "does not exist") || strings.Contains(strings.ToLower(err.Error()), "no such")) && strings.Contains(err.Error(), id)
  144. }
  145. // Exists returns true if an image is registered at the given id.
  146. // If the image doesn't exist or if an error is encountered, false is returned.
  147. func (graph *Graph) Exists(id string) bool {
  148. if _, err := graph.Get(id); err != nil {
  149. return false
  150. }
  151. return true
  152. }
  153. // Get returns the image with the given id, or an error if the image doesn't exist.
  154. func (graph *Graph) Get(name string) (*image.Image, error) {
  155. id, err := graph.idIndex.Get(name)
  156. if err != nil {
  157. return nil, fmt.Errorf("could not find image: %v", err)
  158. }
  159. img, err := graph.loadImage(id)
  160. if err != nil {
  161. return nil, err
  162. }
  163. if img.ID != id {
  164. return nil, fmt.Errorf("Image stored at '%s' has wrong id '%s'", id, img.ID)
  165. }
  166. if img.Size < 0 {
  167. size, err := graph.driver.DiffSize(img.ID, img.Parent)
  168. if err != nil {
  169. return nil, fmt.Errorf("unable to calculate size of image id %q: %s", img.ID, err)
  170. }
  171. img.Size = size
  172. if err := graph.saveSize(graph.imageRoot(id), img.Size); err != nil {
  173. return nil, err
  174. }
  175. }
  176. return img, nil
  177. }
  178. // Create creates a new image and registers it in the graph.
  179. func (graph *Graph) Create(layerData io.Reader, containerID, containerImage, comment, author string, containerConfig, config *runconfig.Config) (*image.Image, error) {
  180. img := &image.Image{
  181. ID: stringid.GenerateRandomID(),
  182. Comment: comment,
  183. Created: time.Now().UTC(),
  184. DockerVersion: dockerversion.VERSION,
  185. Author: author,
  186. Config: config,
  187. Architecture: runtime.GOARCH,
  188. OS: runtime.GOOS,
  189. }
  190. if containerID != "" {
  191. img.Parent = containerImage
  192. img.Container = containerID
  193. img.ContainerConfig = *containerConfig
  194. }
  195. if err := graph.Register(img, layerData); err != nil {
  196. return nil, err
  197. }
  198. return img, nil
  199. }
  200. // Register imports a pre-existing image into the graph.
  201. // Returns nil if the image is already registered.
  202. func (graph *Graph) Register(img *image.Image, layerData io.Reader) (err error) {
  203. if err := image.ValidateID(img.ID); err != nil {
  204. return err
  205. }
  206. // We need this entire operation to be atomic within the engine. Note that
  207. // this doesn't mean Register is fully safe yet.
  208. graph.imageMutex.Lock(img.ID)
  209. defer graph.imageMutex.Unlock(img.ID)
  210. // Skip register if image is already registered
  211. if graph.Exists(img.ID) {
  212. return nil
  213. }
  214. // The returned `error` must be named in this function's signature so that
  215. // `err` is not shadowed in this deferred cleanup.
  216. defer func() {
  217. // If any error occurs, remove the new dir from the driver.
  218. // Don't check for errors since the dir might not have been created.
  219. if err != nil {
  220. graph.driver.Remove(img.ID)
  221. }
  222. }()
  223. // Ensure that the image root does not exist on the filesystem
  224. // when it is not registered in the graph.
  225. // This is common when you switch from one graph driver to another
  226. if err := os.RemoveAll(graph.imageRoot(img.ID)); err != nil && !os.IsNotExist(err) {
  227. return err
  228. }
  229. // If the driver has this ID but the graph doesn't, remove it from the driver to start fresh.
  230. // (the graph is the source of truth).
  231. // Ignore errors, since we don't know if the driver correctly returns ErrNotExist.
  232. // (FIXME: make that mandatory for drivers).
  233. graph.driver.Remove(img.ID)
  234. tmp, err := graph.mktemp()
  235. defer os.RemoveAll(tmp)
  236. if err != nil {
  237. return fmt.Errorf("mktemp failed: %s", err)
  238. }
  239. // Create root filesystem in the driver
  240. if err := createRootFilesystemInDriver(graph, img); err != nil {
  241. return err
  242. }
  243. // Apply the diff/layer
  244. if err := graph.storeImage(img, layerData, tmp); err != nil {
  245. return err
  246. }
  247. // Commit
  248. if err := os.Rename(tmp, graph.imageRoot(img.ID)); err != nil {
  249. return err
  250. }
  251. graph.idIndex.Add(img.ID)
  252. return nil
  253. }
  254. func createRootFilesystemInDriver(graph *Graph, img *image.Image) error {
  255. if err := graph.driver.Create(img.ID, img.Parent); err != nil {
  256. return fmt.Errorf("Driver %s failed to create image rootfs %s: %s", graph.driver, img.ID, err)
  257. }
  258. return nil
  259. }
  260. // TempLayerArchive creates a temporary archive of the given image's filesystem layer.
  261. // The archive is stored on disk and will be automatically deleted as soon as has been read.
  262. // If output is not nil, a human-readable progress bar will be written to it.
  263. func (graph *Graph) TempLayerArchive(id string, sf *streamformatter.StreamFormatter, output io.Writer) (*archive.TempArchive, error) {
  264. image, err := graph.Get(id)
  265. if err != nil {
  266. return nil, err
  267. }
  268. tmp, err := graph.mktemp()
  269. if err != nil {
  270. return nil, err
  271. }
  272. a, err := graph.TarLayer(image)
  273. if err != nil {
  274. return nil, err
  275. }
  276. progressReader := progressreader.New(progressreader.Config{
  277. In: a,
  278. Out: output,
  279. Formatter: sf,
  280. Size: 0,
  281. NewLines: false,
  282. ID: stringid.TruncateID(id),
  283. Action: "Buffering to disk",
  284. })
  285. defer progressReader.Close()
  286. return archive.NewTempArchive(progressReader, tmp)
  287. }
  288. // mktemp creates a temporary sub-directory inside the graph's filesystem.
  289. func (graph *Graph) mktemp() (string, error) {
  290. dir := filepath.Join(graph.root, "_tmp", stringid.GenerateNonCryptoID())
  291. if err := system.MkdirAll(dir, 0700); err != nil {
  292. return "", err
  293. }
  294. return dir, nil
  295. }
  296. func (graph *Graph) newTempFile() (*os.File, error) {
  297. tmp, err := graph.mktemp()
  298. if err != nil {
  299. return nil, err
  300. }
  301. return ioutil.TempFile(tmp, "")
  302. }
  303. // Delete atomically removes an image from the graph.
  304. func (graph *Graph) Delete(name string) error {
  305. id, err := graph.idIndex.Get(name)
  306. if err != nil {
  307. return err
  308. }
  309. tmp, err := graph.mktemp()
  310. graph.idIndex.Delete(id)
  311. if err == nil {
  312. if err := os.Rename(graph.imageRoot(id), tmp); err != nil {
  313. // On err make tmp point to old dir and cleanup unused tmp dir
  314. os.RemoveAll(tmp)
  315. tmp = graph.imageRoot(id)
  316. }
  317. } else {
  318. // On err make tmp point to old dir for cleanup
  319. tmp = graph.imageRoot(id)
  320. }
  321. // Remove rootfs data from the driver
  322. graph.driver.Remove(id)
  323. // Remove the trashed image directory
  324. return os.RemoveAll(tmp)
  325. }
  326. // Map returns a list of all images in the graph, addressable by ID.
  327. func (graph *Graph) Map() map[string]*image.Image {
  328. images := make(map[string]*image.Image)
  329. graph.walkAll(func(image *image.Image) {
  330. images[image.ID] = image
  331. })
  332. return images
  333. }
  334. // walkAll iterates over each image in the graph, and passes it to a handler.
  335. // The walking order is undetermined.
  336. func (graph *Graph) walkAll(handler func(*image.Image)) {
  337. graph.idIndex.Iterate(func(id string) {
  338. if img, err := graph.Get(id); err != nil {
  339. return
  340. } else if handler != nil {
  341. handler(img)
  342. }
  343. })
  344. }
  345. // ByParent returns a lookup table of images by their parent.
  346. // If an image of key ID has 3 children images, then the value for key ID
  347. // will be a list of 3 images.
  348. // If an image has no children, it will not have an entry in the table.
  349. func (graph *Graph) ByParent() map[string][]*image.Image {
  350. byParent := make(map[string][]*image.Image)
  351. graph.walkAll(func(img *image.Image) {
  352. parent, err := graph.Get(img.Parent)
  353. if err != nil {
  354. return
  355. }
  356. if children, exists := byParent[parent.ID]; exists {
  357. byParent[parent.ID] = append(children, img)
  358. } else {
  359. byParent[parent.ID] = []*image.Image{img}
  360. }
  361. })
  362. return byParent
  363. }
  364. // HasChildren returns whether the given image has any child images.
  365. func (graph *Graph) HasChildren(img *image.Image) bool {
  366. return len(graph.ByParent()[img.ID]) > 0
  367. }
  368. // Retain keeps the images and layers that are in the pulling chain so that
  369. // they are not deleted. If not retained, they may be deleted by rmi.
  370. func (graph *Graph) Retain(sessionID string, layerIDs ...string) {
  371. graph.retained.Add(sessionID, layerIDs)
  372. }
  373. // Release removes the referenced image ID from the provided set of layers.
  374. func (graph *Graph) Release(sessionID string, layerIDs ...string) {
  375. graph.retained.Delete(sessionID, layerIDs)
  376. }
  377. // Heads returns all heads in the graph, keyed by id.
  378. // A head is an image which is not the parent of another image in the graph.
  379. func (graph *Graph) Heads() map[string]*image.Image {
  380. heads := make(map[string]*image.Image)
  381. byParent := graph.ByParent()
  382. graph.walkAll(func(image *image.Image) {
  383. // If it's not in the byParent lookup table, then
  384. // it's not a parent -> so it's a head!
  385. if _, exists := byParent[image.ID]; !exists {
  386. heads[image.ID] = image
  387. }
  388. })
  389. return heads
  390. }
  391. // TarLayer returns a tar archive of the image's filesystem layer.
  392. func (graph *Graph) TarLayer(img *image.Image) (arch io.ReadCloser, err error) {
  393. rdr, err := graph.assembleTarLayer(img)
  394. if err != nil {
  395. logrus.Debugf("[graph] TarLayer with traditional differ: %s", img.ID)
  396. return graph.driver.Diff(img.ID, img.Parent)
  397. }
  398. return rdr, nil
  399. }
  400. func (graph *Graph) imageRoot(id string) string {
  401. return filepath.Join(graph.root, id)
  402. }
  403. // loadImage fetches the image with the given id from the graph.
  404. func (graph *Graph) loadImage(id string) (*image.Image, error) {
  405. root := graph.imageRoot(id)
  406. // Open the JSON file to decode by streaming
  407. jsonSource, err := os.Open(jsonPath(root))
  408. if err != nil {
  409. return nil, err
  410. }
  411. defer jsonSource.Close()
  412. img := &image.Image{}
  413. dec := json.NewDecoder(jsonSource)
  414. // Decode the JSON data
  415. if err := dec.Decode(img); err != nil {
  416. return nil, err
  417. }
  418. if err := image.ValidateID(img.ID); err != nil {
  419. return nil, err
  420. }
  421. if buf, err := ioutil.ReadFile(filepath.Join(root, layersizeFileName)); err != nil {
  422. if !os.IsNotExist(err) {
  423. return nil, err
  424. }
  425. // If the layersize file does not exist then set the size to a negative number
  426. // because a layer size of 0 (zero) is valid
  427. img.Size = -1
  428. } else {
  429. // Using Atoi here instead would temporarily convert the size to a machine
  430. // dependent integer type, which causes images larger than 2^31 bytes to
  431. // display negative sizes on 32-bit machines:
  432. size, err := strconv.ParseInt(string(buf), 10, 64)
  433. if err != nil {
  434. return nil, err
  435. }
  436. img.Size = int64(size)
  437. }
  438. return img, nil
  439. }
  440. // saveSize stores the `size` in the provided graph `img` directory `root`.
  441. func (graph *Graph) saveSize(root string, size int64) error {
  442. if err := ioutil.WriteFile(filepath.Join(root, layersizeFileName), []byte(strconv.FormatInt(size, 10)), 0600); err != nil {
  443. return fmt.Errorf("Error storing image size in %s/%s: %s", root, layersizeFileName, err)
  444. }
  445. return nil
  446. }
  447. // SetDigest sets the digest for the image layer to the provided value.
  448. func (graph *Graph) SetDigest(id string, dgst digest.Digest) error {
  449. graph.imageMutex.Lock(id)
  450. defer graph.imageMutex.Unlock(id)
  451. root := graph.imageRoot(id)
  452. if err := ioutil.WriteFile(filepath.Join(root, digestFileName), []byte(dgst.String()), 0600); err != nil {
  453. return fmt.Errorf("Error storing digest in %s/%s: %s", root, digestFileName, err)
  454. }
  455. return nil
  456. }
  457. // GetDigest gets the digest for the provide image layer id.
  458. func (graph *Graph) GetDigest(id string) (digest.Digest, error) {
  459. graph.imageMutex.Lock(id)
  460. defer graph.imageMutex.Unlock(id)
  461. root := graph.imageRoot(id)
  462. cs, err := ioutil.ReadFile(filepath.Join(root, digestFileName))
  463. if err != nil {
  464. if os.IsNotExist(err) {
  465. return "", ErrDigestNotSet
  466. }
  467. return "", err
  468. }
  469. return digest.ParseDigest(string(cs))
  470. }
  471. // RawJSON returns the JSON representation for an image as a byte array.
  472. func (graph *Graph) RawJSON(id string) ([]byte, error) {
  473. root := graph.imageRoot(id)
  474. buf, err := ioutil.ReadFile(jsonPath(root))
  475. if err != nil {
  476. return nil, fmt.Errorf("Failed to read json for image %s: %s", id, err)
  477. }
  478. return buf, nil
  479. }
  480. func jsonPath(root string) string {
  481. return filepath.Join(root, jsonFileName)
  482. }
  483. // storeImage stores file system layer data for the given image to the
  484. // graph's storage driver. Image metadata is stored in a file
  485. // at the specified root directory.
  486. func (graph *Graph) storeImage(img *image.Image, layerData io.Reader, root string) (err error) {
  487. // Store the layer. If layerData is not nil, unpack it into the new layer
  488. if layerData != nil {
  489. if err := graph.disassembleAndApplyTarLayer(img, layerData, root); err != nil {
  490. return err
  491. }
  492. }
  493. if err := graph.saveSize(root, img.Size); err != nil {
  494. return err
  495. }
  496. f, err := os.OpenFile(jsonPath(root), os.O_CREATE|os.O_WRONLY|os.O_TRUNC, os.FileMode(0600))
  497. if err != nil {
  498. return err
  499. }
  500. defer f.Close()
  501. return json.NewEncoder(f).Encode(img)
  502. }
  503. func (graph *Graph) disassembleAndApplyTarLayer(img *image.Image, layerData io.Reader, root string) (err error) {
  504. var ar io.Reader
  505. if graph.tarSplitDisabled {
  506. ar = layerData
  507. } else {
  508. // this is saving the tar-split metadata
  509. mf, err := os.OpenFile(filepath.Join(root, tarDataFileName), os.O_CREATE|os.O_WRONLY|os.O_TRUNC, os.FileMode(0600))
  510. if err != nil {
  511. return err
  512. }
  513. mfz := gzip.NewWriter(mf)
  514. metaPacker := storage.NewJSONPacker(mfz)
  515. defer mf.Close()
  516. defer mfz.Close()
  517. inflatedLayerData, err := archive.DecompressStream(layerData)
  518. if err != nil {
  519. return err
  520. }
  521. // we're passing nil here for the file putter, because the ApplyDiff will
  522. // handle the extraction of the archive
  523. rdr, err := asm.NewInputTarStream(inflatedLayerData, metaPacker, nil)
  524. if err != nil {
  525. return err
  526. }
  527. ar = archive.Reader(rdr)
  528. }
  529. if img.Size, err = graph.driver.ApplyDiff(img.ID, img.Parent, ar); err != nil {
  530. return err
  531. }
  532. return nil
  533. }
  534. func (graph *Graph) assembleTarLayer(img *image.Image) (io.ReadCloser, error) {
  535. root := graph.imageRoot(img.ID)
  536. mFileName := filepath.Join(root, tarDataFileName)
  537. mf, err := os.Open(mFileName)
  538. if err != nil {
  539. if !os.IsNotExist(err) {
  540. logrus.Errorf("failed to open %q: %s", mFileName, err)
  541. }
  542. return nil, err
  543. }
  544. pR, pW := io.Pipe()
  545. // this will need to be in a goroutine, as we are returning the stream of a
  546. // tar archive, but can not close the metadata reader early (when this
  547. // function returns)...
  548. go func() {
  549. defer mf.Close()
  550. // let's reassemble!
  551. logrus.Debugf("[graph] TarLayer with reassembly: %s", img.ID)
  552. mfz, err := gzip.NewReader(mf)
  553. if err != nil {
  554. pW.CloseWithError(fmt.Errorf("[graph] error with %s: %s", mFileName, err))
  555. return
  556. }
  557. defer mfz.Close()
  558. // get our relative path to the container
  559. fsLayer, err := graph.driver.Get(img.ID, "")
  560. if err != nil {
  561. pW.CloseWithError(err)
  562. return
  563. }
  564. defer graph.driver.Put(img.ID)
  565. metaUnpacker := storage.NewJSONUnpacker(mfz)
  566. fileGetter := storage.NewPathFileGetter(fsLayer)
  567. logrus.Debugf("[graph] %s is at %q", img.ID, fsLayer)
  568. ots := asm.NewOutputTarStream(fileGetter, metaUnpacker)
  569. defer ots.Close()
  570. if _, err := io.Copy(pW, ots); err != nil {
  571. pW.CloseWithError(err)
  572. return
  573. }
  574. pW.Close()
  575. }()
  576. return pR, nil
  577. }