コード例 #1
0
ファイル: helpers.c プロジェクト: tpepper/swupd-client
/* Ensure that a directory either doesn't exist
 * or does exist and belongs to root.
 * If it exists, but is not a directory or belonging to root, remove it
 * returns true if it doesn't exist
 */
static int ensure_root_owned_dir(const char *dirname)
{
	struct stat sb;
	int ret = stat(dirname, &sb);
	if (ret && (errno == ENOENT)) {
		return true;
	}
	if ((sb.st_uid == 0) &&
	    (S_ISDIR(sb.st_mode)) &&
	    ((sb.st_mode & 0777) == 0700)) {
		return false;
	}
	/* Oops, not owned by root or
	 * not a directory or wrong perms
	 */
	swupd_rm(dirname);
	errno = 0;
	ret = stat(dirname, &sb);
	if ((ret != -1) || (errno != ENOENT)) {
		fprintf(stderr,
			"Error \"%s\" not owned by root, is not a directory, "
			"or has the wrong permissions.\n"
			"However it couldn't be deleted. stat gives error '%s'\n",
			dirname, strerror(errno));
		exit(100);
	}
	return true; /* doesn't exist now */
}
コード例 #2
0
ファイル: helpers.c プロジェクト: tpepper/swupd-client
static int swupd_rm_dir(const char *path)
{
	DIR *dir;
	struct dirent *entry;
	char *filename = NULL;
	int ret = 0;
	int err;

	dir = opendir(path);
	if (dir == NULL) {
		if (errno == ENOENT) {
			ret = 0;
			goto exit;
		} else {
			ret = -1;
			goto exit;
		}
	}

	while (true) {
		errno = 0;
		entry = readdir(dir);
		if (!entry) {
			if (!errno) {
				ret = errno;
			}
			break;
		}

		if (!strcmp(entry->d_name, ".") ||
		    !strcmp(entry->d_name, "..")) {
			continue;
		}

		free_string(&filename);
		string_or_die(&filename, "%s/%s", path, entry->d_name);

		err = swupd_rm(filename);
		if (err) {
			ret = -1;
			goto exit;
		}
	}

	/* Delete directory once it's empty */
	err = rmdir(path);
	if (err) {
		if (errno == ENOENT) {
		} else {
			ret = -1;
			goto exit;
		}
	}

exit:
	closedir(dir);
	free_string(&filename);
	return ret;
}
コード例 #3
0
ファイル: helpers.c プロジェクト: pdxjohnny/swupd-client
static int swupd_rm_dir(const char *path)
{
	DIR *dir;
	struct dirent entry;
	struct dirent *result;
	char *filename = NULL;
	int ret, err;

	dir = opendir(path);
	if (dir == NULL) {
		if (errno == ENOENT) {
			ret = 0;
			goto exit;
		} else {
			ret = -1;
			goto exit;
		}
	}

	while ((ret = readdir_r(dir, &entry, &result)) == 0) {
		if (result == NULL) {
			break;
		}

		if (!strcmp(entry.d_name, ".") ||
		    !strcmp(entry.d_name, "..")) {
			continue;
		}

		free(filename);
		string_or_die(&filename, "%s/%s", path, entry.d_name);

		err = swupd_rm(filename);
		if (err) {
			ret = -1;
			goto exit;
		}
	}

	/* Delete directory once it's empty */
	err = rmdir(path);
	if (err) {
		if (errno == ENOENT) {
		} else {
			ret = -1;
			goto exit;
		}
	}

exit:
	closedir(dir);
	free(filename);
	return ret;
}
コード例 #4
0
ファイル: staging.c プロジェクト: rojkov/swupd-client
/* Consider adding a remove_leftovers() that runs in verify/fix in order to
 * allow this function to mkdtemp create folders for parallel build */
