github.com/anonymouse64/snapd@v0.0.0-20210824153203-04c4c42d842d/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  
    28  	"github.com/snapcore/snapd/client"
    29  	"github.com/snapcore/snapd/client/clientutil"
    30  	"github.com/snapcore/snapd/logger"
    31  	"github.com/snapcore/snapd/overlord/assertstate"
    32  	"github.com/snapcore/snapd/overlord/healthstate"
    33  	"github.com/snapcore/snapd/overlord/snapstate"
    34  	"github.com/snapcore/snapd/overlord/state"
    35  	"github.com/snapcore/snapd/snap"
    36  )
    37  
    38  var errNoSnap = errors.New("snap not installed")
    39  
    40  type aboutSnap struct {
    41  	info   *snap.Info
    42  	snapst *snapstate.SnapState
    43  	health *client.SnapHealth
    44  }
    45  
    46  // localSnapInfo returns the information about the current snap for the given name plus the SnapState with the active flag and other snap revisions.
    47  func localSnapInfo(st *state.State, name string) (aboutSnap, error) {
    48  	st.Lock()
    49  	defer st.Unlock()
    50  
    51  	var snapst snapstate.SnapState
    52  	err := snapstate.Get(st, name, &snapst)
    53  	if err != nil && err != state.ErrNoState {
    54  		return aboutSnap{}, fmt.Errorf("cannot consult state: %v", err)
    55  	}
    56  
    57  	info, err := snapst.CurrentInfo()
    58  	if err == snapstate.ErrNoCurrent {
    59  		return aboutSnap{}, errNoSnap
    60  	}
    61  	if err != nil {
    62  		return aboutSnap{}, fmt.Errorf("cannot read snap details: %v", err)
    63  	}
    64  
    65  	info.Publisher, err = publisherAccount(st, info.SnapID)
    66  	if err != nil {
    67  		return aboutSnap{}, err
    68  	}
    69  
    70  	health, err := healthstate.Get(st, name)
    71  	if err != nil {
    72  		return aboutSnap{}, err
    73  	}
    74  
    75  	return aboutSnap{
    76  		info:   info,
    77  		snapst: &snapst,
    78  		health: clientHealthFromHealthstate(health),
    79  	}, nil
    80  }
    81  
    82  // allLocalSnapInfos returns the information about the all current snaps and their SnapStates.
    83  func allLocalSnapInfos(st *state.State, all bool, wanted map[string]bool) ([]aboutSnap, error) {
    84  	st.Lock()
    85  	defer st.Unlock()
    86  
    87  	snapStates, err := snapstate.All(st)
    88  	if err != nil {
    89  		return nil, err
    90  	}
    91  	about := make([]aboutSnap, 0, len(snapStates))
    92  
    93  	healths, err := healthstate.All(st)
    94  	if err != nil {
    95  		return nil, err
    96  	}
    97  
    98  	var firstErr error
    99  	for name, snapst := range snapStates {
   100  		if len(wanted) > 0 && !wanted[name] {
   101  			continue
   102  		}
   103  		health := clientHealthFromHealthstate(healths[name])
   104  		var aboutThis []aboutSnap
   105  		var info *snap.Info
   106  		var err error
   107  		if all {
   108  			for _, seq := range snapst.Sequence {
   109  				info, err = snap.ReadInfo(name, seq)
   110  				if err != nil {
   111  					// single revision may be broken
   112  					_, instanceKey := snap.SplitInstanceName(name)
   113  					info = &snap.Info{
   114  						SideInfo:    *seq,
   115  						InstanceKey: instanceKey,
   116  						Broken:      err.Error(),
   117  					}
   118  					// clear the error
   119  					err = nil
   120  				}
   121  				info.Publisher, err = publisherAccount(st, seq.SnapID)
   122  				if err != nil && firstErr == nil {
   123  					firstErr = err
   124  				}
   125  				aboutThis = append(aboutThis, aboutSnap{info, snapst, health})
   126  			}
   127  		} else {
   128  			info, err = snapst.CurrentInfo()
   129  			if err == nil {
   130  				info.Publisher, err = publisherAccount(st, info.SnapID)
   131  				aboutThis = append(aboutThis, aboutSnap{info, snapst, health})
   132  			}
   133  		}
   134  
   135  		if err != nil {
   136  			// XXX: aggregate instead?
   137  			if firstErr == nil {
   138  				firstErr = err
   139  			}
   140  			continue
   141  		}
   142  		about = append(about, aboutThis...)
   143  	}
   144  
   145  	return about, firstErr
   146  }
   147  
   148  func publisherAccount(st *state.State, snapID string) (snap.StoreAccount, error) {
   149  	if snapID == "" {
   150  		return snap.StoreAccount{}, nil
   151  	}
   152  
   153  	pubAcct, err := assertstate.Publisher(st, snapID)
   154  	if err != nil {
   155  		return snap.StoreAccount{}, fmt.Errorf("cannot find publisher details: %v", err)
   156  	}
   157  	return snap.StoreAccount{
   158  		ID:          pubAcct.AccountID(),
   159  		Username:    pubAcct.Username(),
   160  		DisplayName: pubAcct.DisplayName(),
   161  		Validation:  pubAcct.Validation(),
   162  	}, nil
   163  }
   164  
   165  func clientHealthFromHealthstate(h *healthstate.HealthState) *client.SnapHealth {
   166  	if h == nil {
   167  		return nil
   168  	}
   169  	return &client.SnapHealth{
   170  		Revision:  h.Revision,
   171  		Timestamp: h.Timestamp,
   172  		Status:    h.Status.String(),
   173  		Message:   h.Message,
   174  		Code:      h.Code,
   175  	}
   176  }
   177  
   178  func mapLocal(about aboutSnap, sd clientutil.StatusDecorator) *client.Snap {
   179  	localSnap, snapst := about.info, about.snapst
   180  	result, err := clientutil.ClientSnapFromSnapInfo(localSnap, sd)
   181  	if err != nil {
   182  		logger.Noticef("cannot get full app info for snap %q: %v", localSnap.InstanceName(), err)
   183  	}
   184  	result.InstalledSize = localSnap.Size
   185  
   186  	if icon := snapIcon(localSnap); icon != "" {
   187  		result.Icon = icon
   188  	}
   189  
   190  	result.Status = "installed"
   191  	if snapst.Active && localSnap.Revision == snapst.Current {
   192  		result.Status = "active"
   193  	}
   194  
   195  	result.TrackingChannel = snapst.TrackingChannel
   196  	result.IgnoreValidation = snapst.IgnoreValidation
   197  	result.CohortKey = snapst.CohortKey
   198  	result.DevMode = snapst.DevMode
   199  	result.TryMode = snapst.TryMode
   200  	result.JailMode = snapst.JailMode
   201  	result.MountedFrom = localSnap.MountFile()
   202  	if result.TryMode {
   203  		// Readlink instead of EvalSymlinks because it's only expected
   204  		// to be one level, and should still resolve if the target does
   205  		// not exist (this might help e.g. snapcraft clean up after a
   206  		// prime dir)
   207  		result.MountedFrom, _ = os.Readlink(result.MountedFrom)
   208  	}
   209  	result.Health = about.health
   210  
   211  	return result
   212  }
   213  
   214  // snapIcon tries to find the icon inside the snap
   215  func snapIcon(info snap.PlaceInfo) string {
   216  	found, _ := filepath.Glob(filepath.Join(info.MountDir(), "meta", "gui", "icon.*"))
   217  	if len(found) == 0 {
   218  		return ""
   219  	}
   220  
   221  	return found[0]
   222  }