github.com/hugh712/snapd@v0.0.0-20200910133618-1a99902bd583/usersession/agent/rest_api.go (about) 1 // -*- Mode: Go; indent-tabs-mode: t -*- 2 3 /* 4 * Copyright (C) 2019 Canonical Ltd 5 * 6 * This program is free software: you can redistribute it and/or modify 7 * it under the terms of the GNU General Public License version 3 as 8 * published by the Free Software Foundation. 9 * 10 * This program is distributed in the hope that it will be useful, 11 * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 * GNU General Public License for more details. 14 * 15 * You should have received a copy of the GNU General Public License 16 * along with this program. If not, see <http://www.gnu.org/licenses/>. 17 * 18 */ 19 20 package agent 21 22 import ( 23 "encoding/json" 24 "mime" 25 "net/http" 26 "strings" 27 "sync" 28 "time" 29 30 "github.com/snapcore/snapd/dirs" 31 "github.com/snapcore/snapd/systemd" 32 "github.com/snapcore/snapd/timeout" 33 ) 34 35 var restApi = []*Command{ 36 rootCmd, 37 sessionInfoCmd, 38 serviceControlCmd, 39 } 40 41 var ( 42 rootCmd = &Command{ 43 Path: "/", 44 GET: nil, 45 } 46 47 sessionInfoCmd = &Command{ 48 Path: "/v1/session-info", 49 GET: sessionInfo, 50 } 51 52 serviceControlCmd = &Command{ 53 Path: "/v1/service-control", 54 POST: postServiceControl, 55 } 56 ) 57 58 func sessionInfo(c *Command, r *http.Request) Response { 59 m := map[string]interface{}{ 60 "version": c.s.Version, 61 } 62 return SyncResponse(m) 63 } 64 65 type serviceInstruction struct { 66 Action string `json:"action"` 67 Services []string `json:"services"` 68 } 69 70 var ( 71 stopTimeout = time.Duration(timeout.DefaultTimeout) 72 killWait = 5 * time.Second 73 ) 74 75 func serviceStart(inst *serviceInstruction, sysd systemd.Systemd) Response { 76 // Refuse to start non-snap services 77 for _, service := range inst.Services { 78 if !strings.HasPrefix(service, "snap.") { 79 return InternalError("cannot start non-snap service %v", service) 80 } 81 } 82 83 startErrors := make(map[string]string) 84 var started []string 85 for _, service := range inst.Services { 86 if err := sysd.Start(service); err != nil { 87 startErrors[service] = err.Error() 88 break 89 } 90 started = append(started, service) 91 } 92 // If we got any failures, attempt to stop the services we started. 93 stopErrors := make(map[string]string) 94 if len(startErrors) != 0 { 95 for _, service := range started { 96 if err := sysd.Stop(service, stopTimeout); err != nil { 97 stopErrors[service] = err.Error() 98 } 99 } 100 } 101 if len(startErrors) == 0 { 102 return SyncResponse(nil) 103 } 104 return SyncResponse(&resp{ 105 Type: ResponseTypeError, 106 Status: 500, 107 Result: &errorResult{ 108 Message: "some user services failed to start", 109 Kind: errorKindServiceControl, 110 Value: map[string]interface{}{ 111 "start-errors": startErrors, 112 "stop-errors": stopErrors, 113 }, 114 }, 115 }) 116 } 117 118 func serviceStop(inst *serviceInstruction, sysd systemd.Systemd) Response { 119 // Refuse to stop non-snap services 120 for _, service := range inst.Services { 121 if !strings.HasPrefix(service, "snap.") { 122 return InternalError("cannot stop non-snap service %v", service) 123 } 124 } 125 126 stopErrors := make(map[string]string) 127 for _, service := range inst.Services { 128 if err := sysd.Stop(service, stopTimeout); err != nil { 129 stopErrors[service] = err.Error() 130 } 131 } 132 if len(stopErrors) == 0 { 133 return SyncResponse(nil) 134 } 135 return SyncResponse(&resp{ 136 Type: ResponseTypeError, 137 Status: 500, 138 Result: &errorResult{ 139 Message: "some user services failed to stop", 140 Kind: errorKindServiceControl, 141 Value: map[string]interface{}{ 142 "stop-errors": stopErrors, 143 }, 144 }, 145 }) 146 } 147 148 func serviceDaemonReload(inst *serviceInstruction, sysd systemd.Systemd) Response { 149 if len(inst.Services) != 0 { 150 return InternalError("daemon-reload should not be called with any services") 151 } 152 if err := sysd.DaemonReload(); err != nil { 153 return InternalError("cannot reload daemon: %v", err) 154 } 155 return SyncResponse(nil) 156 } 157 158 var serviceInstructionDispTable = map[string]func(*serviceInstruction, systemd.Systemd) Response{ 159 "start": serviceStart, 160 "stop": serviceStop, 161 "daemon-reload": serviceDaemonReload, 162 } 163 164 var systemdLock sync.Mutex 165 166 type dummyReporter struct{} 167 168 func (dummyReporter) Notify(string) {} 169 170 func postServiceControl(c *Command, r *http.Request) Response { 171 contentType := r.Header.Get("Content-Type") 172 mediaType, params, err := mime.ParseMediaType(contentType) 173 if err != nil { 174 return BadRequest("cannot parse content type: %v", err) 175 } 176 177 if mediaType != "application/json" { 178 return BadRequest("unknown content type: %s", contentType) 179 } 180 181 charset := strings.ToUpper(params["charset"]) 182 if charset != "" && charset != "UTF-8" { 183 return BadRequest("unknown charset in content type: %s", contentType) 184 } 185 186 decoder := json.NewDecoder(r.Body) 187 var inst serviceInstruction 188 if err := decoder.Decode(&inst); err != nil { 189 return BadRequest("cannot decode request body into service instruction: %v", err) 190 } 191 impl := serviceInstructionDispTable[inst.Action] 192 if impl == nil { 193 return BadRequest("unknown action %s", inst.Action) 194 } 195 // Prevent multiple systemd actions from being carried out simultaneously 196 systemdLock.Lock() 197 defer systemdLock.Unlock() 198 sysd := systemd.New(dirs.GlobalRootDir, systemd.UserMode, dummyReporter{}) 199 return impl(&inst, sysd) 200 }