github.com/del680202/goofys@v0.19.1-0.20180727070818-6a609fafa266/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 "fmt" 20 "io" 21 "io/ioutil" 22 "os" 23 "os/exec" 24 "strings" 25 "text/tabwriter" 26 "text/template" 27 "time" 28 29 "github.com/urfave/cli" 30 ) 31 32 var flagCategories map[string]string 33 34 // Set up custom help text for goofys; in particular the usage section. 35 func filterCategory(flags []cli.Flag, category string) (ret []cli.Flag) { 36 for _, f := range flags { 37 if flagCategories[f.GetName()] == category { 38 ret = append(ret, f) 39 } 40 } 41 return 42 } 43 44 func init() { 45 cli.AppHelpTemplate = `NAME: 46 {{.Name}} - {{.Usage}} 47 48 USAGE: 49 {{.Name}} {{if .Flags}}[global options]{{end}} bucket[:prefix] mountpoint 50 {{if .Version}} 51 VERSION: 52 {{.Version}} 53 {{end}}{{if len .Authors}} 54 AUTHOR(S): 55 {{range .Authors}}{{ . }}{{end}} 56 {{end}}{{if .Commands}} 57 COMMANDS: 58 {{range .Commands}}{{join .Names ", "}}{{ "\t" }}{{.Usage}} 59 {{end}}{{end}}{{if .Flags}} 60 GLOBAL OPTIONS: 61 {{range category .Flags ""}}{{.}} 62 {{end}} 63 TUNING OPTIONS: 64 {{range category .Flags "tuning"}}{{.}} 65 {{end}} 66 AWS S3 OPTIONS: 67 {{range category .Flags "aws"}}{{.}} 68 {{end}} 69 MISC OPTIONS: 70 {{range category .Flags "misc"}}{{.}} 71 {{end}}{{end}}{{if .Copyright }} 72 COPYRIGHT: 73 {{.Copyright}} 74 {{end}} 75 ` 76 } 77 78 var VersionHash string 79 80 func NewApp() (app *cli.App) { 81 uid, gid := MyUserAndGroup() 82 83 app = &cli.App{ 84 Name: "goofys", 85 Version: "0.19.0-" + VersionHash, 86 Usage: "Mount an S3 bucket locally", 87 HideHelp: true, 88 Writer: os.Stderr, 89 Flags: []cli.Flag{ 90 91 cli.BoolFlag{ 92 Name: "help, h", 93 Usage: "Print this help text and exit successfully.", 94 }, 95 96 ///////////////////////// 97 // File system 98 ///////////////////////// 99 100 cli.StringSliceFlag{ 101 Name: "o", 102 Usage: "Additional system-specific mount options. Be careful!", 103 }, 104 105 cli.StringFlag{ 106 Name: "cache", 107 Usage: "Directory to use for data cache. " + 108 "Requires catfs and `-o allow_other'. " + 109 "Can also pass in other catfs options " + 110 "(ex: --cache \"--free:10%:$HOME/cache\") (default: off)", 111 }, 112 113 cli.IntFlag{ 114 Name: "dir-mode", 115 Value: 0755, 116 Usage: "Permission bits for directories. (default: 0755)", 117 }, 118 119 cli.IntFlag{ 120 Name: "file-mode", 121 Value: 0644, 122 Usage: "Permission bits for files. (default: 0644)", 123 }, 124 125 cli.IntFlag{ 126 Name: "uid", 127 Value: uid, 128 Usage: "UID owner of all inodes.", 129 }, 130 131 cli.IntFlag{ 132 Name: "gid", 133 Value: gid, 134 Usage: "GID owner of all inodes.", 135 }, 136 137 ///////////////////////// 138 // S3 139 ///////////////////////// 140 141 cli.StringFlag{ 142 Name: "endpoint", 143 Value: "", 144 Usage: "The non-AWS endpoint to connect to." + 145 " Possible values: http://127.0.0.1:8081/", 146 }, 147 148 cli.StringFlag{ 149 Name: "region", 150 Value: "us-east-1", 151 Usage: "The region to connect to. Usually this is auto-detected." + 152 " Possible values: us-east-1, us-west-1, us-west-2, eu-west-1, " + 153 "eu-central-1, ap-southeast-1, ap-southeast-2, ap-northeast-1, " + 154 "sa-east-1, cn-north-1", 155 }, 156 157 cli.StringFlag{ 158 Name: "storage-class", 159 Value: "STANDARD", 160 Usage: "The type of storage to use when writing objects." + 161 " Possible values: REDUCED_REDUNDANCY, STANDARD, STANDARD_IA.", 162 }, 163 164 cli.StringFlag{ 165 Name: "profile", 166 Usage: "Use a named profile from $HOME/.aws/credentials instead of \"default\"", 167 }, 168 169 cli.BoolFlag{ 170 Name: "use-content-type", 171 Usage: "Set Content-Type according to file extension and /etc/mime.types (default: off)", 172 }, 173 174 /// http://docs.aws.amazon.com/AmazonS3/latest/API/RESTObjectPUT.html 175 /// See http://docs.aws.amazon.com/AmazonS3/latest/dev/UsingServerSideEncryption.html 176 cli.BoolFlag{ 177 Name: "sse", 178 Usage: "Enable basic server-side encryption at rest (SSE-S3) in S3 for all writes (default: off)", 179 }, 180 181 cli.StringFlag{ 182 Name: "sse-kms", 183 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)", 184 Value: "", 185 }, 186 187 /// http://docs.aws.amazon.com/AmazonS3/latest/dev/acl-overview.html#canned-acl 188 cli.StringFlag{ 189 Name: "acl", 190 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)", 191 Value: "", 192 }, 193 194 ///////////////////////// 195 // Tuning 196 ///////////////////////// 197 198 cli.BoolFlag{ 199 Name: "cheap", 200 Usage: "Reduce S3 operation costs at the expense of some performance (default: off)", 201 }, 202 203 cli.BoolFlag{ 204 Name: "no-implicit-dir", 205 Usage: "Assume all directory objects (\"dir/\") exist (default: off)", 206 }, 207 208 cli.DurationFlag{ 209 Name: "stat-cache-ttl", 210 Value: time.Minute, 211 Usage: "How long to cache StatObject results and inode attributes.", 212 }, 213 214 cli.DurationFlag{ 215 Name: "type-cache-ttl", 216 Value: time.Minute, 217 Usage: "How long to cache name -> file/dir mappings in directory " + 218 "inodes.", 219 }, 220 221 ///////////////////////// 222 // Debugging 223 ///////////////////////// 224 225 cli.BoolFlag{ 226 Name: "debug_fuse", 227 Usage: "Enable fuse-related debugging output.", 228 }, 229 230 cli.BoolFlag{ 231 Name: "debug_s3", 232 Usage: "Enable S3-related debugging output.", 233 }, 234 235 cli.BoolFlag{ 236 Name: "f", 237 Usage: "Run goofys in foreground.", 238 }, 239 }, 240 } 241 242 var funcMap = template.FuncMap{ 243 "category": filterCategory, 244 "join": strings.Join, 245 } 246 247 flagCategories = map[string]string{} 248 249 for _, f := range []string{"region", "sse", "sse-kms", "storage-class", "acl"} { 250 flagCategories[f] = "aws" 251 } 252 253 for _, f := range []string{"cheap", "no-implicit-dir", "stat-cache-ttl", "type-cache-ttl"} { 254 flagCategories[f] = "tuning" 255 } 256 257 for _, f := range []string{"help, h", "debug_fuse", "debug_s3", "version, v", "f"} { 258 flagCategories[f] = "misc" 259 } 260 261 cli.HelpPrinter = func(w io.Writer, templ string, data interface{}) { 262 w = tabwriter.NewWriter(w, 1, 8, 2, ' ', 0) 263 var tmplGet = template.Must(template.New("help").Funcs(funcMap).Parse(templ)) 264 tmplGet.Execute(w, app) 265 } 266 267 return 268 } 269 270 type FlagStorage struct { 271 // File system 272 MountOptions map[string]string 273 MountPoint string 274 MountPointArg string 275 MountPointCreated string 276 277 Cache []string 278 DirMode os.FileMode 279 FileMode os.FileMode 280 Uid uint32 281 Gid uint32 282 283 // S3 284 Endpoint string 285 Region string 286 RegionSet bool 287 StorageClass string 288 Profile string 289 UseContentType bool 290 UseSSE bool 291 UseKMS bool 292 KMSKeyID string 293 ACL string 294 295 // Tuning 296 Cheap bool 297 ExplicitDir bool 298 StatCacheTTL time.Duration 299 TypeCacheTTL time.Duration 300 301 // Debugging 302 DebugFuse bool 303 DebugS3 bool 304 Foreground bool 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 func (flags *FlagStorage) Cleanup() { 331 if flags.MountPointCreated != "" && flags.MountPointCreated != flags.MountPointArg { 332 err := os.Remove(flags.MountPointCreated) 333 if err != nil { 334 log.Errorf("rmdir %v = %v", flags.MountPointCreated, err) 335 } 336 } 337 } 338 339 // Add the flags accepted by run to the supplied flag set, returning the 340 // variables into which the flags will parse. 341 func PopulateFlags(c *cli.Context) (ret *FlagStorage) { 342 flags := &FlagStorage{ 343 // File system 344 MountOptions: make(map[string]string), 345 DirMode: os.FileMode(c.Int("dir-mode")), 346 FileMode: os.FileMode(c.Int("file-mode")), 347 Uid: uint32(c.Int("uid")), 348 Gid: uint32(c.Int("gid")), 349 350 // Tuning, 351 Cheap: c.Bool("cheap"), 352 ExplicitDir: c.Bool("no-implicit-dir"), 353 StatCacheTTL: c.Duration("stat-cache-ttl"), 354 TypeCacheTTL: c.Duration("type-cache-ttl"), 355 356 // S3 357 Endpoint: c.String("endpoint"), 358 Region: c.String("region"), 359 RegionSet: c.IsSet("region"), 360 StorageClass: c.String("storage-class"), 361 Profile: c.String("profile"), 362 UseContentType: c.Bool("use-content-type"), 363 UseSSE: c.Bool("sse"), 364 UseKMS: c.IsSet("sse-kms"), 365 KMSKeyID: c.String("sse-kms"), 366 ACL: c.String("acl"), 367 368 // Debugging, 369 DebugFuse: c.Bool("debug_fuse"), 370 DebugS3: c.Bool("debug_s3"), 371 Foreground: c.Bool("f"), 372 } 373 374 // Handle the repeated "-o" flag. 375 for _, o := range c.StringSlice("o") { 376 parseOptions(flags.MountOptions, o) 377 } 378 379 flags.MountPointArg = c.Args()[1] 380 flags.MountPoint = flags.MountPointArg 381 var err error 382 383 defer func() { 384 if err != nil { 385 flags.Cleanup() 386 } 387 }() 388 389 if c.IsSet("cache") { 390 cache := c.String("cache") 391 cacheArgs := strings.Split(c.String("cache"), ":") 392 cacheDir := cacheArgs[len(cacheArgs)-1] 393 cacheArgs = cacheArgs[:len(cacheArgs)-1] 394 395 fi, err := os.Stat(cacheDir) 396 if err != nil || !fi.IsDir() { 397 io.WriteString(cli.ErrWriter, 398 fmt.Sprintf("Invalid value \"%v\" for --cache: not a directory\n\n", 399 cacheDir)) 400 return nil 401 } 402 403 if _, ok := flags.MountOptions["allow_other"]; !ok { 404 flags.MountPointCreated, err = ioutil.TempDir("", ".goofys-mnt") 405 if err != nil { 406 io.WriteString(cli.ErrWriter, 407 fmt.Sprintf("Unable to create temp dir: %v", err)) 408 return nil 409 } 410 flags.MountPoint = flags.MountPointCreated 411 } 412 413 cacheArgs = append([]string{"--test", "-f"}, cacheArgs...) 414 415 if flags.MountPointArg == flags.MountPoint { 416 cacheArgs = append(cacheArgs, "-ononempty") 417 } 418 419 cacheArgs = append(cacheArgs, "--") 420 cacheArgs = append(cacheArgs, flags.MountPoint) 421 cacheArgs = append(cacheArgs, cacheDir) 422 cacheArgs = append(cacheArgs, flags.MountPointArg) 423 424 fuseLog.Debugf("catfs %v", cacheArgs) 425 catfs := exec.Command("catfs", cacheArgs...) 426 _, err = catfs.Output() 427 if err != nil { 428 if ee, ok := err.(*exec.Error); ok { 429 io.WriteString(cli.ErrWriter, 430 fmt.Sprintf("--cache requires catfs (%v) but %v\n\n", 431 "http://github.com/kahing/catfs", 432 ee.Error())) 433 } else if ee, ok := err.(*exec.ExitError); ok { 434 io.WriteString(cli.ErrWriter, 435 fmt.Sprintf("Invalid value \"%v\" for --cache: %v\n\n", 436 cache, string(ee.Stderr))) 437 } 438 return nil 439 } 440 441 flags.Cache = cacheArgs[1:] 442 } 443 444 // KMS implies SSE 445 if flags.UseKMS { 446 flags.UseSSE = true 447 } 448 449 return flags 450 } 451 452 func MassageMountFlags(args []string) (ret []string) { 453 if len(args) == 5 && args[3] == "-o" { 454 // looks like it's coming from fstab! 455 mountOptions := "" 456 ret = append(ret, args[0]) 457 458 for _, p := range strings.Split(args[4], ",") { 459 if strings.HasPrefix(p, "-") { 460 ret = append(ret, p) 461 } else { 462 mountOptions += p 463 mountOptions += "," 464 } 465 } 466 467 if len(mountOptions) != 0 { 468 // remove trailing , 469 mountOptions = mountOptions[:len(mountOptions)-1] 470 ret = append(ret, "-o") 471 ret = append(ret, mountOptions) 472 } 473 474 ret = append(ret, args[1]) 475 ret = append(ret, args[2]) 476 } else { 477 return args 478 } 479 480 return 481 }