github.com/stolowski/snapd@v0.0.0-20210407085831-115137ce5a22/dirs/dirs.go (about)

     1  // -*- Mode: Go; indent-tabs-mode: t -*-
     2  
     3  /*
     4   * Copyright (C) 2014-2015 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 dirs
    21  
    22  import (
    23  	"fmt"
    24  	"os"
    25  	"path/filepath"
    26  	"strconv"
    27  	"strings"
    28  
    29  	"github.com/snapcore/snapd/release"
    30  )
    31  
    32  // the various file paths
    33  var (
    34  	GlobalRootDir string
    35  
    36  	SnapMountDir string
    37  
    38  	DistroLibExecDir string
    39  
    40  	SnapBlobDir               string
    41  	SnapDataDir               string
    42  	SnapDataHomeGlob          string
    43  	SnapDownloadCacheDir      string
    44  	SnapAppArmorDir           string
    45  	SnapAppArmorAdditionalDir string
    46  	SnapConfineAppArmorDir    string
    47  	SnapSeccompBase           string
    48  	SnapSeccompDir            string
    49  	SnapMountPolicyDir        string
    50  	SnapUdevRulesDir          string
    51  	SnapKModModulesDir        string
    52  	LocaleDir                 string
    53  	SnapMetaDir               string
    54  	SnapdSocket               string
    55  	SnapSocket                string
    56  	SnapRunDir                string
    57  	SnapRunNsDir              string
    58  	SnapRunLockDir            string
    59  	SnapBootstrapRunDir       string
    60  
    61  	SnapdMaintenanceFile string
    62  
    63  	SnapdStoreSSLCertsDir string
    64  
    65  	SnapSeedDir   string
    66  	SnapDeviceDir string
    67  
    68  	SnapAssertsDBDir      string
    69  	SnapCookieDir         string
    70  	SnapTrustedAccountKey string
    71  	SnapAssertsSpoolDir   string
    72  	SnapSeqDir            string
    73  
    74  	SnapStateFile     string
    75  	SnapSystemKeyFile string
    76  
    77  	SnapRepairDir        string
    78  	SnapRepairStateFile  string
    79  	SnapRepairRunDir     string
    80  	SnapRepairAssertsDir string
    81  	SnapRunRepairDir     string
    82  
    83  	SnapRollbackDir string
    84  
    85  	SnapCacheDir        string
    86  	SnapNamesFile       string
    87  	SnapSectionsFile    string
    88  	SnapCommandsDB      string
    89  	SnapAuxStoreInfoDir string
    90  
    91  	SnapBinariesDir     string
    92  	SnapServicesDir     string
    93  	SnapUserServicesDir string
    94  	SnapSystemdConfDir  string
    95  	SnapDesktopFilesDir string
    96  	SnapDesktopIconsDir string
    97  
    98  	SnapDBusSessionPolicyDir   string
    99  	SnapDBusSystemPolicyDir    string
   100  	SnapDBusSessionServicesDir string
   101  	SnapDBusSystemServicesDir  string
   102  
   103  	SnapModeenvFile   string
   104  	SnapBootAssetsDir string
   105  	SnapFDEDir        string
   106  	SnapSaveDir       string
   107  	SnapDeviceSaveDir string
   108  
   109  	CloudMetaDataFile     string
   110  	CloudInstanceDataFile string
   111  
   112  	ClassicDir string
   113  
   114  	XdgRuntimeDirBase string
   115  	XdgRuntimeDirGlob string
   116  
   117  	CompletionHelperInCore string
   118  	CompletersDir          string
   119  
   120  	SystemFontsDir            string
   121  	SystemLocalFontsDir       string
   122  	SystemFontconfigCacheDirs []string
   123  
   124  	SnapshotsDir string
   125  
   126  	ErrtrackerDbDir string
   127  	SysfsDir        string
   128  
   129  	FeaturesDir string
   130  )
   131  
   132  const (
   133  	defaultSnapMountDir = "/snap"
   134  
   135  	// These are directories which are static inside the core snap and
   136  	// can never be prefixed as they will be always absolute once we
   137  	// are in the snap confinement environment.
   138  	CoreLibExecDir   = "/usr/lib/snapd"
   139  	CoreSnapMountDir = "/snap"
   140  
   141  	// Directory with snap data inside user's home
   142  	UserHomeSnapDir = "snap"
   143  
   144  	// LocalInstallBlobTempPrefix is used by local install code:
   145  	// * in daemon to spool the snap file to <SnapBlobDir>/<LocalInstallBlobTempPrefix>*
   146  	// * in snapstate to auto-cleans them up using the same prefix
   147  	LocalInstallBlobTempPrefix = ".local-install-"
   148  )
   149  
   150  var (
   151  	// not exported because it does not honor the global rootdir
   152  	snappyDir = filepath.Join("var", "lib", "snapd")
   153  
   154  	callbacks = []func(string){}
   155  )
   156  
   157  func init() {
   158  	// init the global directories at startup
   159  	root := os.Getenv("SNAPPY_GLOBAL_ROOT")
   160  
   161  	SetRootDir(root)
   162  }
   163  
   164  // StripRootDir strips the custom global root directory from the specified argument.
   165  func StripRootDir(dir string) string {
   166  	if !filepath.IsAbs(dir) {
   167  		panic(fmt.Sprintf("supplied path is not absolute %q", dir))
   168  	}
   169  	if !strings.HasPrefix(dir, GlobalRootDir) {
   170  		panic(fmt.Sprintf("supplied path is not related to global root %q", dir))
   171  	}
   172  	result, err := filepath.Rel(GlobalRootDir, dir)
   173  	if err != nil {
   174  		panic(err)
   175  	}
   176  	return "/" + result
   177  }
   178  
   179  // SupportsClassicConfinement returns true if the current directory layout supports classic confinement.
   180  func SupportsClassicConfinement() bool {
   181  	// Core systems don't support classic confinement as a policy decision.
   182  	if !release.OnClassic {
   183  		return false
   184  	}
   185  
   186  	// Classic systems support classic confinement if using the primary mount
   187  	// location for snaps, that is /snap or if using the alternate mount
   188  	// location, /var/lib/snapd/snap along with the /snap ->
   189  	// /var/lib/snapd/snap symlink in place.
   190  	smd := filepath.Join(GlobalRootDir, defaultSnapMountDir)
   191  	if SnapMountDir == smd {
   192  		return true
   193  	}
   194  	fi, err := os.Lstat(smd)
   195  	if err == nil && fi.Mode()&os.ModeSymlink != 0 {
   196  		if target, err := filepath.EvalSymlinks(smd); err == nil {
   197  			if target == SnapMountDir {
   198  				return true
   199  			}
   200  		}
   201  	}
   202  
   203  	return false
   204  }
   205  
   206  var metaSnapPath = "/meta/snap.yaml"
   207  
   208  // isInsideBaseSnap returns true if the process is inside a base snap environment.
   209  //
   210  // The things that count as a base snap are:
   211  // - any base snap mounted at /
   212  // - any os snap mounted at /
   213  func isInsideBaseSnap() (bool, error) {
   214  	_, err := os.Stat(metaSnapPath)
   215  	if err != nil && os.IsNotExist(err) {
   216  		return false, nil
   217  	}
   218  	return err == nil, err
   219  }
   220  
   221  // SnapdStateDir returns the path to /var/lib/snapd dir under rootdir.
   222  func SnapdStateDir(rootdir string) string {
   223  	return filepath.Join(rootdir, snappyDir)
   224  }
   225  
   226  // SnapBlobDirUnder returns the path to the snap blob dir under rootdir.
   227  func SnapBlobDirUnder(rootdir string) string {
   228  	return filepath.Join(rootdir, snappyDir, "snaps")
   229  }
   230  
   231  // SnapSeedDirUnder returns the path to the snap seed dir under rootdir.
   232  func SnapSeedDirUnder(rootdir string) string {
   233  	return filepath.Join(rootdir, snappyDir, "seed")
   234  }
   235  
   236  // SnapStateFileUnder returns the path to snapd state file under rootdir.
   237  func SnapStateFileUnder(rootdir string) string {
   238  	return filepath.Join(rootdir, snappyDir, "state.json")
   239  }
   240  
   241  // SnapModeenvFileUnder returns the path to the modeenv file under rootdir.
   242  func SnapModeenvFileUnder(rootdir string) string {
   243  	return filepath.Join(rootdir, snappyDir, "modeenv")
   244  }
   245  
   246  // FeaturesDirUnder returns the path to the features dir under rootdir.
   247  func FeaturesDirUnder(rootdir string) string {
   248  	return filepath.Join(rootdir, snappyDir, "features")
   249  }
   250  
   251  // SnapSystemdConfDirUnder returns the path to the systemd conf dir under
   252  // rootdir.
   253  func SnapSystemdConfDirUnder(rootdir string) string {
   254  	return filepath.Join(rootdir, "/etc/systemd/system.conf.d")
   255  }
   256  
   257  // SnapBootAssetsDirUnder returns the path to boot assets directory under a
   258  // rootdir.
   259  func SnapBootAssetsDirUnder(rootdir string) string {
   260  	return filepath.Join(rootdir, snappyDir, "boot-assets")
   261  }
   262  
   263  // SnapDeviceDirUnder returns the path to device directory under rootdir.
   264  func SnapDeviceDirUnder(rootdir string) string {
   265  	return filepath.Join(rootdir, snappyDir, "device")
   266  }
   267  
   268  // SnapFDEDirUnder returns the path to full disk encryption state directory
   269  // under rootdir.
   270  func SnapFDEDirUnder(rootdir string) string {
   271  	return filepath.Join(SnapDeviceDirUnder(rootdir), "fde")
   272  }
   273  
   274  // SnapSaveDirUnder returns the path to device save directory under rootdir.
   275  func SnapSaveDirUnder(rootdir string) string {
   276  	return filepath.Join(rootdir, snappyDir, "save")
   277  }
   278  
   279  // SnapFDEDirUnderSave returns the path to full disk encryption state directory
   280  // inside the given save tree dir.
   281  func SnapFDEDirUnderSave(savedir string) string {
   282  	return filepath.Join(savedir, "device/fde")
   283  }
   284  
   285  // AddRootDirCallback registers a callback for whenever the global root
   286  // directory (set by SetRootDir) is changed to enable updates to variables in
   287  // other packages that depend on its location.
   288  func AddRootDirCallback(c func(string)) {
   289  	callbacks = append(callbacks, c)
   290  }
   291  
   292  // SetRootDir allows settings a new global root directory, this is useful
   293  // for e.g. chroot operations
   294  func SetRootDir(rootdir string) {
   295  	if rootdir == "" {
   296  		rootdir = "/"
   297  	}
   298  	GlobalRootDir = rootdir
   299  
   300  	altDirDistros := []string{
   301  		"antergos",
   302  		"arch",
   303  		"archlinux",
   304  		"fedora",
   305  		"gentoo",
   306  		"manjaro",
   307  		"manjaro-arm",
   308  	}
   309  
   310  	isInsideBase, _ := isInsideBaseSnap()
   311  	if !isInsideBase && release.DistroLike(altDirDistros...) {
   312  		SnapMountDir = filepath.Join(rootdir, "/var/lib/snapd/snap")
   313  	} else {
   314  		SnapMountDir = filepath.Join(rootdir, defaultSnapMountDir)
   315  	}
   316  
   317  	SnapDataDir = filepath.Join(rootdir, "/var/snap")
   318  	SnapDataHomeGlob = filepath.Join(rootdir, "/home/*/", UserHomeSnapDir)
   319  	SnapAppArmorDir = filepath.Join(rootdir, snappyDir, "apparmor", "profiles")
   320  	SnapConfineAppArmorDir = filepath.Join(rootdir, snappyDir, "apparmor", "snap-confine")
   321  	SnapAppArmorAdditionalDir = filepath.Join(rootdir, snappyDir, "apparmor", "additional")
   322  	SnapDownloadCacheDir = filepath.Join(rootdir, snappyDir, "cache")
   323  	SnapSeccompBase = filepath.Join(rootdir, snappyDir, "seccomp")
   324  	SnapSeccompDir = filepath.Join(SnapSeccompBase, "bpf")
   325  	SnapMountPolicyDir = filepath.Join(rootdir, snappyDir, "mount")
   326  	SnapMetaDir = filepath.Join(rootdir, snappyDir, "meta")
   327  	SnapdMaintenanceFile = filepath.Join(rootdir, snappyDir, "maintenance.json")
   328  	SnapBlobDir = SnapBlobDirUnder(rootdir)
   329  	// ${snappyDir}/desktop is added to $XDG_DATA_DIRS.
   330  	// Subdirectories are interpreted according to the relevant
   331  	// freedesktop.org specifications
   332  	SnapDesktopFilesDir = filepath.Join(rootdir, snappyDir, "desktop", "applications")
   333  	SnapDesktopIconsDir = filepath.Join(rootdir, snappyDir, "desktop", "icons")
   334  	SnapRunDir = filepath.Join(rootdir, "/run/snapd")
   335  	SnapRunNsDir = filepath.Join(SnapRunDir, "/ns")
   336  	SnapRunLockDir = filepath.Join(SnapRunDir, "/lock")
   337  
   338  	SnapBootstrapRunDir = filepath.Join(SnapRunDir, "snap-bootstrap")
   339  
   340  	SnapdStoreSSLCertsDir = filepath.Join(rootdir, snappyDir, "ssl/store-certs")
   341  
   342  	// keep in sync with the debian/snapd.socket file:
   343  	SnapdSocket = filepath.Join(rootdir, "/run/snapd.socket")
   344  	SnapSocket = filepath.Join(rootdir, "/run/snapd-snap.socket")
   345  
   346  	SnapAssertsDBDir = filepath.Join(rootdir, snappyDir, "assertions")
   347  	SnapCookieDir = filepath.Join(rootdir, snappyDir, "cookie")
   348  	SnapAssertsSpoolDir = filepath.Join(rootdir, "run/snapd/auto-import")
   349  	SnapSeqDir = filepath.Join(rootdir, snappyDir, "sequence")
   350  
   351  	SnapStateFile = SnapStateFileUnder(rootdir)
   352  	SnapSystemKeyFile = filepath.Join(rootdir, snappyDir, "system-key")
   353  
   354  	SnapCacheDir = filepath.Join(rootdir, "/var/cache/snapd")
   355  	SnapNamesFile = filepath.Join(SnapCacheDir, "names")
   356  	SnapSectionsFile = filepath.Join(SnapCacheDir, "sections")
   357  	SnapCommandsDB = filepath.Join(SnapCacheDir, "commands.db")
   358  	SnapAuxStoreInfoDir = filepath.Join(SnapCacheDir, "aux")
   359  
   360  	SnapSeedDir = SnapSeedDirUnder(rootdir)
   361  	SnapDeviceDir = SnapDeviceDirUnder(rootdir)
   362  
   363  	SnapModeenvFile = SnapModeenvFileUnder(rootdir)
   364  	SnapBootAssetsDir = SnapBootAssetsDirUnder(rootdir)
   365  	SnapFDEDir = SnapFDEDirUnder(rootdir)
   366  	SnapSaveDir = SnapSaveDirUnder(rootdir)
   367  	SnapDeviceSaveDir = filepath.Join(SnapSaveDir, "device")
   368  
   369  	SnapRepairDir = filepath.Join(rootdir, snappyDir, "repair")
   370  	SnapRepairStateFile = filepath.Join(SnapRepairDir, "repair.json")
   371  	SnapRepairRunDir = filepath.Join(SnapRepairDir, "run")
   372  	SnapRepairAssertsDir = filepath.Join(SnapRepairDir, "assertions")
   373  	SnapRunRepairDir = filepath.Join(SnapRunDir, "repair")
   374  
   375  	SnapRollbackDir = filepath.Join(rootdir, snappyDir, "rollback")
   376  
   377  	SnapBinariesDir = filepath.Join(SnapMountDir, "bin")
   378  	SnapServicesDir = filepath.Join(rootdir, "/etc/systemd/system")
   379  	SnapUserServicesDir = filepath.Join(rootdir, "/etc/systemd/user")
   380  	SnapSystemdConfDir = SnapSystemdConfDirUnder(rootdir)
   381  
   382  	SnapDBusSystemPolicyDir = filepath.Join(rootdir, "/etc/dbus-1/system.d")
   383  	SnapDBusSessionPolicyDir = filepath.Join(rootdir, "/etc/dbus-1/session.d")
   384  	// Use 'dbus-1/services' and `dbus-1/system-services' to mirror
   385  	// '/usr/share/dbus-1' hierarchy.
   386  	SnapDBusSessionServicesDir = filepath.Join(rootdir, snappyDir, "dbus-1", "services")
   387  	SnapDBusSystemServicesDir = filepath.Join(rootdir, snappyDir, "dbus-1", "system-services")
   388  
   389  	CloudInstanceDataFile = filepath.Join(rootdir, "/run/cloud-init/instance-data.json")
   390  
   391  	SnapUdevRulesDir = filepath.Join(rootdir, "/etc/udev/rules.d")
   392  
   393  	SnapKModModulesDir = filepath.Join(rootdir, "/etc/modules-load.d/")
   394  
   395  	LocaleDir = filepath.Join(rootdir, "/usr/share/locale")
   396  	ClassicDir = filepath.Join(rootdir, "/writable/classic")
   397  
   398  	opensuseTWWithLibexec := func() bool {
   399  		// XXX: this is pretty naive if openSUSE ever starts going back
   400  		// and forth about the change
   401  		if !release.DistroLike("opensuse-tumbleweed") {
   402  			return false
   403  		}
   404  		v, err := strconv.Atoi(release.ReleaseInfo.VersionID)
   405  		if err != nil {
   406  			// nothing we can do here
   407  			return false
   408  		}
   409  		// first seen on snapshot "20200826"
   410  		if v < 20200826 {
   411  			return false
   412  		}
   413  		return true
   414  	}
   415  
   416  	if release.DistroLike("fedora") || opensuseTWWithLibexec() {
   417  		// RHEL, CentOS, Fedora and derivatives, some more recent
   418  		// snapshots of openSUSE Tumbleweed;
   419  		// both RHEL and CentOS list "fedora" in ID_LIKE
   420  		DistroLibExecDir = filepath.Join(rootdir, "/usr/libexec/snapd")
   421  	} else {
   422  		DistroLibExecDir = filepath.Join(rootdir, "/usr/lib/snapd")
   423  	}
   424  
   425  	XdgRuntimeDirBase = filepath.Join(rootdir, "/run/user")
   426  	XdgRuntimeDirGlob = filepath.Join(XdgRuntimeDirBase, "*/")
   427  
   428  	CompletionHelperInCore = filepath.Join(CoreLibExecDir, "etelpmoc.sh")
   429  	CompletersDir = filepath.Join(rootdir, "/usr/share/bash-completion/completions/")
   430  
   431  	// These paths agree across all supported distros
   432  	SystemFontsDir = filepath.Join(rootdir, "/usr/share/fonts")
   433  	SystemLocalFontsDir = filepath.Join(rootdir, "/usr/local/share/fonts")
   434  	// The cache path is true for Ubuntu, Debian, openSUSE, Arch
   435  	SystemFontconfigCacheDirs = []string{filepath.Join(rootdir, "/var/cache/fontconfig")}
   436  	if release.DistroLike("fedora") && !release.DistroLike("amzn") {
   437  		// Applies to Fedora and CentOS, Amazon Linux 2 is behind with
   438  		// updates to fontconfig and uses /var/cache/fontconfig instead,
   439  		// see:
   440  		// https://fedoraproject.org/wiki/Changes/FontconfigCacheDirChange
   441  		// https://bugzilla.redhat.com/show_bug.cgi?id=1416380
   442  		// https://bugzilla.redhat.com/show_bug.cgi?id=1377367
   443  		//
   444  		// However, snaps may still use older libfontconfig, which fails
   445  		// to parse the new config and defaults to
   446  		// /var/cache/fontconfig. In this case we need to make both
   447  		// locations available
   448  		SystemFontconfigCacheDirs = append(SystemFontconfigCacheDirs, filepath.Join(rootdir, "/usr/lib/fontconfig/cache"))
   449  	}
   450  
   451  	SnapshotsDir = filepath.Join(rootdir, snappyDir, "snapshots")
   452  
   453  	ErrtrackerDbDir = filepath.Join(rootdir, snappyDir, "errtracker.db")
   454  	SysfsDir = filepath.Join(rootdir, "/sys")
   455  
   456  	FeaturesDir = FeaturesDirUnder(rootdir)
   457  
   458  	// call the callbacks last so that the callbacks can just reference the
   459  	// global vars if they want, instead of using the new rootdir directly
   460  	for _, c := range callbacks {
   461  		c(rootdir)
   462  	}
   463  }
   464  
   465  // what inside a (non-classic) snap is /usr/lib/snapd, outside can come from different places
   466  func libExecOutside(base string) string {
   467  	if base == "" {
   468  		// no explicit base; core is it
   469  		return filepath.Join(SnapMountDir, "core/current/usr/lib/snapd")
   470  	}
   471  	// if a base is set, libexec comes from the snapd snap if it's
   472  	// installed, and otherwise from the distro.
   473  	p := filepath.Join(SnapMountDir, "snapd/current/usr/lib/snapd")
   474  	if st, err := os.Stat(p); err == nil && st.IsDir() {
   475  		return p
   476  	}
   477  	return DistroLibExecDir
   478  }
   479  
   480  func CompleteShPath(base string) string {
   481  	return filepath.Join(libExecOutside(base), "complete.sh")
   482  }
   483  
   484  func IsCompleteShSymlink(compPath string) bool {
   485  	target, err := os.Readlink(compPath)
   486  	// check if the target paths ends with "/snapd/complete.sh"
   487  	return err == nil && filepath.Base(filepath.Dir(target)) == "snapd" && filepath.Base(target) == "complete.sh"
   488  }