Skip to content

Commit

Permalink
feat(cmds): files: add new-root command to change the MFS root
Browse files Browse the repository at this point in the history
  • Loading branch information
schomatis committed Jan 10, 2022
1 parent 91c5265 commit 5aa084e
Show file tree
Hide file tree
Showing 2 changed files with 118 additions and 14 deletions.
119 changes: 109 additions & 10 deletions core/commands/files.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,13 @@ import (
humanize "github.com/dustin/go-humanize"
"github.com/ipfs/go-ipfs/core"
"github.com/ipfs/go-ipfs/core/commands/cmdenv"
"github.com/ipfs/go-ipfs/core/node"
"github.com/ipfs/go-ipfs/repo/fsrepo"

bservice "github.com/ipfs/go-blockservice"
cid "github.com/ipfs/go-cid"
cidenc "github.com/ipfs/go-cidutil/cidenc"
"github.com/ipfs/go-datastore"
cmds "github.com/ipfs/go-ipfs-cmds"
offline "github.com/ipfs/go-ipfs-exchange-offline"
ipld "github.com/ipfs/go-ipld-format"
Expand Down Expand Up @@ -70,16 +73,17 @@ operations.
cmds.BoolOption(filesFlushOptionName, "f", "Flush target and ancestors after write.").WithDefault(true),
},
Subcommands: map[string]*cmds.Command{
"read": filesReadCmd,
"write": filesWriteCmd,
"mv": filesMvCmd,
"cp": filesCpCmd,
"ls": filesLsCmd,
"mkdir": filesMkdirCmd,
"stat": filesStatCmd,
"rm": filesRmCmd,
"flush": filesFlushCmd,
"chcid": filesChcidCmd,
"read": filesReadCmd,
"write": filesWriteCmd,
"mv": filesMvCmd,
"cp": filesCpCmd,
"ls": filesLsCmd,
"mkdir": filesMkdirCmd,
"stat": filesStatCmd,
"rm": filesRmCmd,
"flush": filesFlushCmd,
"chcid": filesChcidCmd,
"replace-root": filesReplaceRoot,
},
}

Expand Down Expand Up @@ -1134,6 +1138,101 @@ Remove files or directories.
},
}

var filesReplaceRoot = &cmds.Command{
Helptext: cmds.HelpText{
// FIXME(BLOCKING): Somewhere around we should flag that this is an advanced command
// that you normally wouldn't need except in case of filesystem corruption. Where?
Tagline: "Replace the filesystem root.",
ShortDescription: `
Replace the filesystem root with another CID.
$ ipfs init
[...]
ipfs cat /ipfs/QmQPeNsJPyVWPFDVHb77w8G42Fvo15z4bG2X8D2GhfbSXc/readme # <- init dir
[...]
$ ipfs files ls /
[nothing; empty dir]
$ ipfs files stat / --hash
QmUNLLsPACCz1vLxQVkXqqLX5R1X345qqfHbsf67hvA3Nn
# FIXME(BLOCKING): Need the following to somehow "start" the root dir, otherwise
# the replace-root will fail to find the '/local/filesroot' entry in the repo
$ ipfs files cp /ipfs/QmQPeNsJPyVWPFDVHb77w8G42Fvo15z4bG2X8D2GhfbSXc/readme /file
$ GOLOG_LOG_LEVEL="info" ipfs files replace-root QmQPeNsJPyVWPFDVHb77w8G42Fvo15z4bG2X8D2GhfbSXc # init dir from before
[...] replaced MFS files root QmQPeNsJPyVWPFDVHb77w8G42Fvo15z4bG2X8D2GhfbSXc [...]
[here we have the CID of the old root to "undo" in case of error]
$ ipfs files ls /
[contents from init dir now set as the root of the filesystem]
about
contact
help
ping
quick-start
readme
security-notes
$ ipfs files replace-root QmUNLLsPACCz1vLxQVkXqqLX5R1X345qqfHbsf67hvA3Nn # empty dir from init
$ ipfs files ls /
[nothing; empty dir]
`,
},
Arguments: []cmds.Argument{
cmds.StringArg("new-root", true, false, "New root to use."),
},
// FIXME(BLOCKING): Can/should we do this with the repo running?
NoRemote: true,
Run: func(req *cmds.Request, res cmds.ResponseEmitter, env cmds.Environment) error {
if len(req.Arguments) < 1 {
fmt.Errorf("new root not provided")
}
newFilesRootCid, err := cid.Parse(req.Arguments[0])
if err != nil {
return fmt.Errorf("files root argument provided %s is not a valid CID: %w", req.Arguments[0], err)

}
// FIXME(BLOCKING): Check (a) that this CID exists *locally* and (b)
// that it's a dir.

cfgRoot, err := cmdenv.GetConfigRoot(env)
if err != nil {
return err
}

repo, err := fsrepo.Open(cfgRoot)
if err != nil {
return err
}
localDS := repo.Datastore()
defer repo.Close()

filesRootBytes, err := localDS.Get(req.Context, node.FilesRootDatastoreKey)
if err == datastore.ErrNotFound {
return fmt.Errorf("MFS files root %s not found in repo", node.FilesRootDatastoreKey)
} else if err != nil {
return fmt.Errorf("looking for MFS files root: %w", err)
}
filesRootCid, err := cid.Cast(filesRootBytes)
if err != nil {
return fmt.Errorf("casting MFS files root %s as CID: %w", filesRootBytes, err)
}

err = localDS.Put(req.Context, node.FilesRootDatastoreKey, newFilesRootCid.Bytes())
if err != nil {
return fmt.Errorf("storing new files root: %w", err)
}
// FIXME(BLOCKING): Do we need this if we're closing the repo at the end
// of the command? Likely not.
err = localDS.Sync(req.Context, node.FilesRootDatastoreKey)
if err != nil {
return fmt.Errorf("syncing new files root: %w", err)
}

log.Infof("replaced MFS files root %s with %s", filesRootCid, newFilesRootCid)

return nil
},
}

