Exemple #1
0
/* 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;
}
Exemple #2
0
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);
}
Exemple #3
0
bool read_configuration_file(char *filename)
{
	GError *error = NULL;

	keyfile = g_key_file_new();

	if (!g_key_file_load_from_file(keyfile, filename, G_KEY_FILE_NONE, &error)) {
		printf("Failed to Load configuration file: %s (%s)\n", filename, error->message);
		g_error_free(error);
		return false;
	}
#if 0
	char *c;

	printf("Configuration settings:\n");

	c = config_image_base();
	printf("    image base path  : %s\n", c);
	free(c);

	c = config_output_dir();
	printf("    output directory : %s\n", c);
	free(c);

	c = config_empty_dir();
	printf("    empty  directory : %s\n", c);
	free(c);

	printf("\n");
#endif
	return true;
}
Exemple #4
0
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);
}
Exemple #5
0
/* Returns 0 == success, -1 == failure */
static int write_manifest_signature(struct manifest *manifest, const char *suffix)
{
	char *conf = config_output_dir();
	char *filename = NULL;
	int ret = -1;

	if (conf == NULL) {
		assert(0);
	}
	string_or_die(&filename, "%s/%i/Manifest.%s%s", conf, manifest->version,
		      manifest->component, suffix);
	if (!signature_sign(filename)) {
		fprintf(stderr, "Creating signature for '%s' failed\n", filename);
		goto exit;
	}
	ret = 0;
exit:
	free(filename);
	free(conf);
	return ret;
}
Exemple #6
0
void __create_delta(struct file *file, int from_version)
{
	char *original, *newfile, *outfile, *dotfile, *testnewfile, *sanitycheck;
	char *conf;
	int ret;

	if (file->is_link) {
		return;
	}

	if (file->is_deleted) {
		return; /* file got deleted -> by definition we cannot make a delta */
	}

	if (file->is_dir) {
		return; /* cannot do this for directories yet */
	}

	conf = config_image_base();
	string_or_die(&newfile, "%s/%i/full/%s", conf, file->last_change, file->filename);

	string_or_die(&original, "%s/%i/full/%s", conf, from_version, file->peer->filename);

	free(conf);

	conf = config_output_dir();

	string_or_die(&outfile, "%s/%i/delta/%i-%i-%s", conf, file->last_change, from_version, file->last_change, file->hash);
	string_or_die(&dotfile, "%s/%i/delta/.%i-%i-%s", conf, file->last_change, from_version, file->last_change, file->hash);
	string_or_die(&testnewfile, "%s/%i/delta/.%i-%i-%s.testnewfile", conf, file->last_change, from_version, file->last_change, file->hash);
	string_or_die(&sanitycheck, "cmp -s \"%s\" \"%s\"", newfile, testnewfile);

	LOG(file, "Making delta", "%s->%s", original, newfile);

	ret = xattrs_compare(original, newfile);
	if (ret != 0) {
		LOG(NULL, "xattrs have changed, don't create diff ", "%s", newfile);
		goto out;
	}
	ret = make_bsdiff_delta(original, newfile, dotfile, 0);
	if (ret < 0) {
		LOG(file, "Delta creation failed", "%s->%s ret is %i", original, newfile, ret);
		goto out;
	}
	if (ret == 1) {
		LOG(file, "...delta larger than newfile: FULLDL", "%s", newfile);
		unlink(dotfile);
		goto out;
	}

	/* does delta properly recreate expected content? */
	ret = apply_bsdiff_delta(original, testnewfile, dotfile);
	if (ret != 0) {
		printf("Delta application failed.\n");
		printf("Attempted %s->%s via diff %s\n", original, testnewfile, dotfile);
		LOG(file, "Delta application failed.", "Attempted %s->%s via diff %s", original, testnewfile, dotfile);

#warning the above is racy..tolerate it temporarily
		// ok fine
		//unlink(testnewfile);
		//unlink(dotfile);
		ret = 0;
		goto out;
	}
	xattrs_copy(original, newfile);

	/* does xattrs have been correctly copied?*/
	if (xattrs_compare(original, testnewfile) != 0) {
		printf("Delta application resulted in xattrs mismatch.\n");
		printf("%s->%s via diff %s yielded %s\n", original, newfile, dotfile, testnewfile);
		LOG(file, "Delta xattrs mismatch:", "%s->%s via diff %s yielded %s", original, newfile, dotfile, testnewfile);
		assert(0);
		goto out;
	}

	ret = system(sanitycheck);
	if (ret == -1 || !WIFEXITED(ret) || WEXITSTATUS(ret) == 2) {
		printf("Sanity check system command failed %i. \n", ret);
		printf("%s->%s via diff %s yielded %s\n", original, newfile, dotfile, testnewfile);
		assert(0);
		goto out;
	} else if (WEXITSTATUS(ret) == 1) {
		printf("Delta application resulted in file mismatch %i. \n", ret);
		printf("%s->%s via diff %s yielded %s\n", original, newfile, dotfile, testnewfile);
		LOG(file, "Delta mismatch:", "%s->%s via diff %s yielded %s", original, newfile, dotfile, testnewfile);

#warning this too will have failures due to races
		//unlink(testnewfile);
		//unlink(dotfile);
		ret = 0;
		goto out;
	}

	unlink(testnewfile);

	if (rename(dotfile, outfile) != 0) {
		if (errno == ENOENT) {
			LOG(NULL, "dotfile:", " %s does not exist", dotfile);
		}
		LOG(NULL, "Failed to rename", "");
	}
out:	free(sanitycheck);
	free(testnewfile);
	free(conf);
	free(newfile);
	free(original);
	free(outfile);
	free(dotfile);
}
Exemple #7
0
void write_cookiecrumbs_to_download_area(int version)
{
	char *conf;
	char *filename, *strformat;
	FILE *versionfd, *formatfd;

	conf = config_output_dir();
	if (conf == NULL) {
		assert(0);
	}

	string_or_die(&filename, "%s/%i/swupd-server-src-version", conf, version);
	versionfd = fopen(filename, "w");
	if (versionfd == NULL) {
		assert(0);
	}
	if (fwrite(VERSION, 1, strlen(VERSION), versionfd) != strlen(VERSION)) {
		assert(0);
	}
	fclose(versionfd);
	free(filename);

	string_or_die(&filename, "%s/%i/format", conf, version);
	string_or_die(&strformat, "%llu", format);
	formatfd = fopen(filename, "w");
	if (formatfd == NULL) {
		assert(0);
	}
	if (fwrite(strformat, 1, strlen(strformat), formatfd) != strlen(strformat)) {
		assert(0);
	}
	fclose(formatfd);
	free(filename);
	free(strformat);

/* Updating the WEBDIR/version/formatN/latest file is an operation very closely tied to a DevOps
 * workflow and not something swupd should make a decision about.
 *
 * The supported format values (replacing the "N" in the path above) are:
 *
 * - "staging": intended for testing only
 * - [1-+inf] (positive integer): the number that a corresponding version of swupd-client
 *   understands to run software updates. For example, if a given swupd-client release understands
 *   format 1, then WEBDIR/version/format1/latest contains the value of most recent release that
 *   that swupd-client can update to.
 *
 * Adapt this step appropriately for your DevOps flow.
 */
#if 0
	FILE *file;
	char *fullfile = NULL;
	char *param;

	string_or_die($param, "%s/version/formatstaging/", conf);
	char *const mkdircmd[] = { "mkdir", "-p", param, NULL };

	if (system_argv(mkdircmd) != 0) {
		assert(0);
	}
	free(param);

	string_or_die(&fullfile, "%s/version/formatstaging/latest", conf);
	file = fopen(fullfile, "w");
	if (!file) {
		fprintf(stderr, "Cannot write new version to download area, failed to open %s (%s)\n", fullfile, strerror(errno));
		free(fullfile);
		return;
	}
	fprintf(file, "%i\n", version);
	fclose(file);
	free(fullfile);
#endif
	free(conf);
}
Exemple #8
0
/* output must be a file, which is a (compressed) tar file, of the file denoted by "file", without any of its
   directory paths etc etc */
