| // Copyright 2017 The Fuchsia Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| package amberd |
| |
| import ( |
| "bufio" |
| "flag" |
| "fmt" |
| "io" |
| "log" |
| "os" |
| "path/filepath" |
| "runtime" |
| "strings" |
| "syscall/zx" |
| "syscall/zx/fidl" |
| |
| "amber/control_server" |
| "amber/daemon" |
| "amber/metrics" |
| "amber/source" |
| "amber/sys_update" |
| |
| "fidl/fuchsia/amber" |
| |
| "app/context" |
| "syslog/logger" |
| ) |
| |
| const ( |
| defaultSourceDir = "/system/data/amber/sources" |
| ) |
| |
| func Main() { |
| |
| var ( |
| // TODO(jmatt) replace hard-coded values with something better/more flexible |
| usage = "usage: amber [-k=<path>] [-s=<path>] [-u=<url>]" |
| store = flag.String("s", "/data/amber/store", "The path to the local file store") |
| autoUpdate = flag.Bool("a", false, "Automatically update and restart the system as updates become available") |
| ) |
| |
| flag.CommandLine.Usage = func() { |
| fmt.Println(usage) |
| flag.CommandLine.PrintDefaults() |
| } |
| |
| ctx := context.CreateFromStartupInfo() |
| registerLogger(ctx) |
| |
| metrics.Register(ctx) |
| |
| readExtraFlags() |
| |
| flag.Parse() |
| |
| // The source dir is where we store our database of sources. Because we |
| // don't currently have a mechanism to run "post-install" scripts, |
| // we'll use the existence of the data dir to signify if we need to |
| // load in the default sources. |
| storeExists, err := exists(*store) |
| if err != nil { |
| log.Fatal(err) |
| } |
| |
| var ctlSvc amber.ControlService |
| var evtSvc amber.EventsService |
| d, err := daemon.NewDaemon(*store, "", "", &evtSvc) |
| if err != nil { |
| log.Fatalf("failed to start daemon: %s", err) |
| } |
| |
| // Now that the daemon is up and running, we can register all of the |
| // system configured sources, if they exist. |
| // |
| // TODO(etryzelaar): Since these sources are only installed once, |
| // there's currently no way to upgrade them. PKG-82 is tracking coming |
| // up with a plan to address this. |
| if !storeExists { |
| defaultConfigsExist, err := exists(defaultSourceDir) |
| if err != nil { |
| log.Fatal(err) |
| } |
| |
| if defaultConfigsExist { |
| log.Printf("initializing store: %s", *store) |
| if err := addDefaultSourceConfigs(d, defaultSourceDir); err != nil { |
| log.Fatalf("failed to register default sources: %s", err) |
| } |
| } |
| } |
| |
| supMon := sys_update.NewSystemUpdateMonitor(*autoUpdate, d) |
| ctlSvr := control_server.NewControlServer(d, supMon) |
| ctx.OutgoingService.AddService(amber.ControlName, func(c zx.Channel) error { |
| _, err := ctlSvc.Add(ctlSvr, c, nil) |
| return err |
| }) |
| ctx.OutgoingService.AddService(amber.EventsName, func(c zx.Channel) error { |
| _, err := evtSvc.Add(control_server.EventsImpl{}, c, nil) |
| return err |
| }) |
| // note: not blocking |
| ctx.Serve() |
| |
| go func() { |
| log.Printf("monitoring for updates") |
| supMon.Start() |
| log.Println("system update monitor exited") |
| }() |
| |
| for i := 1; i < runtime.NumCPU(); i++ { |
| go fidl.Serve() |
| } |
| fidl.Serve() |
| } |
| |
| type logWriter struct{} |
| |
| func (l *logWriter) Write(data []byte) (n int, err error) { |
| origLen := len(data) |
| |
| // Strip out the trailing newline the `log` library adds because the |
| // logging service also adds a trailing newline. |
| if len(data) > 0 && data[len(data)-1] == '\n' { |
| data = data[:len(data)-1] |
| } |
| |
| if err := logger.Infof("%s", data); err != nil { |
| return 0, err |
| } |
| |
| return origLen, nil |
| } |
| |
| func registerLogger(ctx *context.Context) { |
| if err := logger.InitDefaultLoggerWithTags(ctx.Connector(), "amber"); err != nil { |
| log.Printf("error initializing syslog interface: %s", err) |
| } |
| log.SetOutput(&logWriter{}) |
| log.SetFlags(0) |
| } |
| |
| // addDefaultSourceConfigs installs source configs from a directory. |
| // The directory structure looks like: |
| // |
| // $dir/source1/config.json |
| // $dir/source2/config.json |
| // ... |
| func addDefaultSourceConfigs(d *daemon.Daemon, dir string) error { |
| configs, err := source.LoadSourceConfigs(dir) |
| if err != nil { |
| return err |
| } |
| |
| var errs []string |
| for _, cfg := range configs { |
| if err := d.AddSource(cfg); err != nil { |
| errs = append(errs, err.Error()) |
| } |
| } |
| |
| if len(errs) == 0 { |
| return nil |
| } |
| return fmt.Errorf("error adding default configs: %s", strings.Join(errs, ", ")) |
| } |
| |
| var flagsDir = filepath.Join("/system", "data", "amber", "flags") |
| |
| func readExtraFlags() { |
| d, err := os.Open(flagsDir) |
| if err != nil { |
| if !os.IsNotExist(err) { |
| log.Printf("unexpected error reading %q: %s", flagsDir, err) |
| } |
| return |
| } |
| defer d.Close() |
| |
| files, err := d.Readdir(0) |
| if err != nil { |
| log.Printf("error listing flags directory %s", err) |
| return |
| } |
| for _, f := range files { |
| if f.IsDir() || f.Size() == 0 { |
| continue |
| } |
| |
| fPath := filepath.Join(d.Name(), f.Name()) |
| file, err := os.Open(fPath) |
| if err != nil { |
| log.Printf("flags file %q could not be opened: %s", fPath, err) |
| continue |
| } |
| r := bufio.NewReader(file) |
| for { |
| line, err := r.ReadString('\n') |
| if err != nil && err != io.EOF { |
| log.Printf("flags file %q had read error: %s", fPath, err) |
| break |
| } |
| |
| line = strings.TrimSpace(line) |
| os.Args = append(os.Args, line) |
| if err == io.EOF { |
| break |
| } |
| } |
| file.Close() |
| } |
| } |
| |
| // Check if a path exists. |
| func exists(path string) (bool, error) { |
| if _, err := os.Stat(path); err != nil { |
| if os.IsNotExist(err) { |
| return false, nil |
| } else { |
| return false, err |
| } |
| } |
| |
| return true, nil |
| } |