diff --git a/core/commands/block.go b/core/commands/block.go index e910e6fc74d..192bd22593e 100644 --- a/core/commands/block.go +++ b/core/commands/block.go @@ -9,8 +9,10 @@ import ( "strings" "github.com/ipfs/go-ipfs/blocks" + bs "github.com/ipfs/go-ipfs/blocks/blockstore" key "github.com/ipfs/go-ipfs/blocks/key" cmds "github.com/ipfs/go-ipfs/commands" + "github.com/ipfs/go-ipfs/pin" mh "gx/ipfs/QmYf7ng2hG5XBtJA3tN34DQ2GUN5HNksEw1rLDkmr6vGku/go-multihash" u "gx/ipfs/QmZNVWh8LLjAavuQ2JXuFmuYH3C11xo988vSgp7UQrTRj1/go-ipfs-util" ) @@ -38,6 +40,7 @@ multihash. "stat": blockStatCmd, "get": blockGetCmd, "put": blockPutCmd, + "rm": blockRmCmd, }, } @@ -185,3 +188,99 @@ func getBlockForKey(req cmds.Request, skey string) (blocks.Block, error) { log.Debugf("ipfs block: got block with key: %q", b.Key()) return b, nil } + +var blockRmCmd = &cmds.Command{ + Helptext: cmds.HelpText{ + Tagline: "Remove IPFS block(s).", + ShortDescription: ` +'ipfs block put' is a plumbing command for removing raw ipfs blocks. +It takes a list of base58 encoded multihashs to remove. +`, + }, + Arguments: []cmds.Argument{ + cmds.StringArg("hash", true, true, "Bash58 encoded multihash of block(s) to remove."), + }, + Options: []cmds.Option{ + cmds.BoolOption("ignore-pins", "Ignore pins.").Default(false), + }, + Run: func(req cmds.Request, res cmds.Response) { + ignorePins, _, err := req.Option("ignore-pins").Bool() + if err != nil { + res.SetError(err, cmds.ErrNormal) + return + } + n, err := req.InvocContext().GetNode() + if err != nil { + res.SetError(err, cmds.ErrNormal) + return + } + hashes := req.Arguments() + keys := make([]key.Key, 0, len(hashes)) + for _, hash := range hashes { + k := key.B58KeyDecode(hash) + keys = append(keys, k) + } + rdr, wtr := io.Pipe() + go func() { + pinning := n.Pinning + if ignorePins { + pinning = nil + } + err := rmBlocks(n.Blockstore, pinning, wtr, keys) + if err != nil { + wtr.CloseWithError(fmt.Errorf("Some blocks not deleted: %s", err)) + } else { + wtr.Close() + } + }() + res.SetOutput(rdr) + return + }, + Marshalers: cmds.MarshalerMap{ + cmds.Text: func(res cmds.Response) (io.Reader, error) { + return res.(io.Reader), nil + }, + }, +} + +type noopLocker struct{} + +func (noopLocker) Unlock() {} + +// pins may be nil +func rmBlocks(blocks bs.GCBlockstore, pins pin.Pinner, out io.Writer, keys []key.Key) error { + var unlocker bs.Unlocker = noopLocker{} + defer unlocker.Unlock() + if pins != nil { + // Need to make sure that some operation that is + // finishing with a pin is ocurr simultaneously. + unlocker = blocks.GCLock() + err := checkIfPinned(pins, keys) + if err != nil { + return err + } + } + for _, k := range keys { + err := blocks.DeleteBlock(k) + if err != nil { + return fmt.Errorf("%s: %s", k, err) + } + if out != nil { + fmt.Fprintf(out, "deleted %s\n", k) + } + } + return nil +} + +func checkIfPinned(pins pin.Pinner, keys []key.Key) error { + for _, k := range keys { + reason, pinned, err := pins.IsPinned(k) + if err != nil { + return err + } + if pinned { + return fmt.Errorf("%s pinned via %s", k, reason) + } + } + return nil +} diff --git a/test/sharness/t0050-block.sh b/test/sharness/t0050-block.sh index 8753322e0e7..d8245f71729 100755 --- a/test/sharness/t0050-block.sh +++ b/test/sharness/t0050-block.sh @@ -33,10 +33,77 @@ test_expect_success "'ipfs block stat' succeeds" ' ipfs block stat $HASH >actual_stat ' -test_expect_success "'ipfs block get' output looks good" ' +test_expect_success "'ipfs block stat' output looks good" ' echo "Key: $HASH" >expected_stat && echo "Size: 12" >>expected_stat && test_cmp expected_stat actual_stat ' +test_expect_success "'ipfs block rm' succeeds" ' + ipfs block rm $HASH >actual_rm +' + +test_expect_success "'ipfs block rm' output looks good" ' + echo "deleted $HASH" > expected_rm && + test_cmp expected_rm actual_rm +' + +test_expect_success "'ipfs block rm' block actually removed" ' + test_must_fail ipfs block stat $HASH +' + +DIRHASH=QmdWmVmM6W2abTgkEfpbtA1CJyTWS2rhuUB9uP1xV8Uwtf +FILE1HASH=Qmae3RedM7SNkWGsdzYzsr6svmsFdsva4WoTvYYsWhUSVz +FILE2HASH=QmUtkGLvPf63NwVzLPKPUYgwhn8ZYPWF6vKWN3fZ2amfJF +FILE3HASH=Qmesmmf1EEG1orJb6XdK6DabxexsseJnCfw8pqWgonbkoj + +test_expect_success "add and pin directory" ' + mkdir adir && + echo "file1" > adir/file1 && + echo "file2" > adir/file2 && + echo "file3" > adir/file3 && + ipfs add -r adir + ipfs pin add -r $DIRHASH +' + +test_expect_success "can't remove pinned block" ' + test_must_fail ipfs block rm $DIRHASH 2> block_rm_err +' + +test_expect_success "can't remove pinned block: output looks good" ' + grep -q "$DIRHASH pinned via recursive" block_rm_err +' + +test_expect_success "can't remove indirectly pinned block" ' + test_must_fail ipfs block rm $FILE1HASH 2> block_rm_err +' + +test_expect_success "can't remove indirectly pinned block: output looks good" ' + grep -q "$FILE1HASH pinned via $DIRHASH" block_rm_err +' + +test_expect_success "multi-block 'ipfs block rm --ignore-pins' succeeds" ' + ipfs block rm --ignore-pins $DIRHASH >actual_rm +' + +test_expect_success "multi-block 'ipfs block rm --ignore-pins' output looks good" ' + echo "deleted $DIRHASH" > expected_rm && + test_cmp expected_rm actual_rm +' + +test_expect_success "fix up pins" ' + ipfs pin rm -r $DIRHASH +' + +test_expect_success "multi-block 'ipfs block rm' succeeds" ' + ipfs block rm $FILE1HASH $FILE2HASH $FILE3HASH > actual_rm +' + +test_expect_success "multi-block 'ipfs block rm' output looks good" ' + echo "deleted $FILE1HASH" > expected_rm && + echo "deleted $FILE2HASH" >> expected_rm && + echo "deleted $FILE3HASH" >> expected_rm && + test_cmp expected_rm actual_rm +' + test_done