github.com/meulengracht/snapd@v0.0.0-20210719210640-8bde69bcc84e/cmd/snapd-generator/main.c (about)

     1  /*
     2   * Copyright (C) 2018 Canonical Ltd
     3   *
     4   * This program is free software: you can redistribute it and/or modify
     5   * it under the terms of the GNU General Public License version 3 as
     6   * published by the Free Software Foundation.
     7   *
     8   * This program is distributed in the hope that it will be useful,
     9   * but WITHOUT ANY WARRANTY; without even the implied warranty of
    10   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    11   * GNU General Public License for more details.
    12   *
    13   * You should have received a copy of the GNU General Public License
    14   * along with this program.  If not, see <http://www.gnu.org/licenses/>.
    15   *
    16   */
    17  
    18  #include <stdio.h>
    19  #include <stdlib.h>
    20  #include <string.h>
    21  #include <sys/stat.h>
    22  #include <dirent.h>
    23  #include <errno.h>
    24  #include <unistd.h>
    25  
    26  #include "config.h"
    27  
    28  #include "../libsnap-confine-private/cleanup-funcs.h"
    29  #include "../libsnap-confine-private/infofile.h"
    30  #include "../libsnap-confine-private/mountinfo.h"
    31  #include "../libsnap-confine-private/string-utils.h"
    32  
    33  static sc_mountinfo_entry *find_root_mountinfo(sc_mountinfo * mounts)
    34  {
    35  	sc_mountinfo_entry *cur, *root = NULL;
    36  	for (cur = sc_first_mountinfo_entry(mounts); cur != NULL;
    37  	     cur = sc_next_mountinfo_entry(cur)) {
    38  		// Look for the mount info entry for the root file-system.
    39  		if (sc_streq("/", cur->mount_dir)) {
    40  			root = cur;
    41  		}
    42  	}
    43  	return root;
    44  }
    45  
    46  static int ensure_root_fs_shared(const char *normal_dir)
    47  {
    48  	// Load /proc/self/mountinfo so that we can inspect the root filesystem.
    49  	sc_mountinfo *mounts SC_CLEANUP(sc_cleanup_mountinfo) = NULL;
    50  	mounts = sc_parse_mountinfo(NULL);
    51  	if (!mounts) {
    52  		fprintf(stderr, "cannot open or parse /proc/self/mountinfo\n");
    53  		return 1;
    54  	}
    55  
    56  	sc_mountinfo_entry *root = find_root_mountinfo(mounts);
    57  	if (!root) {
    58  		fprintf(stderr,
    59  			"cannot find mountinfo entry of the root filesystem\n");
    60  		return 1;
    61  	}
    62  	// Check if the root file-system is mounted with shared option.
    63  	if (strstr(root->optional_fields, "shared:") != NULL) {
    64  		// The workaround is not needed, everything is good as-is.
    65  		return 0;
    66  	}
    67  	// Construct the file name for a new systemd mount unit.
    68  	char fname[PATH_MAX + 1] = { 0 };
    69  	sc_must_snprintf(fname, sizeof fname,
    70  			 "%s/" SNAP_MOUNT_DIR_SYSTEMD_UNIT ".mount",
    71  			 normal_dir);
    72  
    73  	// Open the mount unit and write the contents.
    74  	FILE *f SC_CLEANUP(sc_cleanup_file) = NULL;
    75  	f = fopen(fname, "wt");
    76  	if (!f) {
    77  		fprintf(stderr, "cannot open %s: %m\n", fname);
    78  		return 1;
    79  	}
    80  	fprintf(f,
    81  		"# Ensure that snap mount directory is mounted \"shared\" "
    82  		"so snaps can be refreshed correctly (LP: #1668759).\n");
    83  	fprintf(f, "[Unit]\n");
    84  	fprintf(f,
    85  		"Description=Ensure that the snap directory "
    86  		"shares mount events.\n");
    87  	fprintf(f, "[Mount]\n");
    88  	fprintf(f, "What=" SNAP_MOUNT_DIR "\n");
    89  	fprintf(f, "Where=" SNAP_MOUNT_DIR "\n");
    90  	fprintf(f, "Type=none\n");
    91  	fprintf(f, "Options=bind,shared\n");
    92  	fprintf(f, "[Install]\n");
    93  	fprintf(f, "WantedBy=local-fs.target\n");
    94  	return 0;
    95  }
    96  
    97  static bool file_exists(const char *path)
    98  {
    99  	struct stat buf;
   100  	// Not using lstat to automatically resolve symbolic links,
   101  	// including handling, as an error, dangling symbolic links.
   102  	return stat(path, &buf) == 0 && (buf.st_mode & S_IFMT) == S_IFREG;
   103  }
   104  
   105  // PATH may not be set (the case on 16.04), in which case this is the fallback
   106  // for looking up squashfuse / snapfuse executable.
   107  // Based on what systemd uses when compiled for systems with "unmerged /usr"
   108  // (see man systemd.exec).
   109  static const char *const path_fallback =
   110      "/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin";
   111  
   112  static bool executable_exists(const char *name)
   113  {
   114  	char *path = getenv("PATH");
   115  	char *path_copy SC_CLEANUP(sc_cleanup_string) = NULL;
   116  	if (path == NULL) {
   117  		path_copy = sc_strdup(path_fallback);
   118  	} else {
   119  		path_copy = sc_strdup(path);
   120  	}
   121  
   122  	char *ptr = NULL;
   123  	char *token = strtok_r(path_copy, ":", &ptr);
   124  	char fname[PATH_MAX + 1] = { 0 };
   125  	while (token) {
   126  		sc_must_snprintf(fname, sizeof fname, "%s/%s", token, name);
   127  		if (access(fname, X_OK) == 0) {
   128  			return true;
   129  		}
   130  		token = strtok_r(NULL, ":", &ptr);
   131  	}
   132  	return false;
   133  }
   134  
   135  static bool is_snap_try_snap_unit(const char *units_dir,
   136  				  const char *mount_unit_name)
   137  {
   138  	char fname[PATH_MAX + 1] = { 0 };
   139  	sc_must_snprintf(fname, sizeof fname, "%s/%s", units_dir,
   140  			 mount_unit_name);
   141  	FILE *f SC_CLEANUP(sc_cleanup_file) = NULL;
   142  	f = fopen(fname, "r");
   143  	if (!f) {
   144  		// not really expected
   145  		fprintf(stderr, "cannot open mount unit %s: %m\n", fname);
   146  		return false;
   147  	}
   148  
   149  	char *what SC_CLEANUP(sc_cleanup_string) = NULL;
   150  	sc_error *err = NULL;
   151  	if (sc_infofile_get_ini_section_key(f, "Mount", "What", &what, &err) <
   152  	    0) {
   153  		fprintf(stderr, "cannot read mount unit %s: %s\n", fname,
   154  			sc_error_msg(err));
   155  		sc_cleanup_error(&err);
   156  		return false;
   157  	}
   158  
   159  	struct stat st;
   160  	// if What points to a directory, then it's a snap try unit.
   161  	return stat(what, &st) == 0 && (st.st_mode & S_IFMT) == S_IFDIR;
   162  }
   163  
   164  static int ensure_fusesquashfs_inside_container(const char *normal_dir)
   165  {
   166  	// check if we are running inside a container, systemd
   167  	// provides this file all the way back to trusty if run in a
   168  	// container
   169  	if (!file_exists("/run/systemd/container")) {
   170  		return 0;
   171  	}
   172  
   173  	const char *fstype;
   174  	if (executable_exists("squashfuse")) {
   175  		fstype = "fuse.squashfuse";
   176  	} else if (executable_exists("snapfuse")) {
   177  		fstype = "fuse.snapfuse";
   178  	} else {
   179  		fprintf(stderr,
   180  			"cannot find squashfuse or snapfuse executable\n");
   181  		return 2;
   182  	}
   183  
   184  	DIR *units_dir SC_CLEANUP(sc_cleanup_closedir) = NULL;
   185  	units_dir = opendir("/etc/systemd/system");
   186  	if (units_dir == NULL) {
   187  		// nothing to do
   188  		return 0;
   189  	}
   190  
   191  	char fname[PATH_MAX + 1] = { 0 };
   192  
   193  	struct dirent *ent;
   194  	while (ent = readdir(units_dir)) {
   195  		// find snap mount units, i.e:
   196  		// snap-somename.mount or var-lib-snapd-snap-somename.mount
   197  		if (!sc_endswith(ent->d_name, ".mount")) {
   198  			continue;
   199  		}
   200  		if (!(sc_startswith(ent->d_name, "snap-")
   201  		      || sc_startswith(ent->d_name, "var-lib-snapd-snap-"))) {
   202  			continue;
   203  		}
   204  		if (is_snap_try_snap_unit("/etc/systemd/system", ent->d_name)) {
   205  			continue;
   206  		}
   207  		sc_must_snprintf(fname, sizeof fname,
   208  				 "%s/%s.d", normal_dir, ent->d_name);
   209  		if (mkdir(fname, 0755) != 0) {
   210  			if (errno != EEXIST) {
   211  				fprintf(stderr,
   212  					"cannot create %s directory: %m\n",
   213  					fname);
   214  				return 2;
   215  			}
   216  		}
   217  
   218  		sc_must_snprintf(fname, sizeof fname,
   219  				 "%s/%s.d/container.conf", normal_dir,
   220  				 ent->d_name);
   221  
   222  		FILE *f SC_CLEANUP(sc_cleanup_file) = NULL;
   223  		f = fopen(fname, "w");
   224  		if (!f) {
   225  			fprintf(stderr, "cannot open %s: %m\n", fname);
   226  			return 2;
   227  		}
   228  		fprintf(f,
   229  			"[Mount]\nType=%s\nOptions=nodev,ro,x-gdu.hide,x-gvfs-hide,allow_other\nLazyUnmount=yes\n",
   230  			fstype);
   231  	}
   232  
   233  	return 0;
   234  }
   235  
   236  int main(int argc, char **argv)
   237  {
   238  	if (argc != 4) {
   239  		printf
   240  		    ("usage: snapd-generator normal-dir early-dir late-dir\n");
   241  		return 1;
   242  	}
   243  	const char *normal_dir = argv[1];
   244  	// For reference, but we don't use those variables here.
   245  	// const char *early_dir = argv[2];
   246  	// const char *late_dir = argv[3];
   247  
   248  	int status = 0;
   249  	status = ensure_root_fs_shared(normal_dir);
   250  	status |= ensure_fusesquashfs_inside_container(normal_dir);
   251  
   252  	return status;
   253  }