github.com/slspeek/camlistore_namedsearch@v0.0.0-20140519202248-ed6f70f7721a/cmd/camget/camget.go (about)

     1  /*
     2  Copyright 2011 Google Inc.
     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  
    17  package main
    18  
    19  import (
    20  	"bytes"
    21  	"errors"
    22  	"flag"
    23  	"fmt"
    24  	"io"
    25  	"log"
    26  	"net/http"
    27  	"os"
    28  	"path/filepath"
    29  
    30  	"camlistore.org/pkg/blob"
    31  	"camlistore.org/pkg/buildinfo"
    32  	"camlistore.org/pkg/cacher"
    33  	"camlistore.org/pkg/client"
    34  	"camlistore.org/pkg/httputil"
    35  	"camlistore.org/pkg/index"
    36  	"camlistore.org/pkg/schema"
    37  )
    38  
    39  var (
    40  	flagVersion     = flag.Bool("version", false, "show version")
    41  	flagVerbose     = flag.Bool("verbose", false, "be verbose")
    42  	flagHTTP        = flag.Bool("verbose_http", false, "show HTTP request summaries")
    43  	flagCheck       = flag.Bool("check", false, "just check for the existence of listed blobs; returning 0 if all are present")
    44  	flagOutput      = flag.String("o", "-", "Output file/directory to create.  Use -f to overwrite.")
    45  	flagGraph       = flag.Bool("graph", false, "Output a graphviz directed graph .dot file of the provided root schema blob, to be rendered with 'dot -Tsvg -o graph.svg graph.dot'")
    46  	flagContents    = flag.Bool("contents", false, "If true and the target blobref is a 'bytes' or 'file' schema blob, the contents of that file are output instead.")
    47  	flagShared      = flag.String("shared", "", "If non-empty, the URL of a \"share\" blob. The URL will be used as the root of future fetches. Only \"haveref\" shares are currently supported.")
    48  	flagTrustedCert = flag.String("cert", "", "If non-empty, the fingerprint (20 digits lowercase prefix of the SHA256 of the complete certificate) of the TLS certificate we trust for the share URL. Requires --shared.")
    49  	flagInsecureTLS = flag.Bool("insecure", false, "If set, when using TLS, the server's certificates verification is disabled, and they are not checked against the trustedCerts in the client configuration either.")
    50  )
    51  
    52  func main() {
    53  	client.AddFlags()
    54  	flag.Parse()
    55  
    56  	if *flagVersion {
    57  		fmt.Fprintf(os.Stderr, "camget version: %s\n", buildinfo.Version())
    58  		return
    59  	}
    60  
    61  	if *flagGraph && flag.NArg() != 1 {
    62  		log.Fatalf("The --graph option requires exactly one parameter.")
    63  	}
    64  
    65  	var cl *client.Client
    66  	var items []blob.Ref
    67  
    68  	if *flagShared != "" {
    69  		if client.ExplicitServer() != "" {
    70  			log.Fatal("Can't use --shared with an explicit blobserver; blobserver is implicit from the --shared URL.")
    71  		}
    72  		if flag.NArg() != 0 {
    73  			log.Fatal("No arguments permitted when using --shared")
    74  		}
    75  		cl1, target, err := client.NewFromShareRoot(*flagShared,
    76  			client.OptionInsecure(*flagInsecureTLS),
    77  			client.OptionTrustedCert(*flagTrustedCert))
    78  		if err != nil {
    79  			log.Fatal(err)
    80  		}
    81  		cl = cl1
    82  		items = append(items, target)
    83  	} else {
    84  		if *flagTrustedCert != "" {
    85  			log.Fatal("Can't use --cert without --shared.")
    86  		}
    87  		cl = client.NewOrFail()
    88  		for n := 0; n < flag.NArg(); n++ {
    89  			arg := flag.Arg(n)
    90  			br, ok := blob.Parse(arg)
    91  			if !ok {
    92  				log.Fatalf("Failed to parse argument %q as a blobref.", arg)
    93  			}
    94  			items = append(items, br)
    95  		}
    96  	}
    97  
    98  	cl.InsecureTLS = *flagInsecureTLS
    99  	tr := cl.TransportForConfig(&client.TransportConfig{
   100  		Verbose: *flagHTTP,
   101  	})
   102  	httpStats, _ := tr.(*httputil.StatsTransport)
   103  	cl.SetHTTPClient(&http.Client{Transport: tr})
   104  
   105  	diskCacheFetcher, err := cacher.NewDiskCache(cl)
   106  	if err != nil {
   107  		log.Fatalf("Error setting up local disk cache: %v", err)
   108  	}
   109  	defer diskCacheFetcher.Clean()
   110  	if *flagVerbose {
   111  		log.Printf("Using temp blob cache directory %s", diskCacheFetcher.Root)
   112  	}
   113  
   114  	for _, br := range items {
   115  		if *flagGraph {
   116  			printGraph(diskCacheFetcher, br)
   117  			return
   118  		}
   119  		if *flagCheck {
   120  			// TODO: do HEAD requests checking if the blobs exists.
   121  			log.Fatal("not implemented")
   122  			return
   123  		}
   124  		if *flagOutput == "-" {
   125  			var rc io.ReadCloser
   126  			var err error
   127  			if *flagContents {
   128  				rc, err = schema.NewFileReader(diskCacheFetcher, br)
   129  				if err == nil {
   130  					rc.(*schema.FileReader).LoadAllChunks()
   131  				}
   132  			} else {
   133  				rc, err = fetch(diskCacheFetcher, br)
   134  			}
   135  			if err != nil {
   136  				log.Fatal(err)
   137  			}
   138  			defer rc.Close()
   139  			if _, err := io.Copy(os.Stdout, rc); err != nil {
   140  				log.Fatalf("Failed reading %q: %v", br, err)
   141  			}
   142  		} else {
   143  			if err := smartFetch(diskCacheFetcher, *flagOutput, br); err != nil {
   144  				log.Fatal(err)
   145  			}
   146  		}
   147  	}
   148  
   149  	if *flagVerbose {
   150  		log.Printf("HTTP requests: %d\n", httpStats.Requests())
   151  	}
   152  }
   153  
   154  func fetch(src blob.Fetcher, br blob.Ref) (r io.ReadCloser, err error) {
   155  	if *flagVerbose {
   156  		log.Printf("Fetching %s", br.String())
   157  	}
   158  	r, _, err = src.Fetch(br)
   159  	if err != nil {
   160  		return nil, fmt.Errorf("Failed to fetch %s: %s", br, err)
   161  	}
   162  	return r, err
   163  }
   164  
   165  // A little less than the sniffer will take, so we don't truncate.
   166  const sniffSize = 900 * 1024
   167  
   168  // smartFetch the things that blobs point to, not just blobs.
   169  func smartFetch(src blob.Fetcher, targ string, br blob.Ref) error {
   170  	rc, err := fetch(src, br)
   171  	if err != nil {
   172  		return err
   173  	}
   174  	defer rc.Close()
   175  
   176  	sniffer := index.NewBlobSniffer(br)
   177  	_, err = io.CopyN(sniffer, rc, sniffSize)
   178  	if err != nil && err != io.EOF {
   179  		return err
   180  	}
   181  
   182  	sniffer.Parse()
   183  	b, ok := sniffer.SchemaBlob()
   184  
   185  	if !ok {
   186  		if *flagVerbose {
   187  			log.Printf("Fetching opaque data %v into %q", br, targ)
   188  		}
   189  
   190  		// opaque data - put it in a file
   191  		f, err := os.Create(targ)
   192  		if err != nil {
   193  			return fmt.Errorf("opaque: %v", err)
   194  		}
   195  		defer f.Close()
   196  		body, _ := sniffer.Body()
   197  		r := io.MultiReader(bytes.NewReader(body), rc)
   198  		_, err = io.Copy(f, r)
   199  		return err
   200  	}
   201  
   202  	switch b.Type() {
   203  	case "directory":
   204  		dir := filepath.Join(targ, b.FileName())
   205  		if *flagVerbose {
   206  			log.Printf("Fetching directory %v into %s", br, dir)
   207  		}
   208  		if err := os.MkdirAll(dir, b.FileMode()); err != nil {
   209  			return err
   210  		}
   211  		if err := setFileMeta(dir, b); err != nil {
   212  			log.Print(err)
   213  		}
   214  		entries, ok := b.DirectoryEntries()
   215  		if !ok {
   216  			return fmt.Errorf("bad entries blobref in dir %v", b.BlobRef())
   217  		}
   218  		return smartFetch(src, dir, entries)
   219  	case "static-set":
   220  		if *flagVerbose {
   221  			log.Printf("Fetching directory entries %v into %s", br, targ)
   222  		}
   223  
   224  		// directory entries
   225  		const numWorkers = 10
   226  		type work struct {
   227  			br   blob.Ref
   228  			errc chan<- error
   229  		}
   230  		members := b.StaticSetMembers()
   231  		workc := make(chan work, len(members))
   232  		defer close(workc)
   233  		for i := 0; i < numWorkers; i++ {
   234  			go func() {
   235  				for wi := range workc {
   236  					wi.errc <- smartFetch(src, targ, wi.br)
   237  				}
   238  			}()
   239  		}
   240  		var errcs []<-chan error
   241  		for _, mref := range members {
   242  			errc := make(chan error, 1)
   243  			errcs = append(errcs, errc)
   244  			workc <- work{mref, errc}
   245  		}
   246  		for _, errc := range errcs {
   247  			if err := <-errc; err != nil {
   248  				return err
   249  			}
   250  		}
   251  		return nil
   252  	case "file":
   253  		fr, err := schema.NewFileReader(src, br)
   254  		if err != nil {
   255  			return fmt.Errorf("NewFileReader: %v", err)
   256  		}
   257  		fr.LoadAllChunks()
   258  		defer fr.Close()
   259  
   260  		name := filepath.Join(targ, b.FileName())
   261  
   262  		if fi, err := os.Stat(name); err == nil && fi.Size() == fi.Size() {
   263  			if *flagVerbose {
   264  				log.Printf("Skipping %s; already exists.", name)
   265  				return nil
   266  			}
   267  		}
   268  
   269  		if *flagVerbose {
   270  			log.Printf("Writing %s to %s ...", br, name)
   271  		}
   272  
   273  		f, err := os.Create(name)
   274  		if err != nil {
   275  			return fmt.Errorf("file type: %v", err)
   276  		}
   277  		defer f.Close()
   278  		if _, err := io.Copy(f, fr); err != nil {
   279  			return fmt.Errorf("Copying %s to %s: %v", br, name, err)
   280  		}
   281  		if err := setFileMeta(name, b); err != nil {
   282  			log.Print(err)
   283  		}
   284  		return nil
   285  	case "symlink":
   286  		sf, ok := b.AsStaticFile()
   287  		if !ok {
   288  			return errors.New("blob is not a static file")
   289  		}
   290  		sl, ok := sf.AsStaticSymlink()
   291  		if !ok {
   292  			return errors.New("blob is not a symlink")
   293  		}
   294  		name := filepath.Join(targ, sl.FileName())
   295  		if _, err := os.Lstat(name); err == nil {
   296  			if *flagVerbose {
   297  				log.Printf("Skipping creating symbolic link %s: A file with that name exists", name)
   298  			}
   299  			return nil
   300  		}
   301  		target := sl.SymlinkTargetString()
   302  		if target == "" {
   303  			return errors.New("symlink without target")
   304  		}
   305  
   306  		// TODO (marete): The Go docs promise that everything
   307  		// in pkg os should work the same everywhere. Not true
   308  		// for os.Symlin() at the moment. See what to do for
   309  		// windows here.
   310  		err := os.Symlink(target, name)
   311  		// We won't call setFileMeta for a symlink because:
   312  		// the permissions of a symlink do not matter and Go's
   313  		// os.Chtimes always dereferences (does not act on the
   314  		// symlink but its target).
   315  		return err
   316  
   317  	default:
   318  		return errors.New("unknown blob type: " + b.Type())
   319  	}
   320  	panic("unreachable")
   321  }
   322  
   323  func setFileMeta(name string, blob *schema.Blob) error {
   324  	err1 := os.Chmod(name, blob.FileMode())
   325  	var err2 error
   326  	if mt := blob.ModTime(); !mt.IsZero() {
   327  		err2 = os.Chtimes(name, mt, mt)
   328  	}
   329  	// TODO: we previously did os.Chown here, but it's rarely wanted,
   330  	// then the schema.Blob refactor broke it, so it's gone.
   331  	// Add it back later once we care?
   332  	for _, err := range []error{err1, err2} {
   333  		if err != nil {
   334  			return err
   335  		}
   336  	}
   337  	return nil
   338  }