static void create_fullfile(struct file *file)
{
	char *origin;
	char *tarname = NULL;
	char *rename_source = NULL;
	char *rename_target = NULL;
	char *rename_tmpdir = NULL;
	int ret;
	struct stat sbuf;
	char *empty, *indir, *outdir;
	char *param1, *param2;

	if (file->is_deleted) {
		return; /* file got deleted -> by definition we cannot tar it up */
	}

	empty = config_empty_dir();
	indir = config_image_base();
	outdir = config_output_dir();

	string_or_die(&tarname, "%s/%i/files/%s.tar", outdir, file->last_change, file->hash);
	if (access(tarname, R_OK) == 0) {
		/* output file already exists...done */
		free(tarname);
		return;
	}
	free(tarname);
	//printf("%s was missing\n", file->hash);

	string_or_die(&origin, "%s/%i/full/%s", indir, file->last_change, file->filename);
	if (lstat(origin, &sbuf) < 0) {
		/* no input file: means earlier phase of update creation failed */
		LOG(NULL, "Failed to stat", "%s: %s", origin, strerror(errno));
		assert(0);
	}

	if (file->is_dir) { /* directories are easy */
		char *tmp1, *tmp2, *dir, *base;

		tmp1 = strdup(origin);
		assert(tmp1);
		base = basename(tmp1);

		tmp2 = strdup(origin);
		assert(tmp2);
		dir = dirname(tmp2);

		string_or_die(&rename_tmpdir, "%s/XXXXXX", outdir);
		if (!mkdtemp(rename_tmpdir)) {
			LOG(NULL, "Failed to create temporary directory for %s move", origin);
			assert(0);
		}

		string_or_die(&param1, "--exclude=%s/?*", base);
		string_or_die(&param2, "./%s", base);
		char *const tarcfcmd[] = { TAR_COMMAND, "-C", dir, TAR_PERM_ATTR_ARGS_STRLIST, "-cf", "-", param1, param2, NULL };
		char *const tarxfcmd[] = { TAR_COMMAND, "-C", rename_tmpdir, TAR_PERM_ATTR_ARGS_STRLIST, "-xf", "-", NULL };

		int tarcmdresult = system_argv_pipe(tarcfcmd, tarxfcmd);
		if (tarcmdresult != 0) {
			LOG(NULL, "Tar command for copying directory full file failed with code %d", tarcmdresult);
			assert(0);
		}
		free(param1);
		free(param2);

		string_or_die(&rename_source, "%s/%s", rename_tmpdir, base);
		string_or_die(&rename_target, "%s/%s", rename_tmpdir, file->hash);
		if (rename(rename_source, rename_target)) {
			LOG(NULL, "rename failed for %s to %s", rename_source, rename_target);
			assert(0);
		}
		free(rename_source);

		/* for a directory file, tar up simply with gzip */
		string_or_die(&param1, "%s/%i/files/%s.tar", outdir, file->last_change, file->hash);
		char *const tarcmd[] = { TAR_COMMAND, "-C", rename_tmpdir, TAR_PERM_ATTR_ARGS_STRLIST, "-zcf", param1, file->hash, NULL };

		if (system_argv(tarcmd) != 0) {
			assert(0);
		}
		free(param1);

		if (rmdir(rename_target)) {
			LOG(NULL, "rmdir failed for %s", rename_target);
		}
		free(rename_target);
		if (rmdir(rename_tmpdir)) {
			LOG(NULL, "rmdir failed for %s", rename_tmpdir);
		}
		free(rename_tmpdir);

		free(tmp1);
		free(tmp2);
	} else { /* files are more complex */
		char *gzfile = NULL, *bzfile = NULL, *xzfile = NULL;
		char *tempfile;
		uint64_t gz_size = LONG_MAX, bz_size = LONG_MAX, xz_size = LONG_MAX;

		/* step 1: hardlink the guy to an empty directory with the hash as the filename */
		string_or_die(&tempfile, "%s/%s", empty, file->hash);
		if (link(origin, tempfile) < 0) {
			LOG(NULL, "hardlink failed", "%s due to %s (%s -> %s)", file->filename, strerror(errno), origin, tempfile);
			char *const argv[] = { "cp", "-a", origin, tempfile, NULL };
			if (system_argv(argv) != 0) {
				assert(0);
			}
		}

		/* step 2a: tar it with each compression type  */
		// lzma
		string_or_die(&param1, "--directory=%s", empty);
		string_or_die(&param2, "%s/%i/files/%s.tar.xz", outdir, file->last_change, file->hash);
		char *const tarlzmacmd[] = { TAR_COMMAND, param1, TAR_PERM_ATTR_ARGS_STRLIST, "-Jcf", param2, file->hash, NULL };

		if (system_argv(tarlzmacmd) != 0) {
			assert(0);
		}
		free(param1);
		free(param2);

		// gzip
		string_or_die(&param1, "--directory=%s", empty);
		string_or_die(&param2, "%s/%i/files/%s.tar.gz", outdir, file->last_change, file->hash);
		char *const targzipcmd[] = { TAR_COMMAND, param1, TAR_PERM_ATTR_ARGS_STRLIST, "-zcf", param2, file->hash, NULL };

		if (system_argv(targzipcmd) != 0) {
			assert(0);
		}
		free(param1);
		free(param2);

#ifdef SWUPD_WITH_BZIP2
		string_or_die(&param1, "--directory=%s", empty);
		string_or_die(&param2, "%s/%i/files/%s.tar.bz2", outdir, file->last_change, file->hash);
		char *const tarbzip2cmd[] = { TAR_COMMAND, param1, TAR_PERM_ATTR_ARGS_STRLIST, "-jcf", param2, file->hash, NULL };

		if (system_argv(tarbzip2cmd) != 0) {
			assert(0);
		}
		free(param1);
		free(param2);

#endif

		/* step 2b: pick the smallest of the three compression formats */
		string_or_die(&gzfile, "%s/%i/files/%s.tar.gz", outdir, file->last_change, file->hash);
		if (stat(gzfile, &sbuf) == 0) {
			gz_size = sbuf.st_size;
		}
		string_or_die(&bzfile, "%s/%i/files/%s.tar.bz2", outdir, file->last_change, file->hash);
		if (stat(bzfile, &sbuf) == 0) {
			bz_size = sbuf.st_size;
		}
		string_or_die(&xzfile, "%s/%i/files/%s.tar.xz", outdir, file->last_change, file->hash);
		if (stat(xzfile, &sbuf) == 0) {
			xz_size = sbuf.st_size;
		}
		string_or_die(&tarname, "%s/%i/files/%s.tar", outdir, file->last_change, file->hash);
		if (gz_size <= xz_size && gz_size <= bz_size) {
			ret = rename(gzfile, tarname);
		} else if (xz_size <= bz_size) {
			ret = rename(xzfile, tarname);
		} else {
			ret = rename(bzfile, tarname);
		}
		if (ret != 0) {
			LOG(file, "post-tar rename failed", "ret=%d", ret);
		}
		unlink(bzfile);
		unlink(xzfile);
		unlink(gzfile);
		free(bzfile);
		free(xzfile);
		free(gzfile);
		free(tarname);

		/* step 3: remove the hardlink */
		unlink(tempfile);
		free(tempfile);
	}

	free(indir);
	free(outdir);
	free(empty);
	free(origin);
}
Exemple #9
0
/* Returns 0 == success, -1 == failure */
static int write_manifest_plain(struct manifest *manifest)
{
	GList *includes;
	GList *list;
	struct file *file;
	FILE *out = NULL;
	char *base = NULL, *dir;
	char *conf = config_output_dir();
	char *filename = NULL;
	char *submanifest_filename = NULL;
	int ret = -1;

	if (conf == NULL) {
		assert(0);
	}
	string_or_die(&filename, "%s/%i/Manifest.%s", conf, manifest->version, manifest->component);

	base = strdup(filename);
	if (base == NULL) {
		assert(0);
	}
	dir = dirname(base);

	if (g_mkdir_with_parents(dir, S_IRWXU | S_IRWXG | S_IROTH | S_IXOTH) != 0) {
		assert(0);
	}

	out = fopen(filename, "w");
	if (out == NULL) {
		printf("Failed to open %s for write\n", filename);
		goto exit;
	}

	fprintf(out, "MANIFEST\t%llu\n", format);
	fprintf(out, "version:\t%i\n", manifest->version);
	fprintf(out, "previous:\t%i\n", manifest->prevversion);
	fprintf(out, "filecount:\t%i\n", manifest->count);
	fprintf(out, "timestamp:\t%i\n", (int)time(NULL));
	compute_content_size(manifest);
	fprintf(out, "contentsize:\t%llu\n", (long long unsigned int)manifest->contentsize);
	includes = manifest->includes;
	while (includes) {
		struct manifest *sub = includes->data;
		includes = g_list_next(includes);
		fprintf(out, "includes:\t%s\n", sub->component);
	}
	fprintf(out, "\n");

	list = g_list_first(manifest->files);
	while (list) {
		file = list->data;
		list = g_list_next(list);

		fprintf(out, "%s\t%s\t%i\t%s\n", file_type_to_string(file), file->hash, file->last_change, file->filename);
	}

	list = g_list_first(manifest->manifests);
	while (list) {
		file = list->data;
		list = g_list_next(list);

		string_or_die(&submanifest_filename, "%s/%i/Manifest.%s", conf, file->last_change, file->filename);
		populate_file_struct(file, submanifest_filename);
		ret = compute_hash(file, submanifest_filename);
		if (ret != 0) {
			printf("Hash computation failed\n");
			assert(0);
		}

		fprintf(out, "%s\t%s\t%i\t%s\n", file_type_to_string(file), file->hash, file->last_change, file->filename);
		free(submanifest_filename);
	}

	ret = 0;
exit:
	if (out) {
		fclose(out);
	}
	free(conf);
	free(base);
	free(filename);
	return ret;
}
Exemple #10
0
struct manifest *manifest_from_file(int version, char *component)
{
	FILE *infile;
	GList *includes = NULL;
	char line[8192], *c, *c2;
	int count = 0;
	struct manifest *manifest;
	char *filename, *conf;
	int previous = 0;
	unsigned long long int format_number;

