methods.go 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565
  1. // Copyright 2015 CoreOS, Inc.
  2. //
  3. // Licensed under the Apache License, Version 2.0 (the "License");
  4. // you may not use this file except in compliance with the License.
  5. // You may obtain a copy of the License at
  6. //
  7. // http://www.apache.org/licenses/LICENSE-2.0
  8. //
  9. // Unless required by applicable law or agreed to in writing, software
  10. // distributed under the License is distributed on an "AS IS" BASIS,
  11. // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  12. // See the License for the specific language governing permissions and
  13. // limitations under the License.
  14. package dbus
  15. import (
  16. "errors"
  17. "path"
  18. "strconv"
  19. "github.com/godbus/dbus"
  20. )
  21. func (c *Conn) jobComplete(signal *dbus.Signal) {
  22. var id uint32
  23. var job dbus.ObjectPath
  24. var unit string
  25. var result string
  26. dbus.Store(signal.Body, &id, &job, &unit, &result)
  27. c.jobListener.Lock()
  28. out, ok := c.jobListener.jobs[job]
  29. if ok {
  30. out <- result
  31. delete(c.jobListener.jobs, job)
  32. }
  33. c.jobListener.Unlock()
  34. }
  35. func (c *Conn) startJob(ch chan<- string, job string, args ...interface{}) (int, error) {
  36. if ch != nil {
  37. c.jobListener.Lock()
  38. defer c.jobListener.Unlock()
  39. }
  40. var p dbus.ObjectPath
  41. err := c.sysobj.Call(job, 0, args...).Store(&p)
  42. if err != nil {
  43. return 0, err
  44. }
  45. if ch != nil {
  46. c.jobListener.jobs[p] = ch
  47. }
  48. // ignore error since 0 is fine if conversion fails
  49. jobID, _ := strconv.Atoi(path.Base(string(p)))
  50. return jobID, nil
  51. }
  52. // StartUnit enqueues a start job and depending jobs, if any (unless otherwise
  53. // specified by the mode string).
  54. //
  55. // Takes the unit to activate, plus a mode string. The mode needs to be one of
  56. // replace, fail, isolate, ignore-dependencies, ignore-requirements. If
  57. // "replace" the call will start the unit and its dependencies, possibly
  58. // replacing already queued jobs that conflict with this. If "fail" the call
  59. // will start the unit and its dependencies, but will fail if this would change
  60. // an already queued job. If "isolate" the call will start the unit in question
  61. // and terminate all units that aren't dependencies of it. If
  62. // "ignore-dependencies" it will start a unit but ignore all its dependencies.
  63. // If "ignore-requirements" it will start a unit but only ignore the
  64. // requirement dependencies. It is not recommended to make use of the latter
  65. // two options.
  66. //
  67. // If the provided channel is non-nil, a result string will be sent to it upon
  68. // job completion: one of done, canceled, timeout, failed, dependency, skipped.
  69. // done indicates successful execution of a job. canceled indicates that a job
  70. // has been canceled before it finished execution. timeout indicates that the
  71. // job timeout was reached. failed indicates that the job failed. dependency
  72. // indicates that a job this job has been depending on failed and the job hence
  73. // has been removed too. skipped indicates that a job was skipped because it
  74. // didn't apply to the units current state.
  75. //
  76. // If no error occurs, the ID of the underlying systemd job will be returned. There
  77. // does exist the possibility for no error to be returned, but for the returned job
  78. // ID to be 0. In this case, the actual underlying ID is not 0 and this datapoint
  79. // should not be considered authoritative.
  80. //
  81. // If an error does occur, it will be returned to the user alongside a job ID of 0.
  82. func (c *Conn) StartUnit(name string, mode string, ch chan<- string) (int, error) {
  83. return c.startJob(ch, "org.freedesktop.systemd1.Manager.StartUnit", name, mode)
  84. }
  85. // StopUnit is similar to StartUnit but stops the specified unit rather
  86. // than starting it.
  87. func (c *Conn) StopUnit(name string, mode string, ch chan<- string) (int, error) {
  88. return c.startJob(ch, "org.freedesktop.systemd1.Manager.StopUnit", name, mode)
  89. }
  90. // ReloadUnit reloads a unit. Reloading is done only if the unit is already running and fails otherwise.
  91. func (c *Conn) ReloadUnit(name string, mode string, ch chan<- string) (int, error) {
  92. return c.startJob(ch, "org.freedesktop.systemd1.Manager.ReloadUnit", name, mode)
  93. }
  94. // RestartUnit restarts a service. If a service is restarted that isn't
  95. // running it will be started.
  96. func (c *Conn) RestartUnit(name string, mode string, ch chan<- string) (int, error) {
  97. return c.startJob(ch, "org.freedesktop.systemd1.Manager.RestartUnit", name, mode)
  98. }
  99. // TryRestartUnit is like RestartUnit, except that a service that isn't running
  100. // is not affected by the restart.
  101. func (c *Conn) TryRestartUnit(name string, mode string, ch chan<- string) (int, error) {
  102. return c.startJob(ch, "org.freedesktop.systemd1.Manager.TryRestartUnit", name, mode)
  103. }
  104. // ReloadOrRestart attempts a reload if the unit supports it and use a restart
  105. // otherwise.
  106. func (c *Conn) ReloadOrRestartUnit(name string, mode string, ch chan<- string) (int, error) {
  107. return c.startJob(ch, "org.freedesktop.systemd1.Manager.ReloadOrRestartUnit", name, mode)
  108. }
  109. // ReloadOrTryRestart attempts a reload if the unit supports it and use a "Try"
  110. // flavored restart otherwise.
  111. func (c *Conn) ReloadOrTryRestartUnit(name string, mode string, ch chan<- string) (int, error) {
  112. return c.startJob(ch, "org.freedesktop.systemd1.Manager.ReloadOrTryRestartUnit", name, mode)
  113. }
  114. // StartTransientUnit() may be used to create and start a transient unit, which
  115. // will be released as soon as it is not running or referenced anymore or the
  116. // system is rebooted. name is the unit name including suffix, and must be
  117. // unique. mode is the same as in StartUnit(), properties contains properties
  118. // of the unit.
  119. func (c *Conn) StartTransientUnit(name string, mode string, properties []Property, ch chan<- string) (int, error) {
  120. return c.startJob(ch, "org.freedesktop.systemd1.Manager.StartTransientUnit", name, mode, properties, make([]PropertyCollection, 0))
  121. }
  122. // KillUnit takes the unit name and a UNIX signal number to send. All of the unit's
  123. // processes are killed.
  124. func (c *Conn) KillUnit(name string, signal int32) {
  125. c.sysobj.Call("org.freedesktop.systemd1.Manager.KillUnit", 0, name, "all", signal).Store()
  126. }
  127. // ResetFailedUnit resets the "failed" state of a specific unit.
  128. func (c *Conn) ResetFailedUnit(name string) error {
  129. return c.sysobj.Call("org.freedesktop.systemd1.Manager.ResetFailedUnit", 0, name).Store()
  130. }
  131. // getProperties takes the unit name and returns all of its dbus object properties, for the given dbus interface
  132. func (c *Conn) getProperties(unit string, dbusInterface string) (map[string]interface{}, error) {
  133. var err error
  134. var props map[string]dbus.Variant
  135. path := unitPath(unit)
  136. if !path.IsValid() {
  137. return nil, errors.New("invalid unit name: " + unit)
  138. }
  139. obj := c.sysconn.Object("org.freedesktop.systemd1", path)
  140. err = obj.Call("org.freedesktop.DBus.Properties.GetAll", 0, dbusInterface).Store(&props)
  141. if err != nil {
  142. return nil, err
  143. }
  144. out := make(map[string]interface{}, len(props))
  145. for k, v := range props {
  146. out[k] = v.Value()
  147. }
  148. return out, nil
  149. }
  150. // GetUnitProperties takes the unit name and returns all of its dbus object properties.
  151. func (c *Conn) GetUnitProperties(unit string) (map[string]interface{}, error) {
  152. return c.getProperties(unit, "org.freedesktop.systemd1.Unit")
  153. }
  154. func (c *Conn) getProperty(unit string, dbusInterface string, propertyName string) (*Property, error) {
  155. var err error
  156. var prop dbus.Variant
  157. path := unitPath(unit)
  158. if !path.IsValid() {
  159. return nil, errors.New("invalid unit name: " + unit)
  160. }
  161. obj := c.sysconn.Object("org.freedesktop.systemd1", path)
  162. err = obj.Call("org.freedesktop.DBus.Properties.Get", 0, dbusInterface, propertyName).Store(&prop)
  163. if err != nil {
  164. return nil, err
  165. }
  166. return &Property{Name: propertyName, Value: prop}, nil
  167. }
  168. func (c *Conn) GetUnitProperty(unit string, propertyName string) (*Property, error) {
  169. return c.getProperty(unit, "org.freedesktop.systemd1.Unit", propertyName)
  170. }
  171. // GetServiceProperty returns property for given service name and property name
  172. func (c *Conn) GetServiceProperty(service string, propertyName string) (*Property, error) {
  173. return c.getProperty(service, "org.freedesktop.systemd1.Service", propertyName)
  174. }
  175. // GetUnitTypeProperties returns the extra properties for a unit, specific to the unit type.
  176. // Valid values for unitType: Service, Socket, Target, Device, Mount, Automount, Snapshot, Timer, Swap, Path, Slice, Scope
  177. // return "dbus.Error: Unknown interface" if the unitType is not the correct type of the unit
  178. func (c *Conn) GetUnitTypeProperties(unit string, unitType string) (map[string]interface{}, error) {
  179. return c.getProperties(unit, "org.freedesktop.systemd1."+unitType)
  180. }
  181. // SetUnitProperties() may be used to modify certain unit properties at runtime.
  182. // Not all properties may be changed at runtime, but many resource management
  183. // settings (primarily those in systemd.cgroup(5)) may. The changes are applied
  184. // instantly, and stored on disk for future boots, unless runtime is true, in which
  185. // case the settings only apply until the next reboot. name is the name of the unit
  186. // to modify. properties are the settings to set, encoded as an array of property
  187. // name and value pairs.
  188. func (c *Conn) SetUnitProperties(name string, runtime bool, properties ...Property) error {
  189. return c.sysobj.Call("org.freedesktop.systemd1.Manager.SetUnitProperties", 0, name, runtime, properties).Store()
  190. }
  191. func (c *Conn) GetUnitTypeProperty(unit string, unitType string, propertyName string) (*Property, error) {
  192. return c.getProperty(unit, "org.freedesktop.systemd1."+unitType, propertyName)
  193. }
  194. type UnitStatus struct {
  195. Name string // The primary unit name as string
  196. Description string // The human readable description string
  197. LoadState string // The load state (i.e. whether the unit file has been loaded successfully)
  198. ActiveState string // The active state (i.e. whether the unit is currently started or not)
  199. SubState string // The sub state (a more fine-grained version of the active state that is specific to the unit type, which the active state is not)
  200. Followed string // A unit that is being followed in its state by this unit, if there is any, otherwise the empty string.
  201. Path dbus.ObjectPath // The unit object path
  202. JobId uint32 // If there is a job queued for the job unit the numeric job id, 0 otherwise
  203. JobType string // The job type as string
  204. JobPath dbus.ObjectPath // The job object path
  205. }
  206. type storeFunc func(retvalues ...interface{}) error
  207. func (c *Conn) listUnitsInternal(f storeFunc) ([]UnitStatus, error) {
  208. result := make([][]interface{}, 0)
  209. err := f(&result)
  210. if err != nil {
  211. return nil, err
  212. }
  213. resultInterface := make([]interface{}, len(result))
  214. for i := range result {
  215. resultInterface[i] = result[i]
  216. }
  217. status := make([]UnitStatus, len(result))
  218. statusInterface := make([]interface{}, len(status))
  219. for i := range status {
  220. statusInterface[i] = &status[i]
  221. }
  222. err = dbus.Store(resultInterface, statusInterface...)
  223. if err != nil {
  224. return nil, err
  225. }
  226. return status, nil
  227. }
  228. // ListUnits returns an array with all currently loaded units. Note that
  229. // units may be known by multiple names at the same time, and hence there might
  230. // be more unit names loaded than actual units behind them.
  231. func (c *Conn) ListUnits() ([]UnitStatus, error) {
  232. return c.listUnitsInternal(c.sysobj.Call("org.freedesktop.systemd1.Manager.ListUnits", 0).Store)
  233. }
  234. // ListUnitsFiltered returns an array with units filtered by state.
  235. // It takes a list of units' statuses to filter.
  236. func (c *Conn) ListUnitsFiltered(states []string) ([]UnitStatus, error) {
  237. return c.listUnitsInternal(c.sysobj.Call("org.freedesktop.systemd1.Manager.ListUnitsFiltered", 0, states).Store)
  238. }
  239. // ListUnitsByPatterns returns an array with units.
  240. // It takes a list of units' statuses and names to filter.
  241. // Note that units may be known by multiple names at the same time,
  242. // and hence there might be more unit names loaded than actual units behind them.
  243. func (c *Conn) ListUnitsByPatterns(states []string, patterns []string) ([]UnitStatus, error) {
  244. return c.listUnitsInternal(c.sysobj.Call("org.freedesktop.systemd1.Manager.ListUnitsByPatterns", 0, states, patterns).Store)
  245. }
  246. // ListUnitsByNames returns an array with units. It takes a list of units'
  247. // names and returns an UnitStatus array. Comparing to ListUnitsByPatterns
  248. // method, this method returns statuses even for inactive or non-existing
  249. // units. Input array should contain exact unit names, but not patterns.
  250. func (c *Conn) ListUnitsByNames(units []string) ([]UnitStatus, error) {
  251. return c.listUnitsInternal(c.sysobj.Call("org.freedesktop.systemd1.Manager.ListUnitsByNames", 0, units).Store)
  252. }
  253. type UnitFile struct {
  254. Path string
  255. Type string
  256. }
  257. func (c *Conn) listUnitFilesInternal(f storeFunc) ([]UnitFile, error) {
  258. result := make([][]interface{}, 0)
  259. err := f(&result)
  260. if err != nil {
  261. return nil, err
  262. }
  263. resultInterface := make([]interface{}, len(result))
  264. for i := range result {
  265. resultInterface[i] = result[i]
  266. }
  267. files := make([]UnitFile, len(result))
  268. fileInterface := make([]interface{}, len(files))
  269. for i := range files {
  270. fileInterface[i] = &files[i]
  271. }
  272. err = dbus.Store(resultInterface, fileInterface...)
  273. if err != nil {
  274. return nil, err
  275. }
  276. return files, nil
  277. }
  278. // ListUnitFiles returns an array of all available units on disk.
  279. func (c *Conn) ListUnitFiles() ([]UnitFile, error) {
  280. return c.listUnitFilesInternal(c.sysobj.Call("org.freedesktop.systemd1.Manager.ListUnitFiles", 0).Store)
  281. }
  282. // ListUnitFilesByPatterns returns an array of all available units on disk matched the patterns.
  283. func (c *Conn) ListUnitFilesByPatterns(states []string, patterns []string) ([]UnitFile, error) {
  284. return c.listUnitFilesInternal(c.sysobj.Call("org.freedesktop.systemd1.Manager.ListUnitFilesByPatterns", 0, states, patterns).Store)
  285. }
  286. type LinkUnitFileChange EnableUnitFileChange
  287. // LinkUnitFiles() links unit files (that are located outside of the
  288. // usual unit search paths) into the unit search path.
  289. //
  290. // It takes a list of absolute paths to unit files to link and two
  291. // booleans. The first boolean controls whether the unit shall be
  292. // enabled for runtime only (true, /run), or persistently (false,
  293. // /etc).
  294. // The second controls whether symlinks pointing to other units shall
  295. // be replaced if necessary.
  296. //
  297. // This call returns a list of the changes made. The list consists of
  298. // structures with three strings: the type of the change (one of symlink
  299. // or unlink), the file name of the symlink and the destination of the
  300. // symlink.
  301. func (c *Conn) LinkUnitFiles(files []string, runtime bool, force bool) ([]LinkUnitFileChange, error) {
  302. result := make([][]interface{}, 0)
  303. err := c.sysobj.Call("org.freedesktop.systemd1.Manager.LinkUnitFiles", 0, files, runtime, force).Store(&result)
  304. if err != nil {
  305. return nil, err
  306. }
  307. resultInterface := make([]interface{}, len(result))
  308. for i := range result {
  309. resultInterface[i] = result[i]
  310. }
  311. changes := make([]LinkUnitFileChange, len(result))
  312. changesInterface := make([]interface{}, len(changes))
  313. for i := range changes {
  314. changesInterface[i] = &changes[i]
  315. }
  316. err = dbus.Store(resultInterface, changesInterface...)
  317. if err != nil {
  318. return nil, err
  319. }
  320. return changes, nil
  321. }
  322. // EnableUnitFiles() may be used to enable one or more units in the system (by
  323. // creating symlinks to them in /etc or /run).
  324. //
  325. // It takes a list of unit files to enable (either just file names or full
  326. // absolute paths if the unit files are residing outside the usual unit
  327. // search paths), and two booleans: the first controls whether the unit shall
  328. // be enabled for runtime only (true, /run), or persistently (false, /etc).
  329. // The second one controls whether symlinks pointing to other units shall
  330. // be replaced if necessary.
  331. //
  332. // This call returns one boolean and an array with the changes made. The
  333. // boolean signals whether the unit files contained any enablement
  334. // information (i.e. an [Install]) section. The changes list consists of
  335. // structures with three strings: the type of the change (one of symlink
  336. // or unlink), the file name of the symlink and the destination of the
  337. // symlink.
  338. func (c *Conn) EnableUnitFiles(files []string, runtime bool, force bool) (bool, []EnableUnitFileChange, error) {
  339. var carries_install_info bool
  340. result := make([][]interface{}, 0)
  341. err := c.sysobj.Call("org.freedesktop.systemd1.Manager.EnableUnitFiles", 0, files, runtime, force).Store(&carries_install_info, &result)
  342. if err != nil {
  343. return false, nil, err
  344. }
  345. resultInterface := make([]interface{}, len(result))
  346. for i := range result {
  347. resultInterface[i] = result[i]
  348. }
  349. changes := make([]EnableUnitFileChange, len(result))
  350. changesInterface := make([]interface{}, len(changes))
  351. for i := range changes {
  352. changesInterface[i] = &changes[i]
  353. }
  354. err = dbus.Store(resultInterface, changesInterface...)
  355. if err != nil {
  356. return false, nil, err
  357. }
  358. return carries_install_info, changes, nil
  359. }
  360. type EnableUnitFileChange struct {
  361. Type string // Type of the change (one of symlink or unlink)
  362. Filename string // File name of the symlink
  363. Destination string // Destination of the symlink
  364. }
  365. // DisableUnitFiles() may be used to disable one or more units in the system (by
  366. // removing symlinks to them from /etc or /run).
  367. //
  368. // It takes a list of unit files to disable (either just file names or full
  369. // absolute paths if the unit files are residing outside the usual unit
  370. // search paths), and one boolean: whether the unit was enabled for runtime
  371. // only (true, /run), or persistently (false, /etc).
  372. //
  373. // This call returns an array with the changes made. The changes list
  374. // consists of structures with three strings: the type of the change (one of
  375. // symlink or unlink), the file name of the symlink and the destination of the
  376. // symlink.
  377. func (c *Conn) DisableUnitFiles(files []string, runtime bool) ([]DisableUnitFileChange, error) {
  378. result := make([][]interface{}, 0)
  379. err := c.sysobj.Call("org.freedesktop.systemd1.Manager.DisableUnitFiles", 0, files, runtime).Store(&result)
  380. if err != nil {
  381. return nil, err
  382. }
  383. resultInterface := make([]interface{}, len(result))
  384. for i := range result {
  385. resultInterface[i] = result[i]
  386. }
  387. changes := make([]DisableUnitFileChange, len(result))
  388. changesInterface := make([]interface{}, len(changes))
  389. for i := range changes {
  390. changesInterface[i] = &changes[i]
  391. }
  392. err = dbus.Store(resultInterface, changesInterface...)
  393. if err != nil {
  394. return nil, err
  395. }
  396. return changes, nil
  397. }
  398. type DisableUnitFileChange struct {
  399. Type string // Type of the change (one of symlink or unlink)
  400. Filename string // File name of the symlink
  401. Destination string // Destination of the symlink
  402. }
  403. // MaskUnitFiles masks one or more units in the system
  404. //
  405. // It takes three arguments:
  406. // * list of units to mask (either just file names or full
  407. // absolute paths if the unit files are residing outside
  408. // the usual unit search paths)
  409. // * runtime to specify whether the unit was enabled for runtime
  410. // only (true, /run/systemd/..), or persistently (false, /etc/systemd/..)
  411. // * force flag
  412. func (c *Conn) MaskUnitFiles(files []string, runtime bool, force bool) ([]MaskUnitFileChange, error) {
  413. result := make([][]interface{}, 0)
  414. err := c.sysobj.Call("org.freedesktop.systemd1.Manager.MaskUnitFiles", 0, files, runtime, force).Store(&result)
  415. if err != nil {
  416. return nil, err
  417. }
  418. resultInterface := make([]interface{}, len(result))
  419. for i := range result {
  420. resultInterface[i] = result[i]
  421. }
  422. changes := make([]MaskUnitFileChange, len(result))
  423. changesInterface := make([]interface{}, len(changes))
  424. for i := range changes {
  425. changesInterface[i] = &changes[i]
  426. }
  427. err = dbus.Store(resultInterface, changesInterface...)
  428. if err != nil {
  429. return nil, err
  430. }
  431. return changes, nil
  432. }
  433. type MaskUnitFileChange struct {
  434. Type string // Type of the change (one of symlink or unlink)
  435. Filename string // File name of the symlink
  436. Destination string // Destination of the symlink
  437. }
  438. // UnmaskUnitFiles unmasks one or more units in the system
  439. //
  440. // It takes two arguments:
  441. // * list of unit files to mask (either just file names or full
  442. // absolute paths if the unit files are residing outside
  443. // the usual unit search paths)
  444. // * runtime to specify whether the unit was enabled for runtime
  445. // only (true, /run/systemd/..), or persistently (false, /etc/systemd/..)
  446. func (c *Conn) UnmaskUnitFiles(files []string, runtime bool) ([]UnmaskUnitFileChange, error) {
  447. result := make([][]interface{}, 0)
  448. err := c.sysobj.Call("org.freedesktop.systemd1.Manager.UnmaskUnitFiles", 0, files, runtime).Store(&result)
  449. if err != nil {
  450. return nil, err
  451. }
  452. resultInterface := make([]interface{}, len(result))
  453. for i := range result {
  454. resultInterface[i] = result[i]
  455. }
  456. changes := make([]UnmaskUnitFileChange, len(result))
  457. changesInterface := make([]interface{}, len(changes))
  458. for i := range changes {
  459. changesInterface[i] = &changes[i]
  460. }
  461. err = dbus.Store(resultInterface, changesInterface...)
  462. if err != nil {
  463. return nil, err
  464. }
  465. return changes, nil
  466. }
  467. type UnmaskUnitFileChange struct {
  468. Type string // Type of the change (one of symlink or unlink)
  469. Filename string // File name of the symlink
  470. Destination string // Destination of the symlink
  471. }
  472. // Reload instructs systemd to scan for and reload unit files. This is
  473. // equivalent to a 'systemctl daemon-reload'.
  474. func (c *Conn) Reload() error {
  475. return c.sysobj.Call("org.freedesktop.systemd1.Manager.Reload", 0).Store()
  476. }
  477. func unitPath(name string) dbus.ObjectPath {
  478. return dbus.ObjectPath("/org/freedesktop/systemd1/unit/" + PathBusEscape(name))
  479. }