int do_staging(struct file *file)
{
	char *statfile = NULL, *tmp = NULL, *tmp2 = NULL;
	char *dir, *base, *rel_dir;
	char *tarcommand = NULL;
	char *original = NULL;
	char *target = NULL;
	char *targetpath = NULL;
	char *symbase = NULL;
	char *rename_target = NULL;
	char *rename_tmpdir = NULL;
	int ret;
	struct stat s;

	tmp = strdup(file->filename);
	tmp2 = strdup(file->filename);

	dir = dirname(tmp);
	base = basename(tmp2);

	rel_dir = dir;
	if (*dir == '/') {
		rel_dir = dir + 1;
	}

	string_or_die(&original, "%s/staged/%s", STATE_DIR, file->hash);

	string_or_die(&targetpath, "%s%s", path_prefix, rel_dir);
	ret = stat(targetpath, &s);

	if (S_ISLNK(s.st_mode)) {
		/* Follow symlink to ultimate target and redo stat */
		symbase = realpath(targetpath, NULL);
		if (symbase != NULL) {
			free(targetpath);
			targetpath = strdup(symbase);
			ret = stat(targetpath, &s);
			free(symbase);
		}
	}

	/* For now, just report on error conditions. Once we implement
	* verify_fix_path(char *path, int targetversion), we'll want to call it here */
	if ((ret == -1) && (errno == ENOENT)) {
		printf("Error: Update target directory does not exist: %s\n", targetpath);
	} else if (!S_ISDIR(s.st_mode)) {
		printf("Error: Update target exists but is NOT a directory: %s\n", targetpath);
	}

	free(targetpath);
	string_or_die(&target, "%s%s/.update.%s", path_prefix, rel_dir, base);
	ret = swupd_rm(target);
	if (ret < 0 && ret != -ENOENT) {
		printf("Error: Failed to remove %s\n", target);
	}

	string_or_die(&statfile, "%s%s", path_prefix, file->filename);

	memset(&s, 0, sizeof(struct stat));
	ret = lstat(statfile, &s);
	if (ret == 0) {
		if ((file->is_dir  && !S_ISDIR(s.st_mode)) ||
		    (file->is_link && !S_ISLNK(s.st_mode)) ||
		    (file->is_file && !S_ISREG(s.st_mode))) {
			//file type changed, move old out of the way for new
			ret = swupd_rm(statfile);
			if (ret < 0) {
				ret = -ETYPE_CHANGED_FILE_RM;
				goto out;
			}
		}
	}
	free(statfile);

	if (file->is_dir || S_ISDIR(s.st_mode)) {
		/* In the btrfs only scenario there was an implicit
		 * "create_or_update_dir()" via un-tar-ing a directory.tar after
		 * download and the untar happens in the staging subvolume which
		 * then gets promoted to a "real" usable subvolume.  But for
		 * a live rootfs the directory needs copied out of staged
		 * and into the rootfs.  Tar is a way to copy with
		 * attributes and it includes internal logic that does the
		 * right thing to overlay a directory onto something
		 * pre-existing: */
		/* In order to avoid tar transforms with directories, rename
		 * the directory before and after the tar command */
		string_or_die(&rename_tmpdir, "%s/tmprenamedir", STATE_DIR);
		ret = create_staging_renamedir(rename_tmpdir);
		if (ret) {
			goto out;
		}
		string_or_die(&rename_target, "%s/%s", rename_tmpdir, base);
		if (rename(original, rename_target)) {
			ret = -errno;
			goto out;
		}
		string_or_die(&tarcommand, "tar -C %s " TAR_PERM_ATTR_ARGS " -cf - '%s' 2> /dev/null | "
			"tar -C %s%s " TAR_PERM_ATTR_ARGS " -xf - 2> /dev/null",
			rename_tmpdir, base, path_prefix, rel_dir);
		ret = system(tarcommand);
		if (WIFEXITED(ret)) {
			ret = WEXITSTATUS(ret);
		}
		free(tarcommand);
		if (rename(rename_target, original)) {
			ret = -errno;
			goto out;
		}
		if (ret < 0) {
			ret = -EDIR_OVERWRITE;
			goto out;
		}
	} else { /* (!file->is_dir && !S_ISDIR(stat.st_mode)) */
		/* can't naively hard link(): Non-read-only files with same hash must remain
		 * separate copies otherwise modifications to one instance of the file
		 * propagate to all instances of the file perhaps causing subtle data corruption from
		 * a user's perspective.  In practice the rootfs is stateless and owned by us.
		 * Additionally cross-mount hardlinks fail and it's hard to know what an admin
		 * might have for overlaid mounts.  The use of tar is a simple way to copy, but
		 * inefficient.  So prefer hardlink and fall back if needed: */
		ret = -1;
		if (!file->is_config && !file->is_state && !file->use_xattrs) {
			ret = link(original, target);
		}
		if (ret < 0) {
			/* either the hardlink failed, or it was undesirable (config), do a tar-tar dance */
			/* In order to avoid tar transforms, rename the file
			 * before and after the tar command */
			string_or_die(&rename_target, "%s/staged/.update.%s", STATE_DIR, base);
			ret = rename(original, rename_target);
			if (ret) {
				ret = -errno;
				goto out;
			}
			string_or_die(&tarcommand, "tar -C %s/staged " TAR_PERM_ATTR_ARGS " -cf - '.update.%s' 2> /dev/null | "
				"tar -C %s%s " TAR_PERM_ATTR_ARGS " -xf - 2> /dev/null",
				STATE_DIR, base, path_prefix, rel_dir);
			ret = system(tarcommand);
			if (WIFEXITED(ret)) {
				ret = WEXITSTATUS(ret);
			}
			free(tarcommand);
			ret = rename(rename_target, original);
			if (ret) {
				ret = -errno;
				goto out;
			}
		}

		struct stat buf;
		int err;

		if (file->staging) {
			/* this must never happen...full file never finished download */
			free(file->staging);
			file->staging = NULL;
		}

		string_or_die(&file->staging, "%s%s/.update.%s", path_prefix, rel_dir, base);

		err = lstat(file->staging, &buf);
		if (err != 0) {
			free(file->staging);
			file->staging = NULL;
			ret = -EDOTFILE_WRITE;
			goto out;
		}
	}

out:
	free(target);
	free(original);
	free(rename_target);
	free(rename_tmpdir);
	free(tmp);
	free(tmp2);

	return ret;
}
コード例 #5
0
ファイル: staging.c プロジェクト: rojkov/swupd-client
/* caller should not call this function for do_not_update marked files */
int rename_staged_file_to_final(struct file *file) {
	int ret;
	char *target;

	string_or_die(&target, "%s%s", path_prefix, file->filename);

	if (!file->staging && !file->is_deleted && !file->is_dir) {
		free(target);
		return -1;
	}

	if (file->is_deleted) {
		ret = swupd_rm(target);

		/* don't count missing ones as errors...
		 * if somebody already deleted them for us then all is well */
		if ((ret == -ENOENT) || (ret == -ENOTDIR)) {
			ret = 0;
		}
	} else if (file->is_dir) {
		ret = 0;
	} else {
		struct stat stat;
		ret = lstat(target, &stat);

		/* If the file was previously a directory but no longer, then
		 * we need to move it out of the way.
		 * This should not happen because the server side complains
		 * when creating update content that includes such a state
		 * change.  But...you never know. */

		if ((ret == 0) && (S_ISDIR(stat.st_mode))) {
			char *lostnfound;
			char *base;

			string_or_die(&lostnfound, "%slost+found", path_prefix);
			ret = mkdir(lostnfound, S_IRWXU);
			if ((ret != 0) && (errno != EEXIST)) {
				free(lostnfound);
				free(target);
				return ret;
			}
			free(lostnfound);

			base = basename(file->filename);
			string_or_die(&lostnfound, "%slost+found/%s", path_prefix, base);
			/* this will fail if the directory was not already emptied */
			ret = rename(target, lostnfound);
			if (ret < 0 && errno != ENOTEMPTY && errno != EEXIST) {
				printf("Error: failed to move %s to lost+found: %s\n",
					base, strerror(errno));
			}
			free(lostnfound);
		} else {
			ret = rename(file->staging, target);
			if (ret < 0) {
				printf("Error: failed to rename staged %s to final: %s\n",
					file->hash, strerror(errno));
			}
			unlink(file->staging);
		}
	}

	free(target);
	return ret;
}
コード例 #6
0
ファイル: staging.c プロジェクト: clearlinux/swupd-client
/* Consider adding a remove_leftovers() that runs in verify/fix in order to
 * allow this function to mkdtemp create folders for parallel build */
