1// Copyright 2015 Keybase, Inc. All rights reserved. Use of
2// this source code is governed by the included BSD license.
3
4package libkb
5
6import (
7	"encoding/json"
8	"fmt"
9	"io/ioutil"
10	"os"
11	"time"
12
13	"github.com/keybase/client/go/logger"
14)
15
16// ServiceInfo describes runtime info for a service.
17// This is primarily used to detect service updates.
18type ServiceInfo struct {
19	Version string `json:"version,omitempty"`
20	Label   string `json:"label,omitempty"`
21	Pid     int    `json:"pid,omitempty"`
22}
23
24// KeybaseServiceInfo is runtime info for the Keybase service.
25func KeybaseServiceInfo(g *GlobalContext) ServiceInfo {
26	return ServiceInfo{
27		Version: VersionString(),
28		Label:   g.Env.GetLabel(),
29		Pid:     os.Getpid(),
30	}
31}
32
33// NewServiceInfo generates service info for other services (like KBFS).
34func NewServiceInfo(version string, prerelease string, label string, pid int) ServiceInfo {
35	if prerelease != "" {
36		version = fmt.Sprintf("%s-%s", version, prerelease)
37	}
38	return ServiceInfo{
39		Version: version,
40		Label:   label,
41		Pid:     pid,
42	}
43}
44
45// WriteFile writes service info as JSON in runtimeDir.
46func (s ServiceInfo) WriteFile(path string, log logger.Logger) error {
47	out, err := json.MarshalIndent(s, "", "  ")
48	if err != nil {
49		return err
50	}
51
52	file := NewFile(path, out, 0644)
53	return file.Save(log)
54}
55
56// serviceLog is the log interface for ServiceInfo
57type serviceLog interface {
58	Debug(s string, args ...interface{})
59}
60
61func LoadServiceInfo(path string) (*ServiceInfo, error) {
62	if _, ferr := os.Stat(path); os.IsNotExist(ferr) {
63		return nil, nil
64	}
65	dat, err := ioutil.ReadFile(path)
66	if err != nil {
67		return nil, err
68	}
69	var serviceInfo ServiceInfo
70	err = json.Unmarshal(dat, &serviceInfo)
71	if err != nil {
72		return nil, err
73	}
74
75	return &serviceInfo, nil
76}
77
78// WaitForServiceInfoFile tries to wait for a service info file, which should be
79// written on successful service startup.
80func WaitForServiceInfoFile(path string, label string, pid string, timeout time.Duration, log serviceLog) (*ServiceInfo, error) {
81	if pid == "" {
82		return nil, fmt.Errorf("No pid to wait for")
83	}
84
85	lookForServiceInfo := func() (*ServiceInfo, error) {
86		serviceInfo, err := LoadServiceInfo(path)
87		if err != nil {
88			return nil, err
89		}
90		if serviceInfo == nil {
91			return nil, nil
92		}
93
94		// Make sure the info file is the pid we are waiting for, otherwise it is
95		// still starting up.
96		if pid != fmt.Sprintf("%d", serviceInfo.Pid) {
97			return nil, nil
98		}
99
100		// PIDs match, the service has started up
101		return serviceInfo, nil
102	}
103
104	log.Debug("Looking for service info file (timeout=%s)", timeout)
105	serviceInfo, err := waitForServiceInfo(timeout, time.Millisecond*100, lookForServiceInfo)
106
107	// If no service info was found, let's return an error
108	if serviceInfo == nil {
109		if err == nil {
110			err = fmt.Errorf("%s isn't running (expecting pid=%s)", label, pid)
111		}
112		return nil, err
113	}
114
115	// We succeeded in finding service info
116	log.Debug("Found service info: %#v", *serviceInfo)
117	return serviceInfo, nil
118}
119
120type serviceInfoResult struct {
121	info *ServiceInfo
122	err  error
123}
124
125type loadServiceInfoFn func() (*ServiceInfo, error)
126
127func waitForServiceInfo(timeout time.Duration, delay time.Duration, fn loadServiceInfoFn) (*ServiceInfo, error) {
128	if timeout <= 0 {
129		return fn()
130	}
131
132	ticker := time.NewTicker(delay)
133	defer ticker.Stop()
134	resultChan := make(chan serviceInfoResult, 1)
135	go func() {
136		for range ticker.C {
137			info, err := fn()
138			if err != nil {
139				resultChan <- serviceInfoResult{info: nil, err: err}
140				return
141			}
142			if info != nil {
143				resultChan <- serviceInfoResult{info: info, err: nil}
144				return
145			}
146		}
147	}()
148
149	select {
150	case res := <-resultChan:
151		return res.info, res.err
152	case <-time.After(timeout):
153		return nil, nil
154	}
155}
156