func getPrefixNew(req *cmds.Request) (cid.Builder, error) {
cidVer, cidVerSet := req.Options[filesCidVersionOptionName].(int)
hashFunStr, hashFunSet := req.Options[filesHashOptionName].(string)
Expand Down
13 changes: 9 additions & 4 deletions core/node/core.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,12 @@ import (
"github.com/ipfs/go-ipfs/repo"
)

var FilesRootDatastoreKey datastore.Key

func init() {
FilesRootDatastoreKey = datastore.NewKey("/local/filesroot")
}

// BlockService creates new blockservice which provides an interface to fetch content-addressable blocks
func BlockService(lc fx.Lifecycle, bs blockstore.Blockstore, rem exchange.Interface) blockservice.BlockService {
bsvc := blockservice.New(bs, rem)
Expand Down Expand Up @@ -110,7 +116,6 @@ func Dag(bs blockservice.BlockService) format.DAGService {

// Files loads persisted MFS root
func Files(mctx helpers.MetricsCtx, lc fx.Lifecycle, repo repo.Repo, dag format.DAGService) (*mfs.Root, error) {
dsk := datastore.NewKey("/local/filesroot")
pf := func(ctx context.Context, c cid.Cid) error {
rootDS := repo.Datastore()
if err := rootDS.Sync(ctx, blockstore.BlockPrefix); err != nil {
Expand All @@ -120,15 +125,15 @@ func Files(mctx helpers.MetricsCtx, lc fx.Lifecycle, repo repo.Repo, dag format.
return err
}

if err := rootDS.Put(ctx, dsk, c.Bytes()); err != nil {
if err := rootDS.Put(ctx, FilesRootDatastoreKey, c.Bytes()); err != nil {
return err
}
return rootDS.Sync(ctx, dsk)
return rootDS.Sync(ctx, FilesRootDatastoreKey)
}

var nd *merkledag.ProtoNode
ctx := helpers.LifecycleCtx(mctx, lc)
val, err := repo.Datastore().Get(ctx, dsk)
val, err := repo.Datastore().Get(ctx, FilesRootDatastoreKey)

switch {
case err == datastore.ErrNotFound || val == nil:
Expand Down

0 comments on commit 5aa084e

Please sign in to comment.