enum swupd_code do_staging(struct file *file, struct manifest *MoM)
{
	char *statfile = NULL, *tmp = NULL, *tmp2 = NULL;
	char *dir, *base, *rel_dir;
	char *tarcommand = NULL;
	char *original = NULL;
	char *target = NULL;
	char *targetpath = NULL;
	char *rename_target = NULL;
	char *rename_tmpdir = NULL;
	char real_path[4096] = { 0 };
	int ret;
	struct stat s;

	tmp = strdup_or_die(file->filename);
	tmp2 = strdup_or_die(file->filename);

	dir = dirname(tmp);
	base = basename(tmp2);

	rel_dir = dir;
	if (*dir == '/') {
		rel_dir = dir + 1;
	}

	string_or_die(&original, "%s/staged/%s", state_dir, file->hash);

	string_or_die(&targetpath, "%s%s", path_prefix, rel_dir);
	ret = stat(targetpath, &s);

	if ((ret == -1) && (errno == ENOENT)) {
		if (MoM) {
			warn("Update target directory does not exist: %s. Trying to fix it\n", targetpath);
			verify_fix_path(dir, MoM);
		} else {
			warn("Update target directory does not exist: %s. Auto-fix disabled\n", targetpath);
		}

	} else if (!S_ISDIR(s.st_mode)) {
		error("Update target exists but is NOT a directory: %s\n", targetpath);
	}
	if (!realpath(targetpath, real_path)) {
		ret = -1;
		goto out;
	} else if (strcmp(path_prefix, targetpath) != 0 &&
		   strcmp(targetpath, real_path) != 0) {
		/*
		 * targetpath and real_path should always be equal but
		 * in the case of the targetpath being the path_prefix
		 * there is a trailing '/' in path_prefix but realpath
		 * doesn't keep the trailing '/' so check for that case
		 * specifically.
		 */
		ret = -1;
		goto out;
	}

