github.com/hugh712/snapd@v0.0.0-20200910133618-1a99902bd583/daemon/snap.go (about)

     1  // -*- Mode: Go; indent-tabs-mode: t -*-
     2  
     3  /*
     4   * Copyright (C) 2015-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 daemon
    21  
    22  import (
    23  	"errors"
    24  	"fmt"
    25  	"os"
    26  	"path/filepath"
    27  	"sort"
    28  	"strings"
    29  
    30  	"github.com/snapcore/snapd/client"
    31  	"github.com/snapcore/snapd/client/clientutil"
    32  	"github.com/snapcore/snapd/logger"
    33  	"github.com/snapcore/snapd/overlord/assertstate"
    34  	"github.com/snapcore/snapd/overlord/healthstate"
    35  	"github.com/snapcore/snapd/overlord/snapstate"
    36  	"github.com/snapcore/snapd/overlord/state"
    37  	"github.com/snapcore/snapd/snap"
    38  )
    39  
    40  var errNoSnap = errors.New("snap not installed")
    41  
    42  // snapIcon tries to find the icon inside the snap
    43  func snapIcon(info snap.PlaceInfo) string {
    44  	found, _ := filepath.Glob(filepath.Join(info.MountDir(), "meta", "gui", "icon.*"))
    45  	if len(found) == 0 {
    46  		return ""
    47  	}
    48  
    49  	return found[0]
    50  }
    51  
    52  func publisherAccount(st *state.State, snapID string) (snap.StoreAccount, error) {
    53  	if snapID == "" {
    54  		return snap.StoreAccount{}, nil
    55  	}
    56  
    57  	pubAcct, err := assertstate.Publisher(st, snapID)
    58  	if err != nil {
    59  		return snap.StoreAccount{}, fmt.Errorf("cannot find publisher details: %v", err)
    60  	}
    61  	return snap.StoreAccount{
    62  		ID:          pubAcct.AccountID(),
    63  		Username:    pubAcct.Username(),
    64  		DisplayName: pubAcct.DisplayName(),
    65  		Validation:  pubAcct.Validation(),
    66  	}, nil
    67  }
    68  
    69  type aboutSnap struct {
    70  	info   *snap.Info
    71  	snapst *snapstate.SnapState
    72  	health *client.SnapHealth
    73  }
    74  
    75  func clientHealthFromHealthstate(h *healthstate.HealthState) *client.SnapHealth {
    76  	if h == nil {
    77  		return nil
    78  	}
    79  	return &client.SnapHealth{
    80  		Revision:  h.Revision,
    81  		Timestamp: h.Timestamp,
    82  		Status:    h.Status.String(),
    83  		Message:   h.Message,
    84  		Code:      h.Code,
    85  	}
    86  }
    87  
    88  // localSnapInfo returns the information about the current snap for the given name plus the SnapState with the active flag and other snap revisions.
    89  func localSnapInfo(st *state.State, name string) (aboutSnap, error) {
    90  	st.Lock()
    91  	defer st.Unlock()
    92  
    93  	var snapst snapstate.SnapState
    94  	err := snapstate.Get(st, name, &snapst)
    95  	if err != nil && err != state.ErrNoState {
    96  		return aboutSnap{}, fmt.Errorf("cannot consult state: %v", err)
    97  	}
    98  
    99  	info, err := snapst.CurrentInfo()
   100  	if err == snapstate.ErrNoCurrent {
   101  		return aboutSnap{}, errNoSnap
   102  	}
   103  	if err != nil {
   104  		return aboutSnap{}, fmt.Errorf("cannot read snap details: %v", err)
   105  	}
   106  
   107  	info.Publisher, err = publisherAccount(st, info.SnapID)
   108  	if err != nil {
   109  		return aboutSnap{}, err
   110  	}
   111  
   112  	health, err := healthstate.Get(st, name)
   113  	if err != nil {
   114  		return aboutSnap{}, err
   115  	}
   116  
   117  	return aboutSnap{
   118  		info:   info,
   119  		snapst: &snapst,
   120  		health: clientHealthFromHealthstate(health),
   121  	}, nil
   122  }
   123  
   124  // allLocalSnapInfos returns the information about the all current snaps and their SnapStates.
   125  func allLocalSnapInfos(st *state.State, all bool, wanted map[string]bool) ([]aboutSnap, error) {
   126  	st.Lock()
   127  	defer st.Unlock()
   128  
   129  	snapStates, err := snapstate.All(st)
   130  	if err != nil {
   131  		return nil, err
   132  	}
   133  	about := make([]aboutSnap, 0, len(snapStates))
   134  
   135  	healths, err := healthstate.All(st)
   136  	if err != nil {
   137  		return nil, err
   138  	}
   139  
   140  	var firstErr error
   141  	for name, snapst := range snapStates {
   142  		if len(wanted) > 0 && !wanted[name] {
   143  			continue
   144  		}
   145  		health := clientHealthFromHealthstate(healths[name])
   146  		var aboutThis []aboutSnap
   147  		var info *snap.Info
   148  		var err error
   149  		if all {
   150  			for _, seq := range snapst.Sequence {
   151  				info, err = snap.ReadInfo(name, seq)
   152  				if err != nil {
   153  					// single revision may be broken
   154  					_, instanceKey := snap.SplitInstanceName(name)
   155  					info = &snap.Info{
   156  						SideInfo:    *seq,
   157  						InstanceKey: instanceKey,
   158  						Broken:      err.Error(),
   159  					}
   160  					// clear the error
   161  					err = nil
   162  				}
   163  				info.Publisher, err = publisherAccount(st, seq.SnapID)
   164  				if err != nil && firstErr == nil {
   165  					firstErr = err
   166  				}
   167  				aboutThis = append(aboutThis, aboutSnap{info, snapst, health})
   168  			}
   169  		} else {
   170  			info, err = snapst.CurrentInfo()
   171  			if err == nil {
   172  				info.Publisher, err = publisherAccount(st, info.SnapID)
   173  				aboutThis = append(aboutThis, aboutSnap{info, snapst, health})
   174  			}
   175  		}
   176  
   177  		if err != nil {
   178  			// XXX: aggregate instead?
   179  			if firstErr == nil {
   180  				firstErr = err
   181  			}
   182  			continue
   183  		}
   184  		about = append(about, aboutThis...)
   185  	}
   186  
   187  	return about, firstErr
   188  }
   189  
   190  // this differs from snap.SplitSnapApp in the handling of the
   191  // snap-only case:
   192  //   snap.SplitSnapApp("foo") is ("foo", "foo"),
   193  //   splitAppName("foo") is ("foo", "").
   194  func splitAppName(s string) (snap, app string) {
   195  	if idx := strings.IndexByte(s, '.'); idx > -1 {
   196  		return s[:idx], s[idx+1:]
   197  	}
   198  
   199  	return s, ""
   200  }
   201  
   202  type appInfoOptions struct {
   203  	service bool
   204  }
   205  
   206  func (opts appInfoOptions) String() string {
   207  	if opts.service {
   208  		return "service"
   209  	}
   210  
   211  	return "app"
   212  }
   213  
   214  // appInfosFor returns a sorted list apps described by names.
   215  //
   216  // * If names is empty, returns all apps of the wanted kinds (which
   217  //   could be an empty list).
   218  // * An element of names can be a snap name, in which case all apps
   219  //   from the snap of the wanted kind are included in the result (and
   220  //   it's an error if the snap has no apps of the wanted kind).
   221  // * An element of names can instead be snap.app, in which case that app is
   222  //   included in the result (and it's an error if the snap and app don't
   223  //   both exist, or if the app is not a wanted kind)
   224  // On error an appropriate error Response is returned; a nil Response means
   225  // no error.
   226  //
   227  // It's a programming error to call this with wanted having neither
   228  // services nor commands set.
   229  func appInfosFor(st *state.State, names []string, opts appInfoOptions) ([]*snap.AppInfo, Response) {
   230  	snapNames := make(map[string]bool)
   231  	requested := make(map[string]bool)
   232  	for _, name := range names {
   233  		requested[name] = true
   234  		name, _ = splitAppName(name)
   235  		snapNames[name] = true
   236  	}
   237  
   238  	snaps, err := allLocalSnapInfos(st, false, snapNames)
   239  	if err != nil {
   240  		return nil, InternalError("cannot list local snaps! %v", err)
   241  	}
   242  
   243  	found := make(map[string]bool)
   244  	appInfos := make([]*snap.AppInfo, 0, len(requested))
   245  	for _, snp := range snaps {
   246  		snapName := snp.info.InstanceName()
   247  		apps := make([]*snap.AppInfo, 0, len(snp.info.Apps))
   248  		for _, app := range snp.info.Apps {
   249  			if !opts.service || app.IsService() {
   250  				apps = append(apps, app)
   251  			}
   252  		}
   253  
   254  		if len(apps) == 0 && requested[snapName] {
   255  			return nil, AppNotFound("snap %q has no %ss", snapName, opts)
   256  		}
   257  
   258  		includeAll := len(requested) == 0 || requested[snapName]
   259  		if includeAll {
   260  			// want all services in a snap
   261  			found[snapName] = true
   262  		}
   263  
   264  		for _, app := range apps {
   265  			appName := snapName + "." + app.Name
   266  			if includeAll || requested[appName] {
   267  				appInfos = append(appInfos, app)
   268  				found[appName] = true
   269  			}
   270  		}
   271  	}
   272  
   273  	for k := range requested {
   274  		if !found[k] {
   275  			if snapNames[k] {
   276  				return nil, SnapNotFound(k, fmt.Errorf("snap %q not found", k))
   277  			} else {
   278  				snap, app := splitAppName(k)
   279  				return nil, AppNotFound("snap %q has no %s %q", snap, opts, app)
   280  			}
   281  		}
   282  	}
   283  
   284  	sort.Sort(snap.AppInfoBySnapApp(appInfos))
   285  
   286  	return appInfos, nil
   287  }
   288  
   289  func mapLocal(about aboutSnap, sd clientutil.StatusDecorator) *client.Snap {
   290  	localSnap, snapst := about.info, about.snapst
   291  	result, err := clientutil.ClientSnapFromSnapInfo(localSnap, sd)
   292  	if err != nil {
   293  		logger.Noticef("cannot get full app info for snap %q: %v", localSnap.InstanceName(), err)
   294  	}
   295  	result.InstalledSize = localSnap.Size
   296  
   297  	if icon := snapIcon(localSnap); icon != "" {
   298  		result.Icon = icon
   299  	}
   300  
   301  	result.Status = "installed"
   302  	if snapst.Active && localSnap.Revision == snapst.Current {
   303  		result.Status = "active"
   304  	}
   305  
   306  	result.TrackingChannel = snapst.TrackingChannel
   307  	result.IgnoreValidation = snapst.IgnoreValidation
   308  	result.CohortKey = snapst.CohortKey
   309  	result.DevMode = snapst.DevMode
   310  	result.TryMode = snapst.TryMode
   311  	result.JailMode = snapst.JailMode
   312  	result.MountedFrom = localSnap.MountFile()
   313  	if result.TryMode {
   314  		// Readlink instead of EvalSymlinks because it's only expected
   315  		// to be one level, and should still resolve if the target does
   316  		// not exist (this might help e.g. snapcraft clean up after a
   317  		// prime dir)
   318  		result.MountedFrom, _ = os.Readlink(result.MountedFrom)
   319  	}
   320  	result.Health = about.health
   321  
   322  	return result
   323  }
   324  
   325  func mapRemote(remoteSnap *snap.Info) *client.Snap {
   326  	result, err := clientutil.ClientSnapFromSnapInfo(remoteSnap, nil)
   327  	if err != nil {
   328  		logger.Noticef("cannot get full app info for snap %q: %v", remoteSnap.SnapName(), err)
   329  	}
   330  	result.DownloadSize = remoteSnap.Size
   331  	if remoteSnap.MustBuy {
   332  		result.Status = "priced"
   333  	} else {
   334  		result.Status = "available"
   335  	}
   336  
   337  	return result
   338  }