github.com/meulengracht/snapd@v0.0.0-20210719210640-8bde69bcc84e/cmd/snap/cmd_routine_portal_info.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 main 21 22 import ( 23 "fmt" 24 "path/filepath" 25 "text/template" 26 27 "github.com/jessevdk/go-flags" 28 29 "github.com/snapcore/snapd/client" 30 "github.com/snapcore/snapd/i18n" 31 "github.com/snapcore/snapd/sandbox/apparmor" 32 "github.com/snapcore/snapd/sandbox/cgroup" 33 ) 34 35 type cmdRoutinePortalInfo struct { 36 clientMixin 37 PortalInfoOptions struct { 38 Pid int 39 } `positional-args:"true" required:"true"` 40 } 41 42 var shortRoutinePortalInfoHelp = i18n.G("Return information about a process") 43 var longRoutinePortalInfoHelp = i18n.G(` 44 The portal-info command returns information about a process in keyfile format. 45 46 This command is used by the xdg-desktop-portal service to retrieve 47 information about snap confined processes. 48 `) 49 50 func init() { 51 addRoutineCommand("portal-info", shortRoutinePortalInfoHelp, longRoutinePortalInfoHelp, func() flags.Commander { 52 return &cmdRoutinePortalInfo{} 53 }, nil, []argDesc{{ 54 // TRANSLATORS: This needs to begin with < and end with > 55 name: i18n.G("<process ID>"), 56 // TRANSLATORS: This should not start with a lowercase letter. 57 desc: i18n.G("Process ID of confined app"), 58 }}) 59 } 60 61 var ( 62 cgroupSnapNameFromPid = cgroup.SnapNameFromPid 63 apparmorSnapAppFromPid = apparmor.SnapAppFromPid 64 ) 65 66 func (x *cmdRoutinePortalInfo) Execute(args []string) error { 67 if len(args) > 0 { 68 return ErrExtraArgs 69 } 70 71 snapName, err := cgroupSnapNameFromPid(x.PortalInfoOptions.Pid) 72 if err != nil { 73 return err 74 } 75 snap, _, err := x.client.Snap(snapName) 76 if err != nil { 77 return fmt.Errorf("cannot retrieve info for snap %q: %v", snapName, err) 78 } 79 80 // Try to identify the application name from AppArmor 81 var app *client.AppInfo 82 if snapName, appName, _, err := apparmorSnapAppFromPid(x.PortalInfoOptions.Pid); err == nil && snapName == snap.Name && appName != "" { 83 for i := range snap.Apps { 84 if snap.Apps[i].Name == appName { 85 app = &snap.Apps[i] 86 break 87 } 88 } 89 } 90 // As a fallback, pick an app with a desktop file, favouring 91 // the app named identically to the snap. 92 if app == nil { 93 for i := range snap.Apps { 94 if snap.Apps[i].DesktopFile != "" && (app == nil || snap.Apps[i].Name == snap.Name) { 95 app = &snap.Apps[i] 96 } 97 } 98 } 99 100 var desktopFile string 101 if app != nil { 102 desktopFile = filepath.Base(app.DesktopFile) 103 } 104 105 // Determine whether the snap has access to the network status 106 // TODO: use direct API for asking about interface being connected if 107 // that becomes available 108 connections, err := x.client.Connections(&client.ConnectionOptions{ 109 Snap: snap.Name, 110 Interface: "network-status", 111 }) 112 if err != nil { 113 return fmt.Errorf("cannot get connections for snap %q: %v", snap.Name, err) 114 } 115 // XXX: on non-AppArmor systems, or systems where there is only a 116 // partial AppArmor support, the snap may still be able to access the 117 // network despite the 'network' interface being disconnected 118 var hasNetworkStatus bool 119 for _, conn := range connections.Established { 120 if conn.Plug.Snap == snap.Name && conn.Interface == "network-status" { 121 hasNetworkStatus = true 122 break 123 } 124 } 125 126 const portalInfoTemplate = `[Snap Info] 127 InstanceName={{.Snap.Name}} 128 {{- if .App}} 129 AppName={{.App.Name}} 130 {{- end}} 131 {{- if .DesktopFile}} 132 DesktopFile={{.DesktopFile}} 133 {{- end}} 134 HasNetworkStatus={{.HasNetworkStatus}} 135 ` 136 t := template.Must(template.New("portal-info").Parse(portalInfoTemplate)) 137 data := struct { 138 Snap *client.Snap 139 App *client.AppInfo 140 DesktopFile string 141 HasNetworkStatus bool 142 }{ 143 Snap: snap, 144 App: app, 145 DesktopFile: desktopFile, 146 HasNetworkStatus: hasNetworkStatus, 147 } 148 if err := t.Execute(Stdout, data); err != nil { 149 return fmt.Errorf("cannot render output template: %s", err) 150 } 151 return nil 152 }