	conf = config_output_dir();
	if (conf == NULL) {
		assert(0);
	}

	string_or_die(&filename, "%s/%i/Manifest.%s", conf, version, component);
	free(conf);

	LOG(NULL, "Reading manifest", "%s", filename);
	infile = fopen(filename, "rb");

	if (infile == NULL) {
		LOG(NULL, "Cannot read manifest", "%s (%s)\n", filename, strerror(errno));
		free(filename);
		return alloc_manifest(version, component);
	}

	/* line 1: MANIFEST\t<version> */
	line[0] = 0;
	if (fgets(line, 8191, infile) == NULL) {
		fclose(infile);
		return NULL;
	}

	if (strncmp(line, "MANIFEST\t", 9) != 0) {
		printf("Invalid file format: MANIFEST line\n");
		fclose(infile);
		return NULL;
	}
	c = &line[9];
	format_number = strtoull(c, NULL, 10);
	if ((errno < 0) || (format_number == 0)) {
		//format string shall be a positive integer
		printf("Unknown file format version in MANIFEST line: %s\n", c);
		fclose(infile);
		return NULL;
	}
	line[0] = 0;
	while (strcmp(line, "\n") != 0) {
		/* read the header */
		line[0] = 0;
		if (fgets(line, 8191, infile) == NULL) {
			break;
		}
		c = strchr(line, '\n');
		if (c) {
			*c = 0;
		}
		if (strlen(line) == 0) {
			break;
		}
		c = strchr(line, '\t');
		/* Make sure we're not at the end of the array before incrementing */
		if (c && c <= &line[strlen(line)]) {
			c++;
		} else {
			printf("Manifest is corrupt\n");
			assert(0);
		}

		if (strncmp(line, "version:", 8) == 0) {
			version = strtoull(c, NULL, 10);
		}
		if (strncmp(line, "previous:", 9) == 0) {
			previous = strtoull(c, NULL, 10);
		}
		if (strncmp(line, "includes:", 9) == 0) {
			includes = g_list_prepend(includes, strdup(c));
			if (!includes->data) {
				abort();
			}
		}
	}

