github.com/maobaolong/goofys@v0.24.1-0.20200717030821-b50ef2d29ddf/internal/flags.go (about) 1 // Copyright 2015 - 2017 Ka-Hing Cheung 2 // Copyright 2015 - 2017 Google Inc. All Rights Reserved. 3 // 4 // Licensed under the Apache License, Version 2.0 (the "License"); 5 // you may not use this file except in compliance with the License. 6 // You may obtain a copy of the License at 7 // 8 // http://www.apache.org/licenses/LICENSE-2.0 9 // 10 // Unless required by applicable law or agreed to in writing, software 11 // distributed under the License is distributed on an "AS IS" BASIS, 12 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 // See the License for the specific language governing permissions and 14 // limitations under the License. 15 16 package internal 17 18 import ( 19 . "github.com/kahing/goofys/api/common" 20 21 "fmt" 22 "io" 23 "io/ioutil" 24 "os" 25 "os/exec" 26 "strings" 27 "text/tabwriter" 28 "text/template" 29 "time" 30 31 "github.com/urfave/cli" 32 ) 33 34 var flagCategories map[string]string 35 36 // Set up custom help text for goofys; in particular the usage section. 37 func filterCategory(flags []cli.Flag, category string) (ret []cli.Flag) { 38 for _, f := range flags { 39 if flagCategories[f.GetName()] == category { 40 ret = append(ret, f) 41 } 42 } 43 return 44 } 45 46 func init() { 47 cli.AppHelpTemplate = `NAME: 48 {{.Name}} - {{.Usage}} 49 50 USAGE: 51 {{.Name}} {{if .Flags}}[global options]{{end}} bucket[:prefix] mountpoint 52 {{if .Version}} 53 VERSION: 54 {{.Version}} 55 {{end}}{{if len .Authors}} 56 AUTHOR(S): 57 {{range .Authors}}{{ . }}{{end}} 58 {{end}}{{if .Commands}} 59 COMMANDS: 60 {{range .Commands}}{{join .Names ", "}}{{ "\t" }}{{.Usage}} 61 {{end}}{{end}}{{if .Flags}} 62 GLOBAL OPTIONS: 63 {{range category .Flags ""}}{{.}} 64 {{end}} 65 TUNING OPTIONS: 66 {{range category .Flags "tuning"}}{{.}} 67 {{end}} 68 AWS S3 OPTIONS: 69 {{range category .Flags "aws"}}{{.}} 70 {{end}} 71 MISC OPTIONS: 72 {{range category .Flags "misc"}}{{.}} 73 {{end}}{{end}}{{if .Copyright }} 74 COPYRIGHT: 75 {{.Copyright}} 76 {{end}} 77 ` 78 } 79 80 var VersionHash string 81 82 func NewApp() (app *cli.App) { 83 uid, gid := MyUserAndGroup() 84 85 s3Default := (&S3Config{}).Init() 86 87 app = &cli.App{ 88 Name: "goofys", 89 Version: "0.24.0-" + VersionHash, 90 Usage: "Mount an S3 bucket locally", 91 HideHelp: true, 92 Writer: os.Stderr, 93 Flags: []cli.Flag{ 94 95 cli.BoolFlag{ 96 Name: "help, h", 97 Usage: "Print this help text and exit successfully.", 98 }, 99 100 ///////////////////////// 101 // File system 102 ///////////////////////// 103 104 cli.StringSliceFlag{ 105 Name: "o", 106 Usage: "Additional system-specific mount options. Be careful!", 107 }, 108 109 cli.StringFlag{ 110 Name: "cache", 111 Usage: "Directory to use for data cache. " + 112 "Requires catfs and `-o allow_other'. " + 113 "Can also pass in other catfs options " + 114 "(ex: --cache \"--free:10%:$HOME/cache\") (default: off)", 115 }, 116 117 cli.IntFlag{ 118 Name: "dir-mode", 119 Value: 0755, 120 Usage: "Permission bits for directories. (default: 0755)", 121 }, 122 123 cli.IntFlag{ 124 Name: "file-mode", 125 Value: 0644, 126 Usage: "Permission bits for files. (default: 0644)", 127 }, 128 129 cli.IntFlag{ 130 Name: "uid", 131 Value: uid, 132 Usage: "UID owner of all inodes.", 133 }, 134 135 cli.IntFlag{ 136 Name: "gid", 137 Value: gid, 138 Usage: "GID owner of all inodes.", 139 }, 140 141 cli.IntFlag{ 142 Name: "max-readahead", 143 Value: 400 * 1024 * 1024, 144 Usage: "max readahead size.", 145 }, 146 147 cli.IntFlag{ 148 Name: "readahead-chunk", 149 Value: 20 * 1024 * 1024, 150 Usage: "readahead chunk size.", 151 }, 152 153 ///////////////////////// 154 // S3 155 ///////////////////////// 156 157 cli.StringFlag{ 158 Name: "endpoint", 159 Value: "", 160 Usage: "The non-AWS endpoint to connect to." + 161 " Possible values: http://127.0.0.1:8081/", 162 }, 163 164 cli.StringFlag{ 165 Name: "region", 166 Value: s3Default.Region, 167 Usage: "The region to connect to. Usually this is auto-detected." + 168 " Possible values: us-east-1, us-west-1, us-west-2, eu-west-1, " + 169 "eu-central-1, ap-southeast-1, ap-southeast-2, ap-northeast-1, " + 170 "sa-east-1, cn-north-1", 171 }, 172 173 cli.BoolFlag{ 174 Name: "requester-pays", 175 Usage: "Whether to allow access to requester-pays buckets (default: off)", 176 }, 177 178 cli.StringFlag{ 179 Name: "storage-class", 180 Value: s3Default.StorageClass, 181 Usage: "The type of storage to use when writing objects." + 182 " Possible values: REDUCED_REDUNDANCY, STANDARD, STANDARD_IA.", 183 }, 184 185 cli.StringFlag{ 186 Name: "profile", 187 Usage: "Use a named profile from $HOME/.aws/credentials instead of \"default\"", 188 }, 189 190 cli.BoolFlag{ 191 Name: "use-content-type", 192 Usage: "Set Content-Type according to file extension and /etc/mime.types (default: off)", 193 }, 194 195 /// http://docs.aws.amazon.com/AmazonS3/latest/API/RESTObjectPUT.html 196 /// See http://docs.aws.amazon.com/AmazonS3/latest/dev/UsingServerSideEncryption.html 197 cli.BoolFlag{ 198 Name: "sse", 199 Usage: "Enable basic server-side encryption at rest (SSE-S3) in S3 for all writes (default: off)", 200 }, 201 202 cli.StringFlag{ 203 Name: "sse-kms", 204 Usage: "Enable KMS encryption (SSE-KMS) for all writes using this particular KMS `key-id`. Leave blank to Use the account's CMK - customer master key (default: off)", 205 Value: "", 206 }, 207 208 cli.StringFlag{ 209 Name: "sse-c", 210 Usage: "Enable server-side encryption using this base64-encoded key (default: off)", 211 Value: "", 212 }, 213 214 /// http://docs.aws.amazon.com/AmazonS3/latest/dev/acl-overview.html#canned-acl 215 cli.StringFlag{ 216 Name: "acl", 217 Usage: "The canned ACL to apply to the object. Possible values: private, public-read, public-read-write, authenticated-read, aws-exec-read, bucket-owner-read, bucket-owner-full-control (default: off)", 218 Value: "", 219 }, 220 221 cli.BoolFlag{ 222 Name: "subdomain", 223 Usage: "Enable subdomain mode of S3", 224 }, 225 226 ///////////////////////// 227 // Tuning 228 ///////////////////////// 229 230 cli.BoolFlag{ 231 Name: "cheap", 232 Usage: "Reduce S3 operation costs at the expense of some performance (default: off)", 233 }, 234 235 cli.BoolFlag{ 236 Name: "no-implicit-dir", 237 Usage: "Assume all directory objects (\"dir/\") exist (default: off)", 238 }, 239 240 cli.DurationFlag{ 241 Name: "stat-cache-ttl", 242 Value: time.Minute, 243 Usage: "How long to cache StatObject results and inode attributes.", 244 }, 245 246 cli.DurationFlag{ 247 Name: "type-cache-ttl", 248 Value: time.Minute, 249 Usage: "How long to cache name -> file/dir mappings in directory " + 250 "inodes.", 251 }, 252 cli.DurationFlag{ 253 Name: "http-timeout", 254 Value: 30 * time.Second, 255 Usage: "Set the timeout on HTTP requests to S3", 256 }, 257 258 ///////////////////////// 259 // Debugging 260 ///////////////////////// 261 262 cli.BoolFlag{ 263 Name: "debug_fuse", 264 Usage: "Enable fuse-related debugging output.", 265 }, 266 267 cli.BoolFlag{ 268 Name: "debug_s3", 269 Usage: "Enable S3-related debugging output.", 270 }, 271 272 cli.BoolFlag{ 273 Name: "f", 274 Usage: "Run goofys in foreground.", 275 }, 276 }, 277 } 278 279 var funcMap = template.FuncMap{ 280 "category": filterCategory, 281 "join": strings.Join, 282 } 283 284 flagCategories = map[string]string{} 285 286 for _, f := range []string{"region", "sse", "sse-kms", "sse-c", "storage-class", "acl", "requester-pays"} { 287 flagCategories[f] = "aws" 288 } 289 290 for _, f := range []string{"cheap", "no-implicit-dir", "stat-cache-ttl", "type-cache-ttl", "http-timeout"} { 291 flagCategories[f] = "tuning" 292 } 293 294 for _, f := range []string{"help, h", "debug_fuse", "debug_s3", "version, v", "f"} { 295 flagCategories[f] = "misc" 296 } 297 298 cli.HelpPrinter = func(w io.Writer, templ string, data interface{}) { 299 w = tabwriter.NewWriter(w, 1, 8, 2, ' ', 0) 300 var tmplGet = template.Must(template.New("help").Funcs(funcMap).Parse(templ)) 301 tmplGet.Execute(w, app) 302 } 303 304 return 305 } 306 307 func parseOptions(m map[string]string, s string) { 308 // NOTE(jacobsa): The man pages don't define how escaping works, and as far 309 // as I can tell there is no way to properly escape or quote a comma in the 310 // options list for an fstab entry. So put our fingers in our ears and hope 311 // that nobody needs a comma. 312 for _, p := range strings.Split(s, ",") { 313 var name string 314 var value string 315 316 // Split on the first equals sign. 317 if equalsIndex := strings.IndexByte(p, '='); equalsIndex != -1 { 318 name = p[:equalsIndex] 319 value = p[equalsIndex+1:] 320 } else { 321 name = p 322 } 323 324 m[name] = value 325 } 326 327 return 328 } 329 330 // PopulateFlags adds the flags accepted by run to the supplied flag set, returning the 331 // variables into which the flags will parse. 332 func PopulateFlags(c *cli.Context) (ret *FlagStorage) { 333 flags := &FlagStorage{ 334 // File system 335 MountOptions: make(map[string]string), 336 DirMode: os.FileMode(c.Int("dir-mode")), 337 FileMode: os.FileMode(c.Int("file-mode")), 338 Uid: uint32(c.Int("uid")), 339 Gid: uint32(c.Int("gid")), 340 341 // Tuning, 342 Cheap: c.Bool("cheap"), 343 ExplicitDir: c.Bool("no-implicit-dir"), 344 StatCacheTTL: c.Duration("stat-cache-ttl"), 345 TypeCacheTTL: c.Duration("type-cache-ttl"), 346 HTTPTimeout: c.Duration("http-timeout"), 347 348 // Common Backend Config 349 Endpoint: c.String("endpoint"), 350 UseContentType: c.Bool("use-content-type"), 351 352 // Debugging, 353 DebugFuse: c.Bool("debug_fuse"), 354 DebugS3: c.Bool("debug_s3"), 355 Foreground: c.Bool("f"), 356 357 // ReadAhead 358 MaxReadAhead: uint32(c.Int("max-readahead")), 359 ReadAheadChunk: uint32(c.Int("readahead-chunk")), 360 } 361 362 // S3 363 if c.IsSet("region") || c.IsSet("requester-pays") || c.IsSet("storage-class") || 364 c.IsSet("profile") || c.IsSet("sse") || c.IsSet("sse-kms") || 365 c.IsSet("sse-c") || c.IsSet("acl") || c.IsSet("subdomain") { 366 367 if flags.Backend == nil { 368 flags.Backend = (&S3Config{}).Init() 369 } 370 config, _ := flags.Backend.(*S3Config) 371 372 config.Region = c.String("region") 373 config.RegionSet = c.IsSet("region") 374 config.RequesterPays = c.Bool("requester-pays") 375 config.StorageClass = c.String("storage-class") 376 config.Profile = c.String("profile") 377 config.UseSSE = c.Bool("sse") 378 config.UseKMS = c.IsSet("sse-kms") 379 config.KMSKeyID = c.String("sse-kms") 380 config.SseC = c.String("sse-c") 381 config.ACL = c.String("acl") 382 config.Subdomain = c.Bool("subdomain") 383 384 // KMS implies SSE 385 if config.UseKMS { 386 config.UseSSE = true 387 } 388 } 389 390 // Handle the repeated "-o" flag. 391 for _, o := range c.StringSlice("o") { 392 parseOptions(flags.MountOptions, o) 393 } 394 395 flags.MountPointArg = c.Args()[1] 396 flags.MountPoint = flags.MountPointArg 397 var err error 398 399 defer func() { 400 if err != nil { 401 flags.Cleanup() 402 } 403 }() 404 405 if c.IsSet("cache") { 406 cache := c.String("cache") 407 cacheArgs := strings.Split(c.String("cache"), ":") 408 cacheDir := cacheArgs[len(cacheArgs)-1] 409 cacheArgs = cacheArgs[:len(cacheArgs)-1] 410 411 fi, err := os.Stat(cacheDir) 412 if err != nil || !fi.IsDir() { 413 io.WriteString(cli.ErrWriter, 414 fmt.Sprintf("Invalid value \"%v\" for --cache: not a directory\n\n", 415 cacheDir)) 416 return nil 417 } 418 419 if _, ok := flags.MountOptions["allow_other"]; !ok { 420 flags.MountPointCreated, err = ioutil.TempDir("", ".goofys-mnt") 421 if err != nil { 422 io.WriteString(cli.ErrWriter, 423 fmt.Sprintf("Unable to create temp dir: %v", err)) 424 return nil 425 } 426 flags.MountPoint = flags.MountPointCreated 427 } 428 429 cacheArgs = append([]string{"--test", "-f"}, cacheArgs...) 430 431 if flags.MountPointArg == flags.MountPoint { 432 cacheArgs = append(cacheArgs, "-ononempty") 433 } 434 435 cacheArgs = append(cacheArgs, "--") 436 cacheArgs = append(cacheArgs, flags.MountPoint) 437 cacheArgs = append(cacheArgs, cacheDir) 438 cacheArgs = append(cacheArgs, flags.MountPointArg) 439 440 fuseLog.Debugf("catfs %v", cacheArgs) 441 catfs := exec.Command("catfs", cacheArgs...) 442 _, err = catfs.Output() 443 if err != nil { 444 if ee, ok := err.(*exec.Error); ok { 445 io.WriteString(cli.ErrWriter, 446 fmt.Sprintf("--cache requires catfs (%v) but %v\n\n", 447 "http://github.com/kahing/catfs", 448 ee.Error())) 449 } else if ee, ok := err.(*exec.ExitError); ok { 450 io.WriteString(cli.ErrWriter, 451 fmt.Sprintf("Invalid value \"%v\" for --cache: %v\n\n", 452 cache, string(ee.Stderr))) 453 } 454 return nil 455 } 456 457 flags.Cache = cacheArgs[1:] 458 } 459 460 return flags 461 } 462 463 func MassageMountFlags(args []string) (ret []string) { 464 if len(args) == 5 && args[3] == "-o" { 465 // looks like it's coming from fstab! 466 mountOptions := "" 467 ret = append(ret, args[0]) 468 469 for _, p := range strings.Split(args[4], ",") { 470 if strings.HasPrefix(p, "-") { 471 ret = append(ret, p) 472 } else { 473 mountOptions += p 474 mountOptions += "," 475 } 476 } 477 478 if len(mountOptions) != 0 { 479 // remove trailing , 480 mountOptions = mountOptions[:len(mountOptions)-1] 481 ret = append(ret, "-o") 482 ret = append(ret, mountOptions) 483 } 484 485 ret = append(ret, args[1]) 486 ret = append(ret, args[2]) 487 } else { 488 return args 489 } 490 491 return 492 }