mirror of
https://github.com/clearlinux/swupd-client.git
synced 2026-09-08 06:31:50 +00:00
The following command was run with clang 3.7.1: $ clang-format -i -style=file include/*.h src/*.c test/*.c Signed-off-by: Patrick McCarty <patrick.mccarty@intel.com>
665 lines
12 KiB
C
665 lines
12 KiB
C
/*
|
|
* Software Updater - client side
|
|
*
|
|
* Copyright © 2012-2016 Intel Corporation.
|
|
*
|
|
* This program is free software: you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License as published by
|
|
* the Free Software Foundation, version 2 or later of the License.
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
*
|
|
* Authors:
|
|
* Arjan van de Ven <arjan@linux.intel.com>
|
|
* Tim Pepper <timothy.c.pepper@linux.intel.com>
|
|
*
|
|
*/
|
|
|
|
#define _GNU_SOURCE
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <unistd.h>
|
|
#include <string.h>
|
|
#include <sys/types.h>
|
|
#include <sys/stat.h>
|
|
#include <fcntl.h>
|
|
#include <sys/mount.h>
|
|
#include <sys/statvfs.h>
|
|
#include <dirent.h>
|
|
#include <errno.h>
|
|
#include <stdarg.h>
|
|
|
|
#include "config.h"
|
|
#include "swupd.h"
|
|
|
|
void check_root(void)
|
|
{
|
|
if (getuid() != 0) {
|
|
printf("This program must be run as root..aborting.\n\n");
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
}
|
|
|
|
/* Remove the contents of a staging directory (eg: /mnt/swupd/update/780 or
|
|
* /mnt/swupd/update/delta) which are not supposed to contain
|
|
* subdirectories containing files, ie: no need for true recursive removal.
|
|
* Just the relative path (et: "780" or "delta" is passed as a parameter).
|
|
*
|
|
* return: 0 on success, non-zero on error
|
|
*/
|
|
int rm_staging_dir_contents(const char *rel_path)
|
|
{
|
|
DIR *dir;
|
|
struct dirent entry;
|
|
struct dirent *result;
|
|
char *filename;
|
|
char *abs_path;
|
|
int ret;
|
|
|
|
string_or_die(&abs_path, "%s/%s", STATE_DIR, rel_path);
|
|
|
|
dir = opendir(abs_path);
|
|
if (dir == NULL) {
|
|
free(abs_path);
|
|
return -1;
|
|
}
|
|
|
|
while ((ret = readdir_r(dir, &entry, &result)) == 0) {
|
|
if (result == NULL) {
|
|
break;
|
|
}
|
|
|
|
if (!strcmp(entry.d_name, ".") ||
|
|
!strcmp(entry.d_name, "..")) {
|
|
continue;
|
|
}
|
|
|
|
string_or_die(&filename, "%s/%s", abs_path, entry.d_name);
|
|
|
|
ret = remove(filename);
|
|
if (ret != 0) {
|
|
free(filename);
|
|
break;
|
|
}
|
|
free(filename);
|
|
}
|
|
|
|
free(abs_path);
|
|
closedir(dir);
|
|
|
|
sync();
|
|
return ret;
|
|
}
|
|
|
|
void unlink_all_staged_content(struct file *file)
|
|
{
|
|
char *filename;
|
|
|
|
/* downloaded tar file */
|
|
string_or_die(&filename, "%s/download/%s.tar", STATE_DIR, file->hash);
|
|
unlink(filename);
|
|
free(filename);
|
|
string_or_die(&filename, "%s/download/.%s.tar", STATE_DIR, file->hash);
|
|
unlink(filename);
|
|
free(filename);
|
|
|
|
/* downloaded and un-tar'd file */
|
|
string_or_die(&filename, "%s/staged/%s", STATE_DIR, file->hash);
|
|
if (file->is_dir) {
|
|
rmdir(filename);
|
|
} else {
|
|
unlink(filename);
|
|
}
|
|
free(filename);
|
|
|
|
/* delta file */
|
|
if (file->peer) {
|
|
string_or_die(&filename, "%s/delta/%i-%i-%s", STATE_DIR,
|
|
file->peer->last_change, file->last_change, file->hash);
|
|
unlink(filename);
|
|
free(filename);
|
|
}
|
|
}
|
|
|
|
FILE *fopen_exclusive(const char *filename) /* no mode, opens for write only */
|
|
{
|
|
int fd;
|
|
|
|
fd = open(filename, O_CREAT | O_EXCL | O_RDWR, 00600);
|
|
if (fd < 0) {
|
|
return NULL;
|
|
}
|
|
return fdopen(fd, "w");
|
|
}
|
|
|
|
int create_required_dirs(void)
|
|
{
|
|
int ret = 0;
|
|
int i;
|
|
char *dir;
|
|
#define STATE_DIR_COUNT 3
|
|
const char *dirs[] = { "delta", "staged", "download" };
|
|
struct stat buf;
|
|
bool missing = false;
|
|
|
|
// check for existance
|
|
ret = stat(STATE_DIR, &buf);
|
|
if (ret && (errno == ENOENT)) {
|
|
missing = true;
|
|
}
|
|
for (i = 0; i < STATE_DIR_COUNT; i++) {
|
|
string_or_die(&dir, "%s/%s", STATE_DIR, dirs[i]);
|
|
ret = stat(dir, &buf);
|
|
if (ret) {
|
|
missing = true;
|
|
}
|
|
free(dir);
|
|
}
|
|
|
|
if (missing) { // (re)create dirs
|
|
char *cmd;
|
|
|
|
// laziness here for want of a simple "mkdir -p"
|
|
string_or_die(&cmd, "mkdir -p %s/{delta,staged,download}", STATE_DIR);
|
|
ret = system(cmd);
|
|
if (ret) {
|
|
return -1;
|
|
}
|
|
free(cmd);
|
|
|
|
// chmod 700
|
|
ret = chmod(STATE_DIR, S_IRWXU);
|
|
if (ret) {
|
|
return -1;
|
|
}
|
|
for (i = 0; i < STATE_DIR_COUNT; i++) {
|
|
string_or_die(&dir, "%s/%s", STATE_DIR, dirs[i]);
|
|
ret = chmod(dir, S_IRWXU);
|
|
if (ret) {
|
|
return -1;
|
|
}
|
|
free(dir);
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* store a colon separated list of current mountpoint into
|
|
* variable mounted_dirs, this function do not return a value.
|
|
*
|
|
* e.g: :/proc:/mnt/acct:
|
|
*/
|
|
void get_mounted_directories(void)
|
|
{
|
|
FILE *file;
|
|
char *line = NULL;
|
|
char *mnt;
|
|
char *tmp;
|
|
ssize_t ret;
|
|
char *c;
|
|
size_t n;
|
|
|
|
file = fopen("/proc/self/mountinfo", "r");
|
|
if (!file) {
|
|
return;
|
|
}
|
|
|
|
while (!feof(file)) {
|
|
ret = getline(&line, &n, file);
|
|
if ((ret < 0) || (line == NULL)) {
|
|
break;
|
|
}
|
|
|
|
c = strchr(line, '\n');
|
|
if (c) {
|
|
*c = 0;
|
|
}
|
|
|
|
n = 0;
|
|
mnt = strtok(line, " ");
|
|
while (mnt != NULL) {
|
|
if (n == 4) {
|
|
/* The "4" assumes today's mountinfo form of:
|
|
* 16 36 0:3 / /proc rw,relatime master:7 - proc proc rw
|
|
* where the fifth field is the mountpoint. */
|
|
if (strcmp(mnt, "/") == 0) {
|
|
break;
|
|
}
|
|
|
|
if (mounted_dirs == NULL) {
|
|
string_or_die(&mounted_dirs, "%s", ":");
|
|
}
|
|
tmp = mounted_dirs;
|
|
string_or_die(&mounted_dirs, "%s%s:", tmp, mnt);
|
|
free(tmp);
|
|
break;
|
|
}
|
|
n++;
|
|
mnt = strtok(NULL, " ");
|
|
}
|
|
free(line);
|
|
line = NULL;
|
|
}
|
|
free(line);
|
|
fclose(file);
|
|
}
|
|
|
|
// prepends prefix to an path (eg: the global path_prefix to a
|
|
// file->filename or some other path prefix and path), insuring there
|
|
// is no duplicate '/' at the strings' junction and no trailing '/'
|
|
char *mk_full_filename(const char *prefix, const char *path)
|
|
{
|
|
char *fname = NULL;
|
|
char *abspath;
|
|
|
|
if (path[0] == '/') {
|
|
abspath = strdup(path);
|
|
} else {
|
|
string_or_die(&abspath, "/%s", path);
|
|
}
|
|
if (abspath == NULL) {
|
|
abort();
|
|
}
|
|
|
|
// The prefix is a minimum of "/" or "". If the prefix is only that,
|
|
// just use abspath. If the prefix is longer than the minimal, insure
|
|
// it ends in not "/" and append abspath.
|
|
if ((strcmp(prefix, "/") == 0) ||
|
|
(strcmp(prefix, "") == 0)) {
|
|
// rootfs, use absolute path
|
|
fname = strdup(abspath);
|
|
if (fname == NULL) {
|
|
abort();
|
|
}
|
|
} else if (strcmp(&prefix[strlen(prefix) - 1], "/") == 0) {
|
|
// chroot and need to strip trailing "/" from prefix
|
|
char *tmp = strdup(prefix);
|
|
if (tmp == NULL) {
|
|
abort();
|
|
}
|
|
tmp[strlen(tmp) - 1] = '\0';
|
|
|
|
string_or_die(&fname, "%s%s", tmp, abspath);
|
|
free(tmp);
|
|
} else {
|
|
// chroot and no need to strip trailing "/" from prefix
|
|
string_or_die(&fname, "%s%s", prefix, abspath);
|
|
}
|
|
free(abspath);
|
|
return fname;
|
|
}
|
|
|
|
// expects filename w/o path_prefix prepended
|
|
bool is_directory_mounted(const char *filename)
|
|
{
|
|
char *fname;
|
|
bool ret = false;
|
|
char *tmp;
|
|
|
|
if (mounted_dirs == NULL) {
|
|
return false;
|
|
}
|
|
|
|
tmp = mk_full_filename(path_prefix, filename);
|
|
string_or_die(&fname, ":%s:", tmp);
|
|
free(tmp);
|
|
|
|
if (strstr(mounted_dirs, fname)) {
|
|
ret = true;
|
|
}
|
|
|
|
free(fname);
|
|
|
|
return ret;
|
|
}
|
|
|
|
// expects filename w/o path_prefix prepended
|
|
bool is_under_mounted_directory(const char *filename)
|
|
{
|
|
bool ret = false;
|
|
int err;
|
|
char *token;
|
|
char *mountpoint;
|
|
char *dir;
|
|
char *fname;
|
|
char *tmp;
|
|
|
|
if (mounted_dirs == NULL) {
|
|
return false;
|
|
}
|
|
|
|
dir = strdup(mounted_dirs);
|
|
if (dir == NULL) {
|
|
abort();
|
|
}
|
|
|
|
token = strtok(dir + 1, ":");
|
|
while (token != NULL) {
|
|
string_or_die(&mountpoint, "%s/", token);
|
|
|
|
tmp = mk_full_filename(path_prefix, filename);
|
|
string_or_die(&fname, ":%s:", tmp);
|
|
free(tmp);
|
|
|
|
err = strncmp(fname, mountpoint, strlen(mountpoint));
|
|
free(fname);
|
|
if (err == 0) {
|
|
free(mountpoint);
|
|
ret = true;
|
|
break;
|
|
}
|
|
|
|
token = strtok(NULL, ":");
|
|
|
|
free(mountpoint);
|
|
}
|
|
|
|
free(dir);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int swupd_rm_file(const char *path)
|
|
{
|
|
int err = unlink(path);
|
|
if (err) {
|
|
if (errno == ENOENT) {
|
|
return 0;
|
|
} else {
|
|
return -1;
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
int swupd_rm(const char *filename)
|
|
{
|
|
struct stat stat;
|
|
int ret;
|
|
|
|
ret = lstat(filename, &stat);
|
|
if (ret) {
|
|
if (errno == ENOENT) {
|
|
// Quiet, no real failure here
|
|
return -ENOENT;
|
|
} else {
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
if (S_ISDIR(stat.st_mode)) {
|
|
ret = swupd_rm_dir(filename);
|
|
} else {
|
|
ret = swupd_rm_file(filename);
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
int rm_bundle_file(const char *bundle)
|
|
{
|
|
char *filename = NULL;
|
|
int ret = 0;
|
|
struct stat statb;
|
|
|
|
string_or_die(&filename, "%s/%s/%s", path_prefix, BUNDLES_DIR, bundle);
|
|
|
|
if (stat(filename, &statb) == -1) {
|
|
goto out;
|
|
}
|
|
|
|
if (S_ISREG(statb.st_mode)) {
|
|
if (unlink(filename) != 0) {
|
|
ret = -1;
|
|
goto out;
|
|
}
|
|
} else {
|
|
ret = -1;
|
|
goto out;
|
|
}
|
|
|
|
out:
|
|
free(filename);
|
|
return ret;
|
|
}
|
|
|
|
#if 0
|
|
void dump_file_info(struct file *file)
|
|
{
|
|
printf("%s:\n", file->filename);
|
|
printf("\t%s\n", file->hash);
|
|
printf("\t%d\n", file->last_change);
|
|
|
|
if (file->use_xattrs) {
|
|
printf("\tuse_xattrs\n");
|
|
}
|
|
if (file->is_dir) {
|
|
printf("\tis_dir\n");
|
|
}
|
|
if (file->is_file) {
|
|
printf("\tis_file\n");
|
|
}
|
|
if (file->is_link) {
|
|
printf("\tis_link\n");
|
|
}
|
|
if (file->is_deleted) {
|
|
printf("\tis_deleted\n");
|
|
}
|
|
if (file->is_manifest) {
|
|
printf("\tis_manifest\n");
|
|
}
|
|
if (file->is_config) {
|
|
printf("\tis_config\n");
|
|
}
|
|
if (file->is_state) {
|
|
printf("\tis_state\n");
|
|
}
|
|
if (file->is_boot) {
|
|
printf("\tis_boot\n");
|
|
}
|
|
if (file->is_rename) {
|
|
printf("\tis_rename\n");
|
|
}
|
|
if (file->is_orphan) {
|
|
printf("\tis_orphan\n");
|
|
}
|
|
if (file->do_not_update) {
|
|
printf("\tdo_not_update\n");
|
|
}
|
|
|
|
if (file->peer) {
|
|
printf("\tpeer %s(%s)\n", file->peer->filename, file->peer->hash);
|
|
}
|
|
if (file->deltapeer) {
|
|
printf("\tdeltapeer %s(%s)\n", file->deltapeer->filename, file->deltapeer->hash);
|
|
}
|
|
if (file->staging) {
|
|
printf("\tstaging %s\n", file->staging);
|
|
}
|
|
}
|
|
#endif
|
|
|
|
void free_file_data(void *data)
|
|
{
|
|
struct file *file = (struct file *)data;
|
|
|
|
if (!file) {
|
|
return;
|
|
}
|
|
|
|
/* peer and deltapeer are pointers to files contained
|
|
* in another list and must not be disposed */
|
|
|
|
if (file->filename) {
|
|
free(file->filename);
|
|
}
|
|
|
|
if (file->header) {
|
|
free(file->header);
|
|
}
|
|
|
|
if (file->staging) {
|
|
free(file->staging);
|
|
}
|
|
|
|
free(file);
|
|
}
|
|
|
|
/* this function is intended to encapsulate the basic swupd
|
|
* initializations for the majority of commands, that is:
|
|
* - Make sure root is the user running the code
|
|
* - Initialize log facility
|
|
* - Get the lock
|
|
* - initialize mounted directories
|
|
* - Initialize curl
|
|
*/
|
|
int swupd_init(int *lock_fd)
|
|
{
|
|
int ret = 0;
|
|
|
|
check_root();
|
|
*lock_fd = p_lockfile();
|
|
if (*lock_fd < 0) {
|
|
ret = ELOCK_FILE;
|
|
goto out_fds;
|
|
}
|
|
|
|
get_mounted_directories();
|
|
|
|
if (swupd_curl_init() != 0) {
|
|
ret = ECURL_INIT;
|
|
goto out_close_lock;
|
|
}
|
|
|
|
return ret;
|
|
|
|
out_close_lock:
|
|
v_lockfile(*lock_fd);
|
|
out_fds:
|
|
dump_file_descriptor_leaks();
|
|
|
|
return ret;
|
|
}
|
|
|
|
/* this function prints the initial message for all utils
|
|
*/
|
|
void copyright_header(const char *name)
|
|
{
|
|
printf(PACKAGE " %s " VERSION "\n", name);
|
|
printf(" Copyright (C) 2012-2016 Intel Corporation\n");
|
|
printf("\n");
|
|
}
|
|
|
|
void string_or_die(char **strp, const char *fmt, ...)
|
|
{
|
|
va_list ap;
|
|
|
|
va_start(ap, fmt);
|
|
if (vasprintf(strp, fmt, ap) <= 0) {
|
|
abort();
|
|
}
|
|
va_end(ap);
|
|
}
|
|
|
|
void update_motd(int new_release)
|
|
{
|
|
FILE *motd_fp = NULL;
|
|
|
|
motd_fp = fopen(MOTD_FILE, "w");
|
|
|
|
if (motd_fp != NULL) {
|
|
fprintf(motd_fp, "There is a new OS version available: %d\n", new_release);
|
|
fprintf(motd_fp, "Upgrade to the latest version using 'swupd update'\n");
|
|
fclose(motd_fp);
|
|
}
|
|
}
|
|
|
|
void delete_motd(void)
|
|
{
|
|
if (unlink(MOTD_FILE) == ENOMEM) {
|
|
abort();
|
|
}
|
|
}
|
|
|
|
int is_dirname_link(const char *fullname)
|
|
{
|
|
int ret = -1;
|
|
char *real_path = NULL;
|
|
real_path = realpath(fullname, NULL);
|
|
if (!real_path) {
|
|
printf("Failed to get real path of %s\n", fullname);
|
|
return -1;
|
|
}
|
|
|
|
if (strcmp(real_path, fullname) != 0) {
|
|
ret = 1;
|
|
} else {
|
|
ret = 0;
|
|
}
|
|
|
|
free(real_path);
|
|
return ret;
|
|
}
|