	manifest = alloc_manifest(version, component);
	manifest->format = format_number;
	manifest->prevversion = previous;
	manifest->includes = includes;

	/* empty line */
	while (!feof(infile)) {
		struct file *file;

		line[0] = 0;
		if (fgets(line, 8191, infile) == NULL) {
			break;
		}
		c = strchr(line, '\n');
		if (c) {
			*c = 0;
		}
		if (strlen(line) == 0) {
			break;
		}

		file = calloc(1, sizeof(struct file));
		if (file == NULL) {
			assert(0);
		}
		c = line;

		c2 = strchr(c, '\t');
		if (c2) {
			*c2 = 0;
			c2++;
		}

		if (c[0] == 'F') {
			file->is_file = 1;
		} else if (c[0] == 'D') {
			file->is_dir = 1;
		} else if (c[0] == 'L') {
			file->is_link = 1;
		} else if (c[0] == 'M') {
			LOG(NULL, "Found a manifest!", "%s", c);
			file->is_manifest = 1;
		} else if (c[0] != '.') {
			assert(0); /* unknown file type */
		}

		if (c[1] == 'd') {
			file->is_deleted = 1;
		} else if (c[1] != '.') {
			assert(0); /* unknown deleted status */
		}

		if (c[2] == 'C') {
			file->is_config = 1;
		} else if (c[2] == 's') {
			file->is_state = 1;
		} else if (c[2] == 'b') {
			file->is_boot = 1;
		} else if (c[2] != '.') {
			assert(0); /* unknown modifier status */
		}

		if (c[3] == 'r') {
			file->is_rename = 1;
		} else if (c[3] != '.') {
			; /* field 4: ignore unknown letters */
		}

		c = c2;
		if (!c) {
			free(file);
			continue;
		}
		c2 = strchr(c, '\t');
		if (c2) {
			*c2 = 0;
			c2++;
		}

		hash_assign(c, file->hash);

		c = c2;
		if (!c) {
			free(file);
			continue;
		}
		c2 = strchr(c, '\t');
		if (c2) {
			*c2 = 0;
			c2++;
		}

		file->last_change = strtoull(c, NULL, 10);

		c = c2;
		if (!c) {
			free(file);
			continue;
		}
		file->filename = strdup(c);

		if (file->is_manifest) {
			nest_manifest_file(manifest, file);
		} else {
			manifest->files = g_list_prepend(manifest->files, file);
		}
		manifest->count++;
		count++;
	}

	manifest->files = g_list_sort(manifest->files, file_sort_filename);
	fclose(infile);
	LOG(NULL, "Manifest info", "Manifest for version %i/%s contains %i files", version, component, count);
	free(filename);
	return manifest;
}