mirror of
https://github.com/minio/mc.git
synced 2025-11-12 01:02:26 +03:00
202 lines
5.4 KiB
Go
202 lines
5.4 KiB
Go
/*
|
|
* Minio Client (C) 2014, 2015 Minio, Inc.
|
|
*
|
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
* you may not use this file except in compliance with the License.
|
|
* You may obtain a copy of the License at
|
|
*
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
*
|
|
* Unless required by applicable law or agreed to in writing, software
|
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
* See the License for the specific language governing permissions and
|
|
* limitations under the License.
|
|
*/
|
|
|
|
package main
|
|
|
|
import (
|
|
"os"
|
|
"strings"
|
|
|
|
"github.com/minio/cli"
|
|
"github.com/minio/mc/pkg/client"
|
|
"github.com/minio/minio-xl/pkg/probe"
|
|
)
|
|
|
|
// remove a file or folder.
|
|
var rmCmd = cli.Command{
|
|
Name: "rm",
|
|
Usage: "Remove file or bucket.",
|
|
Action: mainRm,
|
|
CustomHelpTemplate: `NAME:
|
|
mc {{.Name}} - {{.Usage}}
|
|
|
|
USAGE:
|
|
mc {{.Name}} TARGET
|
|
|
|
EXAMPLES:
|
|
1. Remove a file on Cloud storage
|
|
$ mc {{.Name}} https://s3.amazonaws.com/jazz-songs/louis/file01.mp3
|
|
|
|
2. Remove a folder recursively on Cloud storage
|
|
$ mc {{.Name}} force https://s3.amazonaws.com/jazz-songs/louis/...
|
|
|
|
3. Remove a bucket on Minio cloud storage
|
|
$ mc {{.Name}} https://play.minio.io:9000/mongodb-backup
|
|
|
|
4. Remove a bucket on Cloud storage recursively
|
|
$ mc {{.Name}} force https://s3.amazonaws.com/jazz-songs/...
|
|
|
|
5. Remove a file on local filesystem:
|
|
$ mc {{.Name}} march/expenses.doc
|
|
|
|
6. Remove a file named "force" on local filesystem:
|
|
$ mc {{.Name}} force force
|
|
`,
|
|
}
|
|
|
|
func rmList(url string) (<-chan string, *probe.Error) {
|
|
clnt, err := url2Client(url)
|
|
if err != nil {
|
|
errorIf(err.Trace(), "Unable to get client object for "+url)
|
|
return nil, err.Trace()
|
|
}
|
|
in := clnt.List(true)
|
|
out := make(chan string)
|
|
|
|
var depthFirst func(currentDir string) (*client.Content, bool)
|
|
|
|
depthFirst = func(currentDir string) (*client.Content, bool) {
|
|
entry, ok := <-in
|
|
for {
|
|
if !ok || !strings.HasPrefix(entry.Content.Name, currentDir) {
|
|
return entry.Content, ok
|
|
}
|
|
if entry.Content.Type.IsRegular() {
|
|
out <- entry.Content.Name
|
|
}
|
|
if entry.Content.Type.IsDir() {
|
|
var content *client.Content
|
|
content, ok = depthFirst(entry.Content.Name)
|
|
out <- entry.Content.Name
|
|
entry = client.ContentOnChannel{Content: content}
|
|
continue
|
|
}
|
|
entry, ok = <-in
|
|
}
|
|
}
|
|
|
|
go func() {
|
|
depthFirst("")
|
|
close(out)
|
|
}()
|
|
return out, nil
|
|
}
|
|
|
|
func rm(url string) {
|
|
clnt, err := url2Client(url)
|
|
if err != nil {
|
|
errorIf(err.Trace(), "Unable to get client object for "+url)
|
|
return
|
|
}
|
|
err = clnt.Remove()
|
|
errorIf(err.Trace(), "Unable to remove "+url)
|
|
}
|
|
|
|
func rmAll(url string) {
|
|
clnt, err := url2Client(url)
|
|
if err != nil {
|
|
errorIf(err.Trace(), "Unable to get client object for "+url)
|
|
return
|
|
}
|
|
urlPartial1 := url2Dir(url)
|
|
out, err := rmList(url)
|
|
if err != nil {
|
|
errorIf(err.Trace(), "Unable to List "+url)
|
|
return
|
|
}
|
|
for urlPartial2 := range out {
|
|
urlFull := urlPartial1 + urlPartial2
|
|
newclnt, e := url2Client(urlFull)
|
|
if e != nil {
|
|
errorIf(e, "Unable to create client object : "+urlFull)
|
|
continue
|
|
}
|
|
err = newclnt.Remove()
|
|
errorIf(err, "Unable to remove : "+urlFull)
|
|
}
|
|
_, err = clnt.Stat()
|
|
if err == nil {
|
|
err = clnt.Remove()
|
|
errorIf(err, "Unable to remove : "+clnt.URL().String())
|
|
}
|
|
}
|
|
|
|
func checkRmSyntax(ctx *cli.Context) {
|
|
args, err := args2URLs(ctx.Args())
|
|
fatalIf(err.Trace(), "args2URL failed")
|
|
var force bool
|
|
if len(args) == 0 {
|
|
cli.ShowCommandHelpAndExit(ctx, "rm", 1) // last argument is exit code.
|
|
}
|
|
if len(args) == 1 && args[0] == "force" {
|
|
cli.ShowCommandHelpAndExit(ctx, "rm", 1)
|
|
}
|
|
if args[0] == "force" {
|
|
force = true
|
|
args = args[1:]
|
|
}
|
|
// If input validation fails then provide context sensitive help without displaying generic help message.
|
|
// The context sensitive help is shown per argument instead of all arguments to keep the help display
|
|
// as well as the code simple. Also most of the times there will be just one arg
|
|
for _, arg := range args {
|
|
url := client.NewURL(arg)
|
|
if strings.HasSuffix(arg, string(url.Separator)) {
|
|
helpStr := "Usage : mc rm force " + arg + recursiveSeparator
|
|
fatalIf(errDummy().Trace(), helpStr)
|
|
}
|
|
if isURLRecursive(arg) && !force {
|
|
helpStr := "Usage : mc rm force " + arg
|
|
fatalIf(errDummy().Trace(), helpStr)
|
|
}
|
|
if url.Type == client.Filesystem {
|
|
// For local file system we don't support "mc rm fileprefix..." just like the behavior of "mc ls fileprefix..."
|
|
// So recursive delete has to be of the form "mc rm dir1/dir2/..."
|
|
isRecursive := isURLRecursive(arg)
|
|
path := stripRecursiveURL(arg)
|
|
if isRecursive && (strings.HasSuffix(path, string(url.Separator)) == false) {
|
|
helpStr := "Usage : mc rm force " + path + string(url.Separator) + recursiveSeparator
|
|
fatalIf(errDummy().Trace(), helpStr)
|
|
}
|
|
_, content, err := url2Stat(path)
|
|
if err != nil {
|
|
fatalIf(err.Trace(), "url2stat error on "+arg)
|
|
}
|
|
if content.Type&os.ModeDir != 0 && !isRecursive {
|
|
helpStr := "Usage : mc rm force " + arg + string(url.Separator) + recursiveSeparator
|
|
fatalIf(errDummy().Trace(), helpStr)
|
|
}
|
|
continue
|
|
}
|
|
}
|
|
}
|
|
|
|
func mainRm(ctx *cli.Context) {
|
|
checkRmSyntax(ctx)
|
|
args, err := args2URLs(ctx.Args())
|
|
fatalIf(err.Trace(), "args2URL failed")
|
|
if args[0] == "force" {
|
|
args = args[1:]
|
|
}
|
|
for _, arg := range args {
|
|
if isURLRecursive(arg) {
|
|
url := stripRecursiveURL(arg)
|
|
rmAll(url)
|
|
} else {
|
|
rm(arg)
|
|
}
|
|
}
|
|
}
|