github.com/stolowski/snapd@v0.0.0-20210407085831-115137ce5a22/wrappers/dbus.go (about) 1 // -*- Mode: Go; indent-tabs-mode: t -*- 2 3 /* 4 * Copyright (C) 2020 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 wrappers 21 22 import ( 23 "bytes" 24 "io/ioutil" 25 "os" 26 "path/filepath" 27 "regexp" 28 "text/template" 29 30 "github.com/snapcore/snapd/dirs" 31 "github.com/snapcore/snapd/osutil" 32 "github.com/snapcore/snapd/snap" 33 ) 34 35 func generateDBusActivationFile(app *snap.AppInfo, busName string) ([]byte, error) { 36 // The D-Bus service activation file format is defined as part 37 // of the protocol specification: 38 // 39 // https://dbus.freedesktop.org/doc/dbus-specification.html#message-bus-starting-services 40 serviceTemplate := `[D-BUS Service] 41 Name={{.BusName}} 42 Comment=Bus name for snap application {{.App.Snap.InstanceName}}.{{.App.Name}} 43 SystemdService={{.App.ServiceName}} 44 Exec={{.App.LauncherCommand}} 45 AssumedAppArmorLabel={{.App.SecurityTag}} 46 {{- if eq .App.DaemonScope "system"}} 47 User=root 48 {{- end}} 49 X-Snap={{.App.Snap.InstanceName}} 50 ` 51 t := template.Must(template.New("dbus-service").Parse(serviceTemplate)) 52 53 serviceData := struct { 54 App *snap.AppInfo 55 BusName string 56 }{ 57 App: app, 58 BusName: busName, 59 } 60 var templateOut bytes.Buffer 61 if err := t.Execute(&templateOut, serviceData); err != nil { 62 return nil, err 63 } 64 return templateOut.Bytes(), nil 65 } 66 67 var snapNameLine = regexp.MustCompile(`(?m)^X-Snap=(.*)$`) 68 69 // snapNameFromServiceFile returns the snap name for the D-Bus service activation file. 70 func snapNameFromServiceFile(filename string) (owner string, err error) { 71 content, err := ioutil.ReadFile(filename) 72 if err != nil { 73 return "", err 74 } 75 m := snapNameLine.FindSubmatch(content) 76 if m != nil { 77 owner = string(m[1]) 78 } 79 return owner, nil 80 } 81 82 // snapServiceActivationFiles returns the list of service activation files for a snap. 83 func snapServiceActivationFiles(dir, snapName string) (services []string, err error) { 84 glob := filepath.Join(dir, "*.service") 85 matches, err := filepath.Glob(glob) 86 if err != nil { 87 return nil, err 88 } 89 for _, match := range matches { 90 serviceSnap, err := snapNameFromServiceFile(match) 91 if err != nil { 92 return nil, err 93 } 94 if serviceSnap == snapName { 95 services = append(services, filepath.Base(match)) 96 } 97 } 98 return services, nil 99 } 100 101 func AddSnapDBusActivationFiles(s *snap.Info) error { 102 if err := os.MkdirAll(dirs.SnapDBusSessionServicesDir, 0755); err != nil { 103 return err 104 } 105 if err := os.MkdirAll(dirs.SnapDBusSystemServicesDir, 0755); err != nil { 106 return err 107 } 108 109 // Make sure we include any service files that claim to have 110 // been written by the snap. 111 sessionServices, err := snapServiceActivationFiles(dirs.SnapDBusSessionServicesDir, s.InstanceName()) 112 if err != nil { 113 return err 114 } 115 systemServices, err := snapServiceActivationFiles(dirs.SnapDBusSystemServicesDir, s.InstanceName()) 116 if err != nil { 117 return err 118 } 119 120 sessionContent := make(map[string]osutil.FileState) 121 systemContent := make(map[string]osutil.FileState) 122 123 for _, app := range s.Apps { 124 if !app.IsService() { 125 continue 126 } 127 128 for _, slot := range app.ActivatesOn { 129 var busName string 130 if err := slot.Attr("name", &busName); err != nil { 131 return err 132 } 133 134 content, err := generateDBusActivationFile(app, busName) 135 if err != nil { 136 return err 137 } 138 filename := busName + ".service" 139 fileState := &osutil.MemoryFileState{ 140 Content: content, 141 Mode: 0644, 142 } 143 switch app.DaemonScope { 144 case snap.SystemDaemon: 145 systemContent[filename] = fileState 146 systemServices = append(systemServices, filename) 147 case snap.UserDaemon: 148 sessionContent[filename] = fileState 149 sessionServices = append(sessionServices, filename) 150 } 151 } 152 } 153 154 if _, _, err = osutil.EnsureDirStateGlobs(dirs.SnapDBusSessionServicesDir, sessionServices, sessionContent); err != nil { 155 return err 156 } 157 158 if _, _, err = osutil.EnsureDirStateGlobs(dirs.SnapDBusSystemServicesDir, systemServices, systemContent); err != nil { 159 // On error, remove files installed by first invocation 160 osutil.EnsureDirStateGlobs(dirs.SnapDBusSessionServicesDir, sessionServices, nil) 161 return err 162 } 163 164 return nil 165 } 166 167 func RemoveSnapDBusActivationFiles(s *snap.Info) error { 168 // Select files to delete via X-Snap line to ensure everything 169 // is cleaned up if "snap try" is used and snap.yaml is 170 // modified. 171 for _, servicesDir := range []string{ 172 dirs.SnapDBusSessionServicesDir, 173 dirs.SnapDBusSystemServicesDir, 174 } { 175 toRemove, err := snapServiceActivationFiles(servicesDir, s.InstanceName()) 176 if err != nil { 177 return err 178 } 179 if _, _, err = osutil.EnsureDirStateGlobs(servicesDir, toRemove, nil); err != nil { 180 return err 181 } 182 } 183 return nil 184 }