void prepare_delta_dir(struct manifest *manifest) { char *path; char *conf; printf("Preparing delta directory \n"); conf = config_output_dir(); string_or_die(&path, "%s/%i", conf, manifest->version); if (mkdir(path, S_IRWXU | S_IRWXG | S_IROTH | S_IXOTH) != 0) { if (errno != EEXIST) { LOG(NULL, "Failed to create directory ", "%s", path); return; } } free(path); string_or_die(&path, "%s/%i/delta/", conf, manifest->version); if (mkdir(path, S_IRWXU | S_IRWXG | S_IROTH | S_IXOTH) != 0) { if (errno != EEXIST) { LOG(NULL, "Failed to create directory ", "%s", path); return; } } free(path); free(conf); }
static double query_total_download_size(struct list *list) { double ret; double size_sum = 0; struct file *file = NULL; char *untard_file = NULL; char *url = NULL; while (list) { file = list->data; list = list->next; string_or_die(&untard_file, "%s/%i/Manifest.%s", state_dir, file->last_change, file->filename); if (access(untard_file, F_OK) == -1) { /* Does not exist client-side. Must download */ string_or_die(&url, "%s/%i/Manifest.%s.tar", content_url, file->last_change, file->filename); ret = swupd_query_url_content_size(url); if (ret != -1) { /* Convert file size from bytes to MB */ ret = ret / 1000000; size_sum += ret; } else { return ret; } } } free(untard_file); return size_sum; }
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); } }
/* Returns 0 == success, -1 == failure */ static int write_manifest_tar(struct manifest *manifest) { char *conf = config_output_dir(); char *tarcmd = NULL; int ret = -1; if (conf == NULL) { assert(0); } /* now, tar the thing up for efficient full file download */ /* and put the signature of the plain manifest into the archive, too */ if (enable_signing) { string_or_die(&tarcmd, TAR_COMMAND " --directory=%s/%i " TAR_PERM_ATTR_ARGS " -Jcf " "%s/%i/Manifest.%s.tar Manifest.%s Manifest.%s.signed", conf, manifest->version, conf, manifest->version, manifest->component, manifest->component, manifest->component); } else { string_or_die(&tarcmd, TAR_COMMAND " --directory=%s/%i " TAR_PERM_ATTR_ARGS " -Jcf " "%s/%i/Manifest.%s.tar Manifest.%s", conf, manifest->version, conf, manifest->version, manifest->component, manifest->component); } if (system(tarcmd) != 0) { fprintf(stderr, "Creation of Manifest.tar failed\n"); goto exit; } ret = 0; exit: free(tarcmd); free(conf); return ret; }
// 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; size_t len = 0; if (!path) { return NULL; } if (prefix) { len = strlen(prefix); } //Remove trailing '/' at the end of prefix while (len && prefix[len - 1] == '/') { len--; } //make sure a '/' will always be added between prefix and path if (path[0] == '/') { string_or_die(&fname, "%.*s%s", len, prefix, path); } else { string_or_die(&fname, "%.*s/%s", len, prefix, path); } return fname; }
static int download_pack(int oldversion, int newversion, char *module) { FILE *tarfile = NULL; char *tar = NULL; char *url = NULL; int err = -1; char *filename; struct stat stat; string_or_die(&filename, "%s/pack-%s-from-%i-to-%i.tar", STATE_DIR, module, oldversion, newversion); err = lstat(filename, &stat); if (err == 0 && stat.st_size == 0) { free(filename); return 0; } printf("Downloading %s pack for version %i\n", module, newversion); string_or_die(&url, "%s/%i/pack-%s-from-%i.tar", preferred_content_url, newversion, module, oldversion); err = swupd_curl_get_file(url, filename, NULL, NULL, true); if (err) { free(url); if ((lstat(filename, &stat) == 0) && (stat.st_size == 0)) { unlink(filename); } free(filename); return err; } if (!signature_download_and_verify(url, filename)) { free(url); unlink(filename); free(filename); return -1; } free(url); printf("Extracting pack.\n"); string_or_die(&tar, "tar -C %s " TAR_PERM_ATTR_ARGS " -xf %s/pack-%s-from-%i-to-%i.tar 2> /dev/null", STATE_DIR, STATE_DIR, module, oldversion, newversion); err = system(tar); if (WIFEXITED(err)) { err = WEXITSTATUS(err); } free(tar); unlink(filename); /* make a zero sized file to prevent redownload */ tarfile = fopen(filename, "w"); free(filename); if (tarfile) { fclose(tarfile); } return err; }
static int download_pack(int oldversion, int newversion, char *module) { FILE *tarfile = NULL; char *tar = NULL; char *url = NULL; int err = -1; char *filename; struct stat stat; string_or_die(&filename, "%s/pack-%s-from-%i-to-%i.tar", state_dir, module, oldversion, newversion); err = lstat(filename, &stat); if (err == 0 && stat.st_size == 0) { free(filename); return 0; } printf("Downloading %s pack for version %i\n", module, newversion); string_or_die(&url, "%s/%i/pack-%s-from-%i.tar", content_url, newversion, module, oldversion); err = swupd_curl_get_file(url, filename, NULL, NULL, true); if (err) { free(url); if ((lstat(filename, &stat) == 0) && (stat.st_size == 0)) { unlink(filename); } free(filename); return err; } free(url); printf("Extracting pack.\n"); string_or_die(&tar, TAR_COMMAND " -C %s " TAR_PERM_ATTR_ARGS " -xf %s/pack-%s-from-%i-to-%i.tar 2> /dev/null", state_dir, state_dir, module, oldversion, newversion); err = system(tar); if (WIFEXITED(err)) { err = WEXITSTATUS(err); } free(tar); unlink(filename); /* make a zero sized file to prevent redownload */ tarfile = fopen(filename, "w"); free(filename); if (tarfile) { fclose(tarfile); } // Only negative return values should indicate errors if (err > 0) { return -err; } else { return err; } }
static void download_file(struct swupd_curl_parallel_handle *download_handle, struct file *file) { char *url, *filename; string_or_die(&filename, "%s/download/.%s.tar", state_dir, file->hash); string_or_die(&url, "%s/%i/files/%s.tar", content_url, file->last_change, file->hash); swupd_curl_parallel_download_enqueue(download_handle, url, filename, file->hash, file); free_string(&url); free_string(&filename); }
static 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; for (i = 0; i < STATE_DIR_COUNT; i++) { string_or_die(&cmd, "mkdir -p %s/%s", state_dir, dirs[i]); ret = system(cmd); if (ret) { printf("Error: failed to create %s/%s\n", state_dir, dirs[i]); return -1; } free(cmd); string_or_die(&dir, "%s/%s", state_dir, dirs[i]); ret = chmod(dir, S_IRWXU); if (ret) { printf("Error: failed to set mode for %s/%s\n", state_dir, dirs[i]); return -1; } free(dir); } // chmod 700 ret = chmod(state_dir, S_IRWXU); if (ret) { printf("Error: failed to set mode for state dir (%s)\n", state_dir); return -1; } } 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: */ static 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); }
int get_current_version(char *path_prefix) { char line[LINE_MAX]; FILE *file; int v = -1; int err; char *buildstamp; char *src, *dest; string_or_die(&buildstamp, "%s/usr/lib/os-release", path_prefix); file = fopen(buildstamp, "rm"); if (!file) { free_string(&buildstamp); string_or_die(&buildstamp, "%s/etc/os-release", path_prefix); file = fopen(buildstamp, "rm"); if (!file) { free_string(&buildstamp); return v; } } while (!feof(file)) { line[0] = 0; if (fgets(line, LINE_MAX, file) == NULL) { break; } if (strncmp(line, "VERSION_ID=", 11) == 0) { src = &line[11]; /* Drop quotes and newline in value */ dest = src; while (*src) { if (*src == '\'' || *src == '"' || *src == '\n') { ++src; } else { *dest = *src; ++dest; ++src; } } *dest = 0; err = strtoi_err(&line[11], &v); if (err != 0) { v = -1; } break; } } free_string(&buildstamp); fclose(file); return v; }
/* try to insert the file into the hashmap download queue * returns 1 if no download is needed * returns 0 if download is needed * returns -1 if error */ static int swupd_curl_hashmap_insert(struct file *file) { struct list *iter; struct file *tmp; char *tar_dotfile; char *targetfile; struct stat stat; int hashmap_index = file->hash[0]; struct swupd_curl_hashbucket *bucket = &swupd_curl_hashmap[hashmap_index]; pthread_mutex_lock(&bucket->mutex); iter = bucket->list; while (iter) { tmp = iter->data; if (hash_equal(tmp->hash, file->hash)) { // hash already in download queue pthread_mutex_unlock(&bucket->mutex); return 1; } iter = iter->next; } // if valid target file is already here, no need to download string_or_die(&targetfile, "%s/staged/%s", state_dir, file->hash); if (lstat(targetfile, &stat) == 0) { if (verify_file(file, targetfile)) { free(targetfile); pthread_mutex_unlock(&bucket->mutex); return 1; } } free(targetfile); // hash not in queue and not present in staged // clean up in case any prior download failed in a partial state string_or_die(&tar_dotfile, "%s/download/.%s.tar", state_dir, file->hash); unlink(tar_dotfile); free(tar_dotfile); // queue the hash for download iter = bucket->list; if ((iter = list_prepend_data(iter, file)) == NULL) { pthread_mutex_unlock(&bucket->mutex); return -1; } bucket->list = iter; pthread_mutex_unlock(&bucket->mutex); return 0; }
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; }
/* 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; char *filename; char *abs_path; int ret = 0; string_or_die(&abs_path, "%s/%s", state_dir, rel_path); dir = opendir(abs_path); if (dir == NULL) { free_string(&abs_path); return -1; } while (true) { errno = 0; entry = readdir(dir); if (!entry) { /* readdir returns NULL on the end of a directory stream, we only * want to set ret if errno is also set, indicating a failure */ if (errno) { ret = errno; } 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_string(&filename); break; } free_string(&filename); } free_string(&abs_path); closedir(dir); return ret; }
/* Compares the hash for BUNDLE with that listed in the Manifest.MoM. If the * hash check fails, for now, we print a warning and return success, but * eventually will switch to being a fatal error and exit early. */ int verify_bundle_hash(struct manifest *manifest, struct file *bundle) { struct list *iter = list_head(manifest->manifests); struct file *current; char *local = NULL; int ret = 0; while (iter) { current = iter->data; iter = iter->next; if (strcmp(current->filename, bundle->filename) != 0) { continue; } string_or_die(&local, "%s/%i/Manifest.%s", state_dir, current->last_change, current->filename); if (!verify_file(bundle, local)) { printf("Warning: hash check failed for Manifest.%s for version %i\n", current->filename, manifest->version); ret = 0; } break; } free(local); return ret; }
/* clean_statedir will clean the state directory used by swupd (default to * /var/lib/swupd). It will remove all files except relevant manifests unless * all is set to true. Setting dry_run to true will print the files that would * be removed but will not actually remove them. */ enum swupd_code clean_statedir(bool dry_run, bool all) { char *staged_dir = NULL; string_or_die(&staged_dir, "%s/staged", state_dir); int ret = remove_if(staged_dir, dry_run, is_fullfile); free_string(&staged_dir); if (ret != 0) { return ret; } /* Pack presence indicator files. */ ret = remove_if(state_dir, dry_run, is_pack_indicator); if (ret != 0) { return ret; } /* Manifest delta files. */ ret = remove_if(state_dir, dry_run, is_manifest_delta); if (ret != 0) { return ret; } /* NOTE: do not clean the state_dir/bundles directory */ return clean_staged_manifests(state_dir, dry_run, all); }
void ensure_version_image_exists(int version) { char *conf; char *path = NULL; struct stat buf; int ret; conf = config_image_base(); string_or_die(&path, "%s/%i", conf, version); free(conf); ret = stat(path, &buf); if (ret < 0) { fprintf(stderr, "Failed to stat image directory %s (%s).. exiting\n", path, strerror(errno)); exit(EXIT_FAILURE); } if (!S_ISDIR(buf.st_mode)) { fprintf(stderr, "Assumed image directory %s is not a directory.. exiting\n", path); exit(EXIT_FAILURE); } free(path); }
void write_new_version(char *filename, int version) { FILE *file; char *fullfile = NULL; char *conf; conf = config_image_base(); string_or_die(&fullfile, "%s/%s", conf, filename); file = fopen(fullfile, "w"); free(conf); if (!file) { fprintf(stderr, "Cannot write new version, failed to open %s (%s)\n", fullfile, strerror(errno)); free(fullfile); return; } fprintf(file, "%i\n", version); fclose(file); free(fullfile); }
void read_current_version(char *filename) { FILE *file; int v = 0; char *fullfile = NULL; char *conf; conf = config_image_base(); string_or_die(&fullfile, "%s/%s", conf, filename); file = fopen(fullfile, "rm"); free(conf); if (!file) { fprintf(stderr, "Cannot read current version, failed to open %s (%s)\n", fullfile, strerror(errno)); free(fullfile); return; } if (fscanf(file, "%i", &v) < 0) { /* not even a single int there --> make sure to return 0 */ fprintf(stderr, "Version file %s does not have a number in it. Setting version to 0\n", fullfile); } fclose(file); free(fullfile); current_version = v; }
static int create_required_dirs(void) { int ret = 0; unsigned int i; char *dir; #define STATE_DIR_COUNT (sizeof(state_dirs) / sizeof(state_dirs[0])) const char *state_dirs[] = { "delta", "staged", "download", "telemetry" }; // check for existence if (ensure_root_owned_dir(state_dir)) { //state dir doesn't exist if (mkdir_p(state_dir) != 0 || chmod(state_dir, S_IRWXU) != 0) { fprintf(stderr, "Error: failed to create %s\n", state_dir); return -1; } } for (i = 0; i < STATE_DIR_COUNT; i++) { string_or_die(&dir, "%s/%s", state_dir, state_dirs[i]); ret = ensure_root_owned_dir(dir); if (ret) { ret = mkdir(dir, S_IRWXU); if (ret) { fprintf(stderr, "Error: failed to create %s\n", dir); return -1; } } free_string(&dir); } /* Do a final check to make sure that the top level dir wasn't * tampered with whilst we were creating the dirs */ return ensure_root_owned_dir(state_dir); }
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; }
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; }
int mkdir_p(const char *dir) { char *cmd; string_or_die(&cmd, "mkdir -p %s", dir); int ret = system(cmd); free_string(&cmd); return ret; }
static 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_string(&filename); string_or_die(&filename, "%s/download/.%s.tar", state_dir, file->hash); unlink(filename); free_string(&filename); /* downloaded and un-tar'd file */ string_or_die(&filename, "%s/staged/%s", state_dir, file->hash); (void)remove(filename); free_string(&filename); }
void check_mix_versions(int *current_version, int *server_version, char *path_prefix) { *current_version = read_mix_version_file("/usr/share/clear/version", path_prefix); char *format_file; string_or_die(&format_file, MIX_STATE_DIR "version/format%s/latest", format_string); *server_version = read_mix_version_file(format_file, path_prefix); free_string(&format_file); }
static void download_mix_file(struct file *file) { char *url, *filename; string_or_die(&url, "%s/%i/files/%s.tar", MIX_STATE_DIR, file->last_change, file->hash); string_or_die(&filename, "%s/download/.%s.tar", state_dir, file->hash); /* Mix content is local, so don't queue files up for curl downloads */ if (link_or_rename(url, filename) == 0) { untar_full_download(file); } else { warn("Failed to copy local mix file: %s\n", file->staging); } free_string(&url); free_string(&filename); }
int copy_all(const char *src, const char *dst) { char *cmd; string_or_die(&cmd, "cp -a %s %s 2> /dev/null", src, dst); int ret = system(cmd); free_string(&cmd); 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; }
void create_fullfiles(struct manifest *manifest) { GList *deduped_file_list; char *path; char *conf, *empty; empty = config_empty_dir(); char *const rmcmd[] = { "rm", "-rf", empty, NULL }; if (system_argv(rmcmd) != 0) { assert(0); } g_mkdir_with_parents(empty, S_IRWXU | S_IRWXG | S_IROTH | S_IXOTH); free(empty); conf = config_output_dir(); string_or_die(&path, "%s/%i", conf, manifest->version); if (mkdir(path, S_IRWXU | S_IRWXG | S_IROTH | S_IXOTH) != 0) { if (errno != EEXIST) { LOG(NULL, "Failed to create directory ", "%s", path); return; } } free(path); string_or_die(&path, "%s/%i/files/", conf, manifest->version); if (mkdir(path, S_IRWXU | S_IRWXG | S_IROTH | S_IXOTH) != 0) { if (errno != EEXIST) { LOG(NULL, "Failed to create directory ", "%s", path); return; } } free(path); free(conf); /* De-duplicate the list of fullfiles needing created to avoid races */ deduped_file_list = get_deduplicated_fullfile_list(manifest); /* Submit tasks to create full files */ submit_fullfile_tasks(deduped_file_list); g_list_free(deduped_file_list); }
/* 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; }