	string_or_die(&target, "%s%s/.update.%s", path_prefix, rel_dir, base);
	ret = swupd_rm(target);
	if (ret < 0 && ret != -ENOENT) {
		error("Failed to remove %s\n", target);
	}

	string_or_die(&statfile, "%s%s", path_prefix, file->filename);

	memset(&s, 0, sizeof(struct stat));
	ret = lstat(statfile, &s);
	if (ret == 0) {
		if ((file->is_dir && !S_ISDIR(s.st_mode)) ||
		    (file->is_link && !S_ISLNK(s.st_mode)) ||
		    (file->is_file && !S_ISREG(s.st_mode))) {
			//file type changed, move old out of the way for new
			ret = swupd_rm(statfile);
			if (ret < 0) {
				ret = SWUPD_COULDNT_REMOVE_FILE;
				goto out;
			}
		}
	}
	free_string(&statfile);

	if (file->is_dir || S_ISDIR(s.st_mode)) {
		/* In the btrfs only scenario there was an implicit
		 * "create_or_update_dir()" via un-tar-ing a directory.tar after
		 * download and the untar happens in the staging subvolume which
		 * then gets promoted to a "real" usable subvolume.  But for
		 * a live rootfs the directory needs copied out of staged
		 * and into the rootfs.  Tar is a way to copy with
		 * attributes and it includes internal logic that does the
		 * right thing to overlay a directory onto something
		 * pre-existing: */
		/* In order to avoid tar transforms with directories, rename
		 * the directory before and after the tar command */
		string_or_die(&rename_tmpdir, "%s/tmprenamedir", state_dir);
		ret = create_staging_renamedir(rename_tmpdir);
		if (ret) {
			ret = SWUPD_COULDNT_CREATE_DIR;
			goto out;
		}
		string_or_die(&rename_target, "%s/%s", rename_tmpdir, base);
		if (rename(original, rename_target)) {
			ret = SWUPD_COULDNT_RENAME_DIR;
			goto out;
		}
		string_or_die(&tarcommand, TAR_COMMAND " -C '%s' " TAR_PERM_ATTR_ARGS " -cf - './%s' 2> /dev/null | " TAR_COMMAND " -C '%s%s' " TAR_PERM_ATTR_ARGS " -xf - 2> /dev/null",
			      rename_tmpdir, base, path_prefix, rel_dir);
		ret = system(tarcommand);
		if (ret == -1) {
			ret = SWUPD_SUBPROCESS_ERROR;
		}
		if (WIFEXITED(ret)) {
			ret = WEXITSTATUS(ret);
		}
		free_string(&tarcommand);
		if (rename(rename_target, original)) {
			ret = SWUPD_COULDNT_RENAME_DIR;
			goto out;
		}
		if (ret) {
			ret = SWUPD_COULDNT_RENAME_DIR;
			goto out;
		}
	} else { /* (!file->is_dir && !S_ISDIR(stat.st_mode)) */
		/* can't naively hard link(): Non-read-only files with same hash must remain
		 * separate copies otherwise modifications to one instance of the file
		 * propagate to all instances of the file perhaps causing subtle data corruption from
		 * a user's perspective.  In practice the rootfs is stateless and owned by us.
		 * Additionally cross-mount hardlinks fail and it's hard to know what an admin
		 * might have for overlaid mounts.  The use of tar is a simple way to copy, but
		 * inefficient.  So prefer hardlink and fall back if needed: */
		ret = -1;
		if (!file->is_config && !file->is_state && !file->use_xattrs) {
			ret = link(original, target);
		}
		if (ret < 0) {
			/* either the hardlink failed, or it was undesirable (config), do a tar-tar dance */
			/* In order to avoid tar transforms, rename the file
			 * before and after the tar command */
			string_or_die(&rename_target, "%s/staged/.update.%s", state_dir, base);
			ret = rename(original, rename_target);
			if (ret) {
				ret = SWUPD_COULDNT_RENAME_FILE;
				goto out;
			}
			string_or_die(&tarcommand, TAR_COMMAND " -C '%s/staged' " TAR_PERM_ATTR_ARGS " -cf - '.update.%s' 2> /dev/null | " TAR_COMMAND " -C '%s%s' " TAR_PERM_ATTR_ARGS " -xf - 2> /dev/null",
				      state_dir, base, path_prefix, rel_dir);
			ret = system(tarcommand);
			if (ret == -1) {
				ret = SWUPD_SUBPROCESS_ERROR;
			}
			if (WIFEXITED(ret)) {
				ret = WEXITSTATUS(ret);
			}
			free_string(&tarcommand);
			ret = rename(rename_target, original);
			if (ret) {
				ret = SWUPD_COULDNT_RENAME_FILE;
				goto out;
			}
		}

		struct stat buf;
		int err;

		free_string(&file->staging);
		string_or_die(&file->staging, "%s%s/.update.%s", path_prefix, rel_dir, base);

		err = lstat(file->staging, &buf);
		if (err != 0) {
			free_string(&file->staging);
			ret = SWUPD_COULDNT_CREATE_FILE;
			goto out;
		}
	}

out:
	free_string(&target);
	free_string(&targetpath);
	free_string(&original);
	free_string(&rename_target);
	free_string(&rename_tmpdir);
	free_string(&tmp);
	free_string(&tmp2);

	return ret;
}