github.com/olivere/camlistore@v0.0.0-20140121221811-1b7ac2da0199/pkg/client/android/android.go (about)

     1  /*
     2  Copyright 2013 The Camlistore Authors
     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 android contains code specific to running the Camlistore client
    18  // code as a child process on Android. This removes ugly API from the
    19  // client package itself.
    20  package android
    21  
    22  import (
    23  	"bufio"
    24  	"crypto/tls"
    25  	"crypto/x509"
    26  	"fmt"
    27  	"io"
    28  	"io/ioutil"
    29  	"log"
    30  	"net"
    31  	"net/http"
    32  	"os"
    33  	"os/exec"
    34  	"path/filepath"
    35  	"regexp"
    36  	"strconv"
    37  	"sync"
    38  
    39  	"camlistore.org/pkg/blob"
    40  	"camlistore.org/pkg/blobserver"
    41  	"camlistore.org/pkg/schema"
    42  )
    43  
    44  // TODO(mpl): distinguish CAMPUT, CAMGET, etc
    45  var androidOutput, _ = strconv.ParseBool(os.Getenv("CAMPUT_ANDROID_OUTPUT"))
    46  
    47  // IsChild reports whether this process is running as an Android
    48  // child process and should report its output in the form that the
    49  // Android uploader app expects.
    50  func IsChild() bool {
    51  	return androidOutput
    52  }
    53  
    54  var androidOutMu sync.Mutex
    55  
    56  func Printf(format string, args ...interface{}) {
    57  	androidOutMu.Lock()
    58  	defer androidOutMu.Unlock()
    59  	fmt.Printf(format, args...)
    60  }
    61  
    62  var detectOnce sync.Once
    63  var onAndroidCache bool
    64  
    65  func dirExists(f string) bool {
    66  	fi, err := os.Stat(f)
    67  	return err == nil && fi.IsDir()
    68  }
    69  
    70  func initOnAndroid() {
    71  	// Good enough heuristic. Suggestions welcome.
    72  	onAndroidCache = dirExists("/data/data") && dirExists("/system/etc")
    73  }
    74  
    75  func OnAndroid() bool {
    76  	detectOnce.Do(initOnAndroid)
    77  	return onAndroidCache
    78  }
    79  
    80  var pingRx = regexp.MustCompile(`\((.+?)\)`)
    81  
    82  type namedInt struct {
    83  	name string
    84  	sync.Mutex
    85  	val int64
    86  }
    87  
    88  func (ni *namedInt) Incr(delta int64) {
    89  	ni.Lock()
    90  	ni.val += delta
    91  	nv := ni.val
    92  	ni.Unlock()
    93  	Printf("STAT %s %d\n", ni.name, nv)
    94  }
    95  
    96  var (
    97  	statDNSStart       = &namedInt{name: "dns_start"}
    98  	statDNSDone        = &namedInt{name: "dns_done"}
    99  	statTCPStart       = &namedInt{name: "tcp_start"}
   100  	statTCPStarted     = &namedInt{name: "tcp_started"}
   101  	statTCPFail        = &namedInt{name: "tcp_fail"}
   102  	statTCPDone        = &namedInt{name: "tcp_done"}
   103  	statTCPWrites      = &namedInt{name: "tcp_write_byte"}
   104  	statTCPWrote       = &namedInt{name: "tcp_wrote_byte"}
   105  	statTCPReads       = &namedInt{name: "tcp_read_byte"}
   106  	statHTTPStart      = &namedInt{name: "http_start"}
   107  	statHTTPResHeaders = &namedInt{name: "http_res_headers"}
   108  	statBlobUploaded   = &namedInt{name: "blob_uploaded"}
   109  	statBlobExisted    = &namedInt{name: "blob_existed"}
   110  	statFileUploaded   = &namedInt{name: "file_uploaded"}
   111  	statFileExisted    = &namedInt{name: "file_existed"}
   112  )
   113  
   114  type statTrackingConn struct {
   115  	net.Conn
   116  }
   117  
   118  func (c statTrackingConn) Write(p []byte) (n int, err error) {
   119  	statTCPWrites.Incr(int64(len(p)))
   120  	n, err = c.Conn.Write(p)
   121  	statTCPWrote.Incr(int64(n))
   122  	return
   123  }
   124  
   125  func (c statTrackingConn) Read(p []byte) (n int, err error) {
   126  	n, err = c.Conn.Read(p)
   127  	statTCPReads.Incr(int64(n))
   128  	return
   129  }
   130  
   131  var (
   132  	dnsMu    sync.Mutex
   133  	dnsCache = make(map[string]string)
   134  )
   135  
   136  func androidLookupHost(host string) string {
   137  	dnsMu.Lock()
   138  	v, ok := dnsCache[host]
   139  	dnsMu.Unlock()
   140  	if ok {
   141  		return v
   142  	}
   143  	statDNSStart.Incr(1)
   144  	defer statDNSDone.Incr(1)
   145  
   146  	// Android has no "dig" or "host" tool, so use "ping -c 1". Ghetto.
   147  	// $ ping -c 1 google.com
   148  	// PING google.com (74.125.224.64) 56(84) bytes of data.
   149  	c := make(chan string, 1)
   150  	go func() {
   151  		cmd := exec.Command("/system/bin/ping", "-c", "1", host)
   152  		stdout, err := cmd.StdoutPipe()
   153  		if err != nil {
   154  			panic(err)
   155  		}
   156  		defer stdout.Close()
   157  		if err := cmd.Start(); err != nil {
   158  			log.Printf("Error resolving %q with ping: %v", host, err)
   159  			c <- host
   160  			return
   161  		}
   162  		defer func() {
   163  			if p := cmd.Process; p != nil {
   164  				p.Kill()
   165  			}
   166  		}()
   167  		br := bufio.NewReader(stdout)
   168  		line, err := br.ReadString('\n')
   169  		if err != nil {
   170  			log.Printf("Failed to resolve %q with ping", host)
   171  			c <- host
   172  			return
   173  		}
   174  		if m := pingRx.FindStringSubmatch(line); m != nil {
   175  			ip := m[1]
   176  			dnsMu.Lock()
   177  			dnsCache[host] = ip
   178  			dnsMu.Unlock()
   179  			c <- ip
   180  			return
   181  		}
   182  		log.Printf("Failed to resolve %q with ping", host)
   183  		c <- host
   184  	}()
   185  	return <-c
   186  	return v
   187  }
   188  
   189  type StatsTransport struct {
   190  	Rt http.RoundTripper
   191  }
   192  
   193  func (t StatsTransport) RoundTrip(req *http.Request) (res *http.Response, err error) {
   194  	statHTTPStart.Incr(1)
   195  	res, err = t.Rt.RoundTrip(req)
   196  	statHTTPResHeaders.Incr(1)
   197  	// TODO: track per-response code stats, and also track when body done being read.
   198  	return
   199  }
   200  
   201  func Dial(network, addr string) (net.Conn, error) {
   202  	// Temporary laziness hack, avoiding doing a
   203  	// cross-compiled Android cgo build.
   204  	// Without cgo, package net uses
   205  	// /etc/resolv.conf (not available on
   206  	// Android).  We really want a cgo binary to
   207  	// use Android's DNS cache, but it's kinda
   208  	// hard/impossible to cross-compile for now.
   209  	statTCPStart.Incr(1)
   210  	host, port, err := net.SplitHostPort(addr)
   211  	if err != nil {
   212  		statTCPFail.Incr(1)
   213  		return nil, fmt.Errorf("couldn't split %q", addr)
   214  	}
   215  	c, err := net.Dial(network, net.JoinHostPort(androidLookupHost(host), port))
   216  	if err != nil {
   217  		statTCPFail.Incr(1)
   218  		return nil, err
   219  	}
   220  	statTCPStarted.Incr(1)
   221  	return statTrackingConn{Conn: c}, err
   222  }
   223  
   224  func TLSConfig() (*tls.Config, error) {
   225  	if !OnAndroid() {
   226  		return nil, nil
   227  	}
   228  	certDir := "/system/etc/security/cacerts"
   229  	fi, err := os.Stat(certDir)
   230  	if err != nil {
   231  		return nil, err
   232  	}
   233  	if !fi.IsDir() {
   234  		return nil, fmt.Errorf("%q not a dir", certDir)
   235  	}
   236  	pool := x509.NewCertPool()
   237  	cfg := &tls.Config{RootCAs: pool}
   238  
   239  	f, err := os.Open(certDir)
   240  	if err != nil {
   241  		return nil, err
   242  	}
   243  	defer f.Close()
   244  	names, _ := f.Readdirnames(-1)
   245  	for _, name := range names {
   246  		pem, err := ioutil.ReadFile(filepath.Join(certDir, name))
   247  		if err != nil {
   248  			return nil, err
   249  		}
   250  		pool.AppendCertsFromPEM(pem)
   251  	}
   252  	return cfg, nil
   253  }
   254  
   255  // NoteFileUploaded is a hook for camput to report that a file
   256  // was uploaded.  TODO: move this to pkg/client/android probably.
   257  func NoteFileUploaded(fullPath string, uploaded bool) {
   258  	if !IsChild() {
   259  		return
   260  	}
   261  	if uploaded {
   262  		statFileUploaded.Incr(1)
   263  	} else {
   264  		statFileExisted.Incr(1)
   265  	}
   266  	Printf("FILE_UPLOADED %s\n", fullPath)
   267  }
   268  
   269  // androidStatusReceiver is a blobserver.StatReceiver wrapper that
   270  // reports the full filename path and size of uploaded blobs.
   271  // The android app wrapping camput watches stdout for this, for progress bars.
   272  type StatusReceiver struct {
   273  	Sr   blobserver.StatReceiver
   274  	Path string
   275  }
   276  
   277  func (asr StatusReceiver) noteChunkOnServer(sb blob.SizedRef) {
   278  	Printf("CHUNK_UPLOADED %d %s %s\n", sb.Size, sb.Ref, asr.Path)
   279  }
   280  
   281  func (asr StatusReceiver) ReceiveBlob(blob blob.Ref, source io.Reader) (blob.SizedRef, error) {
   282  	// Sniff the first 1KB of it and don't print the stats if it looks like it was just a schema
   283  	// blob.  We won't update the progress bar for that yet.
   284  	var buf [1024]byte
   285  	contents := buf[:0]
   286  	sb, err := asr.Sr.ReceiveBlob(blob, io.TeeReader(source, writeUntilSliceFull{&contents}))
   287  	if err == nil && !schema.LikelySchemaBlob(contents) {
   288  		statBlobUploaded.Incr(1)
   289  		asr.noteChunkOnServer(sb)
   290  	}
   291  	return sb, err
   292  }
   293  
   294  func (asr StatusReceiver) StatBlobs(dest chan<- blob.SizedRef, blobs []blob.Ref) error {
   295  	midc := make(chan blob.SizedRef)
   296  	errc := make(chan error, 1)
   297  	go func() {
   298  		err := asr.Sr.StatBlobs(midc, blobs)
   299  		errc <- err
   300  		close(midc)
   301  	}()
   302  	for sb := range midc {
   303  		asr.noteChunkOnServer(sb)
   304  		statBlobExisted.Incr(1)
   305  		dest <- sb
   306  	}
   307  	return <-errc
   308  }
   309  
   310  type writeUntilSliceFull struct {
   311  	s *[]byte
   312  }
   313  
   314  func (w writeUntilSliceFull) Write(p []byte) (n int, err error) {
   315  	s := *w.s
   316  	l := len(s)
   317  	growBy := cap(s) - l
   318  	if growBy > len(p) {
   319  		growBy = len(p)
   320  	}
   321  	s = s[0 : l+growBy]
   322  	copy(s[l:], p)
   323  	*w.s = s
   324  	return len(p), nil
   325  }