Compare commits

...
49 Commits
Author SHA1 Message Date
Patrick McCarty 8ac1869adf Call checkrenamed() with correct arguments
Some of the renames tests omitted the leading forward slash for the two
function arguments, so add them here.

Signed-off-by: Patrick McCarty <patrick.mccarty@intel.com>
2018-04-19 11:15:26 -07:00
Patrick McCarty fde00265e2 test: fix some typos in checkrenamed()
Due to these typos being present, several tests were passing that should
not have been.

Signed-off-by: Patrick McCarty <patrick.mccarty@intel.com>
2018-04-19 11:15:26 -07:00
Matthew Johnson 25d3ef658d Add deprecation message to README
Signed-off-by: Matthew Johnson <matthew.johnson@intel.com>
2018-04-19 11:04:08 -07:00
Matthew Johnson 3cf2ade205 Release v3.7.5
This release specifies that /usr/src/kernel/* files are not state files.
These files are needed to ship kernel source files for kernel
development.

Signed-off-by: Matthew Johnson <matthew.johnson@intel.com>
2017-12-13 16:47:30 -08:00
Miguel Bernal Marin e814a73480 heuristics: set /usr/src/kernel as not state dir
The kernel sources will be shipped in /usr/src/kernel but /usr/src/* is
state.

This commit enables /usr/src/kernel and its contents as a no state
directory.

Fixes #108

Signed-off-by: Miguel Bernal Marin <miguel.bernal.marin@linux.intel.com>
2017-12-13 16:40:27 -08:00
Matthew Johnson 9d3a813859 Release v3.7.4
This release cleans up renamed-to files by unsetting their rename flag
when orphaned. This was previously only being done for renamed-from
files.

Signed-off-by: Matthew Johnson <matthew.johnson@intel.com>
2017-12-05 12:21:44 -08:00
Matthew Johnson 5aa1bf9188 Also clean renamed-to files by unsetting the rename bit
Signed-off-by: Matthew Johnson <matthew.johnson@intel.com>
2017-12-05 12:21:35 -08:00
Matthew Johnson de04b8400c Release v3.7.3
This release fixes a bug causing fullfile creation to be attempted for
ghosted files.

Signed-off-by: Matthew Johnson <matthew.johnson@intel.com>
2017-12-04 14:15:15 -08:00
Matthew Johnson f5b614e39c Add comments to pack.c
As I was trying to understand the flow of pack.c I added comments as
notes to myself. These are helpful enough to be in the source.

Signed-off-by: Matthew Johnson <matthew.johnson@intel.com>
2017-12-02 11:56:42 -08:00
Matthew Johnson 03feb2af49 Do not attempt full file creation for ghosted files
Signed-off-by: Matthew Johnson <matthew.johnson@intel.com>
2017-12-02 11:56:42 -08:00
Matthew Johnson f3f1524f34 Refactor match_manifests function
This function had multiple sections of copy-pasted code. This was
causing an issue of bug-fixes being added to one part of code but not
the other. This patch refactors it to use internal functions where
appropriate and removes some dead code.

Signed-off-by: Matthew Johnson <matthew.johnson@intel.com>
2017-12-02 11:56:01 -08:00
Matthew Johnson 90c1c89183 Release v3.7.2
This release fixes a bug that caused ghosted files to only be deprecated
over format bumps. Ghosted files are now removed from the manifest every
build.

Signed-off-by: Matthew Johnson <matthew.johnson@intel.com>
2017-11-28 13:39:54 -08:00
Matthew Johnson 010131eb85 Fix ghosted-file deprecation
Ghosted files were only being deprecated over format bumps. Move the
format-bump-check logic out to the calling function to allow ghosted
files to be removed on every build. Also assign the is_ghosted flag when
adding "deleted" files from the old manifest so that the file will
actually be removed.

Signed-off-by: Matthew Johnson <matthew.johnson@intel.com>
2017-11-28 13:39:38 -08:00
Matthew Johnson fcf4e0d7fc Release v3.7.1
This release fixes the bug introduced in v3.7.0 that caused valid
renames from old versions to be dropped. Instead of pruning orphaned
renames, they are now marked as deleted as well.

Signed-off-by: Matthew Johnson <matthew.johnson@intel.com>
2017-11-28 09:19:14 -08:00
Matthew Johnson 6d3121cdc6 Add test cases for orphaned renames
* test that renames from old manifests persist
* test that renamed-to deleted is handled properly with the renamed-from
  becoming deleted.

Signed-off-by: Matthew Johnson <matthew.johnson@intel.com>
2017-11-27 17:22:50 -08:00
Matthew Johnson 6190894f40 Do not delete renames from old versions
Perform a final link on all renames in the manifest in order to track
renames from older versions. Do not remove orphaned renames but instead
keep them around as deleted files as well.

Signed-off-by: Matthew Johnson <matthew.johnson@intel.com>
2017-11-27 17:22:50 -08:00
Matthew Johnson e554a06ea6 Release v3.7.0
- Track ghosted files to enable renames on files that will be deleted by
  third-party programs (boot files via clr-boot-manager).
- Prune orphaned renames from manifest (renamed-to deleted or renamed
  again)

Signed-off-by: Matthew Johnson <matthew.johnson@intel.com>
2017-11-21 14:10:00 -08:00
Matthew Johnson 599ce7cb77 Add functional test for ghosting files
Adds ability to create test files with custom content to swupdlib.bash
as well.

Signed-off-by: Matthew Johnson <matthew.johnson@intel.com>
2017-11-21 14:06:14 -08:00
Matthew Johnson b8c5bdd2f3 Enable ghosted file generation
Ghost files that will be deleted by third-party software on the
client-side instead of marking them as deleted in the manifest. This
allows the client to treat these as deleted when doing rename detection.
Remove those ghosted files from the manifest in the next update.

Signed-off-by: Matthew Johnson <matthew.johnson@intel.com>
2017-11-21 14:06:14 -08:00
Matthew Johnson 14cef226d1 Add test for orphaned rename removal
Signed-off-by: Matthew Johnson <matthew.johnson@intel.com>
2017-11-21 14:03:45 -08:00
Matthew Johnson 83a9c39556 Prune orphaned rename files from manifest
When renamed-to files are deleted or are renamed again, the original
renamed-from file is orphaned as it has no renamed-to peer. Prune these
from the manifests by checking if the rename_peer field is NULL.

Signed-off-by: Matthew Johnson <matthew.johnson@intel.com>
2017-11-21 14:03:45 -08:00
Patrick McCarty b0c4a1c24e Enable rename support for Travis testing
Because renames are an opt-in feature (i.e. not enabled by default),
pass the required configure option for Travis builds, which enables the
rename tests.

Signed-off-by: Patrick McCarty <patrick.mccarty@intel.com>
2017-10-31 09:51:26 -07:00
Matthew Johnson 302ab343ca Use readdir instead of deprecated readdir_r in version.c
Fixes #78

Instead of using the deprecated readdir_r convert to readdir(3). This
fixes a compiler warning in version.c.

Signed-off-by: Matthew Johnson <matthew.johnson@intel.com>
2017-10-12 10:35:44 -07:00
Matthew Johnson 073b1fe062 Fix unused parameter compiler warnings in rename.c
Fixes #77

Signed-off-by: Matthew Johnson <matthew.johnson@intel.com>
2017-10-12 10:35:09 -07:00
Matthew Johnson 47addb4fa4 Release v3.6.3
This release improves full-file creation to make it thread-safe,
allowing parallelized runs of many instances of swupd_make_fullfiles.

Signed-off-by: Matthew Johnson <matthew.johnson@intel.com>
2017-10-05 13:33:28 -07:00
George T Kramer 557fb493ca Make full file creation for directories thread-safe
When running many instances of swupd_make_fullfiles in parallel, the
read end of the pipe between the tar's for creating the full file of a
directory becomes reused.  The observed behavior of this is
swupd_make_fullfiles hangs indefinitely with the expected tar reader
missing.  The corresponding tar writer is not killed with SIGPIPE because
there is at least one reader still for the pipe, swupd_make_fullfiles.
First forking from swupd_make_fullfiles, creating the pipe, and then
fork-and-exec'ing for each tar ensures that pipe and file descriptor
management is contained for the directory rename in question and cannot
be reused by other directory renames.

Signed-off-by: George T Kramer <george.t.kramer@intel.com>
2017-10-05 13:30:20 -07:00
Matthew Johnson ded4f3003d Travis: Use github as upstream for check
Recent outages to sourceforge break Travis CI runs. Use the github
release instead and update to the latest version of libcheck (0.11.0).

Signed-off-by: Matthew Johnson <matthew.johnson@intel.com>
2017-09-27 11:25:09 -07:00
Matthew Johnson cb9fe9f208 Improve debuginfo ban test
Improve the test for banning debuginfo via server.ini by first creating
an update without the ban followed by an update with the ban to make
sure the debuginfo is being properly pruned from the manifest.

Signed-off-by: Matthew Johnson <matthew.johnson@intel.com>
2017-09-21 09:17:24 -07:00
Matthew Johnson 5ecb58edff Release v3.6.2
The previous release (v3.6.1) was tagged at the wrong commit. This
release includes the changes described in that release note.

This release fixes a bug where swupd-server was creating the staged and
delta directories within the packs with 0750 permissions instead of the
expected 0700 permissions. It also adds a configuration option to
server.ini to ban debuginfo from the manifests and configure where
debuginfo libs and src are stored. Also prunes mistakenly added
debuginfo from the manifests when added accidentally and configured to
do so via server.ini.

Signed-off-by: Matthew Johnson <matthew.johnson@intel.com>
2017-09-20 11:02:18 -07:00
Matthew Johnson e3ca1cc566 Prune debuginfo when configured to do so
Fixes #71

Prune debuginfo from manifests when the [Debuginfo][pruned]
configuration is set to "true" in server.ini.

Signed-off-by: Matthew Johnson <matthew.johnson@intel.com>
2017-09-18 12:56:31 -07:00
Matthew Johnson 330bede498 Release v3.6.1
This release fixes a bug where swupd-server was creating the staged and
delta directories within the packs with 0750 permissions instead of the
expected 0700 permissions. It also adds a configuration option to
server.ini to ban debuginfo from the manifests and configure where
debuginfo libs and src are stored.

Signed-off-by: Matthew Johnson <matthew.johnson@intel.com>
2017-09-15 16:02:41 -07:00
Matthew Johnson 0fe32ce8fb Allow configuration to ban debuginfo from manifests
Use server.ini to optionally ban debuginfo from the manifests at
configurable paths.

Signed-off-by: Matthew Johnson <matthew.johnson@intel.com>
2017-09-15 15:56:02 -07:00
Matthew Johnson 31aec4684f Create staged and delta directories with 700 permissions
These directories were previously created with 750 permissions, which
caused a new check in swupd-client to remove them in order to correct
the permissions to 700. Create them the right way in the first place.

Signed-off-by: Matthew Johnson <matthew.johnson@intel.com>
2017-09-15 10:43:42 -07:00
Matthew Johnson 1fa24df6d6 Release v3.6.0
Calculate contentsize for all files in the manifest, not just in the
current update.

Signed-off-by: Matthew Johnson <matthew.johnson@intel.com>
2017-08-30 15:29:39 -07:00
Matthew Johnson e84600c21d Merge pull request #65 from matthewrsj/contentsize-fix
Calculate entire contentsize for manifest
2017-08-18 10:40:27 -07:00
Matthew Johnson 350bb54fdf Add functional test for contentsize fix
Add functional test to check manifest contentsize across versions and
includes.

Signed-off-by: Matthew Johnson <matthew.johnson@intel.com>
2017-08-18 10:13:11 -07:00
Matthew Johnson 15e6b1466f Calculate entire contentsize for manifest
Instead of only calculating the contentsize for files that were updated
in the current version (an inaccurate number for download size since
this is not the compressed size), calculate for all files in the
manifest. Additionally, do not add submanifest contentsizes to the
current manifest contentsize, as this will result in overcount on client
systems when multiple bundles include the same bundle.

With this change the contentsize field of the manifests will only report
the size of the files unique to that bundle. It is then the client's
responsibility to calculate total bundle size including included
bundles. This is reasonably easy to accomplish with the upcoming
swupd-client bundle-list --deps feature.

Although the above use case is a bit more work for the client, it
additionally allows a user to calculate the installation size of
multiple bundles much more easily, since it only has to count bundle
dependencies once to ensure files are not over counted.

If two bundles not in the same include chain have overlapping content,
summing the include chain of each bundle in the client will result in an
over-estimation of the total size on the system. The more content is
shared, the higher the over-estimation. In reality this overlap will not
be large, but it is currently impossible to calculate the exact
installed size using just the contentsize.

Signed-off-by: Matthew Johnson <matthew.johnson@intel.com>
2017-08-18 10:11:58 -07:00
Tudor Marcu f9ec967aa2 Release v3.5.0
This release removes a stale, unused script, adds a check to ensure format
number validity to ensure format numbers do not decrease, and enables adding
an actions: field in the Manifest.MoM to tell swupd-client to perform certain
actions post update.

Signed-off-by: Tudor Marcu <tudor.marcu@intel.com>
2017-07-28 23:54:30 -07:00
Matthew Johnson 925a5203f4 Add functional test for format bump actions field
Adds a functional test to check for the

actions:	update

line in the Manifest.MoM for the new format.

Signed-off-by: Matthew Johnson <matthew.johnson@intel.com>
2017-07-28 23:13:21 -07:00
Matthew Johnson 31bb949b9b Add actions field to Manifest.MoM for format bumps
When a format bump occurs and the new format is greater than the old
format, an actions field is written to the Manifest.MoM containing the
string "update". This "update" action tells the client that it is
necessary to re-execute swupd update to bring the client to the latest
version within the new format.

Signed-off-by: Matthew Johnson <matthew.johnson@intel.com>
2017-07-28 23:13:21 -07:00
Patrick McCarty a0c7025a9b Add basic check for format number validity
Because the intention is for swupd format numbers to either remain the
same between LAST_VER and current, or to increase as part of a format
bump, this leaves the remaining undesirable case.

Add a basic check to make sure the format never decreases, and add a
functional test.

Signed-off-by: Patrick McCarty <patrick.mccarty@intel.com>
2017-07-24 15:00:39 -07:00
Patrick McCarty 06320f2862 Remove unused script
The packfsck.pl is unused, so we can safely remove it from the repo.

Signed-off-by: Patrick McCarty <patrick.mccarty@intel.com>
2017-07-05 11:23:07 -07:00
Tudor Marcu 4f84b66321 Release v3.4.0
This release adds rename detection support which can be enabled via the
--enable-rename-detection option during configure time.

Signed-off-by: Tudor Marcu <tudor.marcu@intel.com>
2017-05-10 11:33:07 -07:00
Tudor Marcu 0d352881e7 Fix code style issues
Signed-off-by: Tudor Marcu <tudor.marcu@intel.com>
2017-05-10 11:05:28 -07:00
Icarus Sparry 7eff79397c Rename support
Signed-off-by: Icarus Sparry <icarus.w.sparry@intel.com>
2017-05-09 16:43:19 -07:00
Icarus Sparry 37f84501eb Additional tests for renames
Signed-off-by: Icarus Sparry <icarus.w.sparry@intel.com>
2017-05-09 16:43:19 -07:00
Icarus Sparry 850e6b9a92 Allow running only a single test
If the environment variable RUN_JUST_ONE is set then skip any test
which is not numerically equal to it.

Signed-off-by: Icarus Sparry <icarus.w.sparry@intel.com>
2017-05-09 16:43:19 -07:00
Icarus Sparry ce594a6529 Helper routine to generate files with content
Signed-off-by: Icarus Sparry <icarus.w.sparry@intel.com>
2017-05-09 16:43:19 -07:00
Icarus Sparry cfb0406b2e Fix creating test files in subdirectories
Allow the filenames to contain directory names

Signed-off-by: Icarus Sparry <icarus.w.sparry@intel.com>
2017-05-09 16:43:19 -07:00
34 changed files with 1674 additions and 563 deletions
+4 -4
View File
@@ -11,9 +11,9 @@ before_install:
- sudo apt-get install -y libmagic-dev - sudo apt-get install -y libmagic-dev
install: install:
- wget http://downloads.sourceforge.net/project/check/check/0.10.0/check-0.10.0.tar.gz - wget https://github.com/libcheck/check/releases/download/0.11.0/check-0.11.0.tar.gz
- tar -xvf check-0.10.0.tar.gz - tar -xvf check-0.11.0.tar.gz
- pushd check-0.10.0 && ./configure --prefix=/usr && make -j48 && sudo make install && popd - pushd check-0.11.0 && ./configure --prefix=/usr && make -j48 && sudo make install && popd
- wget https://github.com/clearlinux/bsdiff/releases/download/v1.0.2/bsdiff-1.0.2.tar.xz - wget https://github.com/clearlinux/bsdiff/releases/download/v1.0.2/bsdiff-1.0.2.tar.xz
- tar -xvf bsdiff-1.0.2.tar.xz - tar -xvf bsdiff-1.0.2.tar.xz
- pushd bsdiff-1.0.2 && ./configure --prefix=/usr --disable-tests && make -j48 && sudo make install && popd - pushd bsdiff-1.0.2 && ./configure --prefix=/usr --disable-tests && make -j48 && sudo make install && popd
@@ -21,5 +21,5 @@ install:
# Ubuntu's default umask is 0002, but this break's swupd hash calculations. # Ubuntu's default umask is 0002, but this break's swupd hash calculations.
script: script:
- sudo find test/functional -exec chmod g-w {} \; - sudo find test/functional -exec chmod g-w {} \;
- autoreconf --verbose --warnings=none --install --force && ./configure && make -j48 && sudo sh -c 'umask 0022 && make -j48 check' - autoreconf --verbose --warnings=none --install --force && ./configure --enable-rename-detection && make -j48 && sudo sh -c 'umask 0022 && make -j48 check'
after_failure: cat test-suite.log after_failure: cat test-suite.log
+12 -1
View File
@@ -108,18 +108,29 @@ TESTS = $(dist_check_SCRIPTS)
dist_check_SCRIPTS = \ dist_check_SCRIPTS = \
test/functional/basic/test.bats \ test/functional/basic/test.bats \
test/functional/contentsize-across-versions-includes/test.bats \
test/functional/delete-no-version-bump/test.bats \ test/functional/delete-no-version-bump/test.bats \
test/functional/file-name-blacklisted/test.bats \ test/functional/file-name-blacklisted/test.bats \
test/functional/file-name-debuginfo/test.bats \
test/functional/format-no-decrement/test.bats \
test/functional/full-run-delta/test.bats \ test/functional/full-run-delta/test.bats \
test/functional/full-run/test.bats \ test/functional/full-run/test.bats \
test/functional/fullfiles/test.bats \ test/functional/fullfiles/test.bats \
test/functional/ghosting/test.bats \
test/functional/include-version-bump/test.bats \ test/functional/include-version-bump/test.bats \
test/functional/includes-deduplicate/test.bats \ test/functional/includes-deduplicate/test.bats \
test/functional/no-delta/test.bats \ test/functional/no-delta/test.bats \
test/functional/pack/test.bats \ test/functional/pack/test.bats \
test/functional/state-file/test.bats \ test/functional/state-file/test.bats \
test/functional/subtract-delete/test.bats \ test/functional/subtract-delete/test.bats \
test/functional/update/test.bats test/functional/update/test.bats \
test/functional/format-bump/test.bats
if RENAMES
dist_check_SCRIPTS += \
test/functional/orphaned-renames/test.bats \
test/functional/renames/test.bats
endif
endif endif
if COVERAGE if COVERAGE
+9
View File
@@ -1,3 +1,12 @@
With the release of mixer-tools v4.3.0 [1] the swupd-server project is
deprecated. All functionality has been re-written into the mixer tool under
the `mixer build update` command. All development done around creating update
content consumed by swupd-client is now done in that project.
[1] https://github.com/clearlinux/mixer-tools
---
The swupd-server package provides a reference implementation of a software The swupd-server package provides a reference implementation of a software
update server-side component that generates update content consumable by a update server-side component that generates update content consumable by a
software update client (swupd-client). Such content includes manifests that software update client (swupd-client). Such content includes manifests that
+6 -1
View File
@@ -2,7 +2,7 @@
# Process this file with autoconf to produce a configure script. # Process this file with autoconf to produce a configure script.
AC_PREREQ([2.66]) AC_PREREQ([2.66])
AC_INIT(swupd-server, 3.3.5, tudor.marcu@intel.com) AC_INIT(swupd-server, 3.7.5, matthew.johnson@intel.com)
AM_INIT_AUTOMAKE([foreign -Wall -W subdir-objects]) AM_INIT_AUTOMAKE([foreign -Wall -W subdir-objects])
AM_SILENT_RULES([yes]) AM_SILENT_RULES([yes])
AC_PROG_CC AC_PROG_CC
@@ -82,6 +82,11 @@ AS_IF([test "$enable_lzma" != "no"], [
]) ])
AM_CONDITIONAL([ENABLE_LZMA], [test "$enable_lzma" != "no"]) AM_CONDITIONAL([ENABLE_LZMA], [test "$enable_lzma" != "no"])
AC_ARG_ENABLE([rename-detection], [AS_HELP_STRING([--enable-rename-detection], [enable rename detection feature])])
AS_IF([test "$enable_rename_detection" = "yes"], [AC_DEFINE(RENAMES,1,[Use rename detection])])
AM_CONDITIONAL([RENAMES], [test "$enable_rename_detection" = "yes"])
AC_CONFIG_FILES([Makefile]) AC_CONFIG_FILES([Makefile])
AC_REQUIRE_AUX_FILE([tap-driver.sh]) AC_REQUIRE_AUX_FILE([tap-driver.sh])
AC_OUTPUT AC_OUTPUT
+33 -10
View File
@@ -65,6 +65,13 @@
#include <lzma.h> #include <lzma.h>
#endif #endif
/* Approximatly the smallest size of a pair of input files which
* differ by a single bit that bsdiff can produce a more compact
* deltafile. Files smaller than this are always marked as different.
* See the magic 200 value in the bsdiff/src/diff.c code.
*/
#define BSDIFFSIZE 200
struct manifest { struct manifest {
unsigned long long int format; unsigned long long int format;
int version; int version;
@@ -78,6 +85,8 @@ struct manifest {
GList *submanifests; /* as struct manifest */ GList *submanifests; /* as struct manifest */
GList *includes; /* struct manifests for all bundles included into this one */ GList *includes; /* struct manifests for all bundles included into this one */
GList *actions; /* post-update actions */
}; };
struct file; struct file;
@@ -108,6 +117,10 @@ struct file {
unsigned int is_file : 1; unsigned int is_file : 1;
unsigned int is_link : 1; unsigned int is_link : 1;
unsigned int is_deleted : 1; unsigned int is_deleted : 1;
/* a ghosted file is treated as deleted so that it can be used as a rename,
* but is not actually deleted in the client due to a 3rd-party program
* cleaning it up. This happens with boot files managed by a boot manager */
unsigned int is_ghosted : 1;
unsigned int is_manifest : 1; unsigned int is_manifest : 1;
/* and these are modifiers */ /* and these are modifiers */
@@ -122,7 +135,7 @@ struct file {
double rename_score; double rename_score;
struct file *rename_peer; struct file *rename_peer;
char *alpha_only_filename; /* filename minus all numerics/etc */ char *alpha_only_filename; /* filename minus all numerics/etc */
char *filetype; char *filetype; /* The output of 'file', truncated */
char *basename; char *basename;
char *dirname; char *dirname;
/* end of rename detection fields */ /* end of rename detection fields */
@@ -166,7 +179,9 @@ extern void release_configuration_data(void);
extern char *config_image_base(void); extern char *config_image_base(void);
extern char *config_output_dir(void); extern char *config_output_dir(void);
extern char *config_empty_dir(void); extern char *config_empty_dir(void);
extern char *config_debuginfo_path(const char *path);
extern int config_initial_version(void); extern int config_initial_version(void);
extern bool config_ban_debuginfo(void);
extern void read_current_version(char *filename); extern void read_current_version(char *filename);
extern void write_new_version(char *filename, int version); extern void write_new_version(char *filename, int version);
@@ -177,13 +192,14 @@ extern GList *get_last_versions_list(int next_version, int max_versions);
extern char *file_type_to_string(struct file *file); extern char *file_type_to_string(struct file *file);
extern struct manifest *manifest_from_file(int version, char *module); extern struct manifest *manifest_from_file(int version, char *module);
extern void free_manifest(struct manifest *manifest); extern void free_manifest(struct manifest *manifest);
extern struct manifest *alloc_manifest(int version, char *module); extern struct manifest *alloc_manifest(int version, char *module, GList *actions);
extern int match_manifests(struct manifest *m1, struct manifest *m2); extern int match_manifests(struct manifest *m1, struct manifest *m2);
extern void sort_manifest_by_version(struct manifest *manifest); extern void sort_manifest_by_version(struct manifest *manifest);
extern bool manifest_includes(struct manifest *manifest, char *component); extern bool manifest_includes(struct manifest *manifest, char *component);
extern bool changed_includes(struct manifest *old, struct manifest *new); extern bool changed_includes(struct manifest *old, struct manifest *new);
extern int prune_manifest(struct manifest *manifest); extern int prune_manifest(struct manifest *manifest);
extern int remove_old_deleted_files(struct manifest *m1, struct manifest *m2); extern void clean_renames(struct manifest *manifest);
extern int remove_deprecated_files(struct manifest *m1, struct manifest *m2, bool (*compfunc)(struct file *, struct file *));
extern void create_manifest_delta(int oldversion, int newversion, char *module); extern void create_manifest_delta(int oldversion, int newversion, char *module);
extern void create_manifest_deltas(struct manifest *manifest, GList *last_versions_list); extern void create_manifest_deltas(struct manifest *manifest, GList *last_versions_list);
extern void subtract_manifests_frontend(struct manifest *m1, struct manifest *m2); extern void subtract_manifests_frontend(struct manifest *m1, struct manifest *m2);
@@ -218,8 +234,6 @@ extern void chroot_create_full(int newversion);
extern void read_group_file(char *filename); extern void read_group_file(char *filename);
extern void release_group_file(void); extern void release_group_file(void);
extern char *group_groups(char *group);
extern char *group_packages(char *group);
extern char *group_status(char *group); extern char *group_status(char *group);
extern char *next_group(void); extern char *next_group(void);
@@ -242,8 +256,9 @@ extern int previous_version_manifest(struct manifest *mom, char *name);
extern void type_change_detection(struct manifest *manifest); extern void type_change_detection(struct manifest *manifest);
extern void rename_detection(struct manifest *manifest, int last_change, GList *last_versions_list); extern void rename_detection(struct manifest *manifest);
extern void link_renames(GList *newfiles, struct manifest *from_manifest); extern void link_renames(GList *newfiles, int to_version);
extern void final_link(GList *files);
extern void __create_delta(struct file *file, int from_version, char *from_hash); extern void __create_delta(struct file *file, int from_version, char *from_hash);
extern void account_delta_hit(void); extern void account_delta_hit(void);
@@ -253,10 +268,18 @@ extern FILE *fopen_exclusive(const char *filename); /* no mode, opens for write
extern void dump_file_info(struct file *file); extern void dump_file_info(struct file *file);
extern void string_or_die(char **strp, const char *fmt, ...); extern void string_or_die(char **strp, const char *fmt, ...);
extern void print_elapsed_time(const char *step, struct timeval *previous_time, struct timeval *current_time); extern void print_elapsed_time(const char *step, struct timeval *previous_time, struct timeval *current_time);
extern int system_argv_pipe(char *const lhscmd[], char *const rhscmd[]);
extern int system_argv_pipe_fd(int lnewstdinfd, int lnewstderrfd, char *const lhscmd[],
int rnewstdoutfd, int rnewstderrfd, char *const rhscmd[]);
extern void pipe_monitor(int lnewstdinfd, int lnewstderrfd, char *const lhscmd[],
int rnewstdoutfd, int rnewstderrfd, char *const rhscmd[]);
extern int system_argv(char *const argv[]); extern int system_argv(char *const argv[]);
extern int system_argv_fd(char *const argv[], int newstdin, int newstdout, int newstderr); extern int system_argv_fd(int newstdinfd, int newstdoutfd, int newstderrfd, char *const cmd[]);
extern int system_argv_pipe(char *const argvp1[], int stdinp1, int stderrp1, extern pid_t system_argv_fd_nowait(int newstdinfd, int newstdoutfd, int newstderrfd, int closefd, char *const cmd[]);
char *const argvp2[], int stdoutp2, int stderrp2); extern void exec_cmd_fd(int newstdinfd, int newstdoutfd, int newstderrfd, int closefd, char *const cmd[]);
extern void move_fd(int oldfd, int newfd);
extern int wait_process_terminate(pid_t pid);
extern int num_threads(float scaling); extern int num_threads(float scaling);
extern bool file_is_debuginfo(const char *path);
#endif #endif
-82
View File
@@ -1,82 +0,0 @@
#!/usr/bin/perl -w
#
#
# arguments:
# perl packfsck.pl <target version>
my $target = $ARGV[0];
system("rm /tmp/Manifest");
system("wget --quiet --output-document=/tmp/Manifest https://download.clearlinux.org/update/$target/Manifest.os-core");
my $from = $target;
while ($from > $target - 100) {
$from = $from - 10;
print "Testing the $from-$target pack\n";
system("rm /tmp/pack.tar");
system("wget --quiet ---output-document=/tmp/pack.tar https://download.clearlinux.org/update/$target/pack-os-core-from-$from.tar");
open FILE, "</tmp/Manifest";
my %expected_hashes;
my %files;
while (<FILE>) {
my $line = $_;
if ($line =~ /(^[FDLr\.]+)\s*([0-9a-f]+)\s*([0-9]+)\s*+(.*)\n/) {
my $type = $1;
my $hash = $2;
my $version = $3;
my $filename = $4;
if ($version > $from) {
$expected_hashes{"$hash"} = 1;
$files{"$hash"} = $filename;
# print "$1 - $2 - $3 - $4\n";
}
}
}
close FILE;
open FILE2, "-|", "tar -tf /tmp/pack.tar 2> /dev/null";
while (<FILE2>) {
my $line = $_;
if ($line =~ /^\.\/delta\/[0-9]+.[0-9]+.([0-9a-f]+)/) {
my $hash = $1;
if (!defined($expected_hashes{"$hash"})) {
print "\tUnexpected delta hash found $hash\n";
}
$expected_hashes{"$hash"} = 2;
}
if ($line =~ /\.\/staged\/([0-9a-f]+)/) {
my $hash = $1;
if (!defined($expected_hashes{"$hash"})) {
print "\tUnexpected staged hash found $hash\n";
}
$expected_hashes{"$hash"} = 2;
}
}
close FILE2;
my $count = 0;
foreach my $key ( keys %expected_hashes )
{
my $value = $expected_hashes{$key};
if ($value eq 1) {
my $fn = $files{$key};
print "\tFile $fn ($key) is not in the pack\n";
$count = $count = 1;
}
}
if ($count < 1) {
print "\tThe $from-$target pack has no files missing\n";
}
}
+5
View File
@@ -2,3 +2,8 @@
emptydir=/var/lib/update/empty/ emptydir=/var/lib/update/empty/
imagebase=/var/lib/update/image/ imagebase=/var/lib/update/image/
outputdir=/var/lib/update/www/ outputdir=/var/lib/update/www/
[Debuginfo]
banned=true
lib=/usr/lib/debug/
src=/usr/src/debug/
+20 -3
View File
@@ -42,6 +42,10 @@
static GThreadPool *threadpool; static GThreadPool *threadpool;
/* Why not strcpy? Looks like the hash was going to be stored in
* binary at one stage. Should use g_string_chunk_insert_const to
* change hash_compare to a pointer compare
*/
void hash_assign(char *src, char *dst) void hash_assign(char *src, char *dst)
{ {
memcpy(dst, src, SWUPD_HASH_LEN - 1); memcpy(dst, src, SWUPD_HASH_LEN - 1);
@@ -147,7 +151,13 @@ void populate_file_struct(struct file *file, char *filename)
ret = lstat(filename, &stat); ret = lstat(filename, &stat);
if (ret < 0) { if (ret < 0) {
LOG(NULL, "stat error ", "%s: %s", filename, strerror(errno)); LOG(NULL, "stat error ", "%s: %s", filename, strerror(errno));
/* delete the file if it isn't found and isn't a boot file,
* mark as ghosted if this is a boot file */
if (file->is_boot) {
file->is_ghosted = 1;
} else {
file->is_deleted = 1; file->is_deleted = 1;
}
return; return;
} }
file->stat.st_mode = stat.st_mode; file->stat.st_mode = stat.st_mode;
@@ -196,7 +206,7 @@ int compute_hash(struct file *file, char *filename)
return 0; return 0;
} }
hash_set_zeros(key); hash_set_zeros(key); /* Set to 64 '0' (not '\0') characters */
if (file->is_link) { if (file->is_link) {
char link[PATH_MAX]; char link[PATH_MAX];
@@ -310,6 +320,13 @@ static struct file *add_file(struct manifest *manifest,
GError *err = NULL; GError *err = NULL;
struct file *file; struct file *file;
if (config_ban_debuginfo() && file_is_debuginfo(sub_filename)) {
printf("WARNING: File %s is banned ...skipping.\n", sub_filename);
free(sub_filename);
free(fullname);
return NULL;
}
if (illegal_characters(entry_name)) { if (illegal_characters(entry_name)) {
printf("WARNING: Filename %s includes illegal character(s) ...skipping.\n", sub_filename); printf("WARNING: Filename %s includes illegal character(s) ...skipping.\n", sub_filename);
free(sub_filename); free(sub_filename);
@@ -477,7 +494,7 @@ struct manifest *full_manifest_from_directory(int version)
LOG(NULL, "Computing hashes", "for %i/full", version); LOG(NULL, "Computing hashes", "for %i/full", version);
manifest = alloc_manifest(version, "full"); manifest = alloc_manifest(version, "full", NULL);
string_or_die(&dir, "%s/%i/full", image_dir, version); string_or_die(&dir, "%s/%i/full", image_dir, version);
@@ -553,7 +570,7 @@ struct manifest *sub_manifest_from_directory(char *component, int version)
LOG(NULL, "Creating component manifest", "for %i/%s", version, component); LOG(NULL, "Creating component manifest", "for %i/%s", version, component);
manifest = alloc_manifest(version, component); manifest = alloc_manifest(version, component, NULL);
string_or_die(&dir, "%s/%i/%s", image_dir, version, component); string_or_die(&dir, "%s/%i/%s", image_dir, version, component);
+14
View File
@@ -67,6 +67,20 @@ int config_initial_version(void)
return version; return version;
} }
bool config_ban_debuginfo(void)
{
assert(keyfile != NULL);
return g_key_file_get_boolean(keyfile, "Debuginfo", "banned", NULL);
}
char *config_debuginfo_path(const char *comp)
{
assert(keyfile != NULL);
return g_key_file_get_value(keyfile, "Debuginfo", comp, NULL);
}
bool read_configuration_file(char *filename) bool read_configuration_file(char *filename)
{ {
GError *error = NULL; GError *error = NULL;
+75 -17
View File
@@ -236,6 +236,16 @@ static int check_group_file(void)
return ret; return ret;
} }
static bool both_ghosted(struct file *file1, struct file *file2)
{
return (file1->is_ghosted && file2->is_ghosted);
}
static bool both_deleted(struct file *file1, struct file *file2)
{
return (file1->is_deleted && file2->is_deleted);
}
int main(int argc, char **argv) int main(int argc, char **argv)
{ {
struct manifest *new_core = NULL; struct manifest *new_core = NULL;
@@ -252,6 +262,7 @@ int main(int argc, char **argv)
GList *manifests_last_versions_list = NULL; GList *manifests_last_versions_list = NULL;
int newfiles = 0; int newfiles = 0;
int old_deleted = 0; int old_deleted = 0;
int old_ghosted = 0;
struct timeval current_time; struct timeval current_time;
struct timeval previous_time; struct timeval previous_time;
@@ -325,15 +336,24 @@ int main(int argc, char **argv)
apply_heuristics(new_full); apply_heuristics(new_full);
match_manifests(old_full, new_full); match_manifests(old_full, new_full);
old_deleted = remove_old_deleted_files(old_full, new_full); if (old_full->format < new_full->format) {
old_deleted = remove_deprecated_files(old_full, new_full, both_deleted);
} else {
old_deleted = 0;
}
if (old_deleted > 0) { if (old_deleted > 0) {
LOG(NULL, "", "Old deleted files (%d) removed from full manifest", old_deleted); LOG(NULL, "", "Old deleted files (%d) removed from full manifest", old_deleted);
printf("Old deleted files (%d) removed from full manifest\n", old_deleted); printf("Old deleted files (%d) removed from full manifest\n", old_deleted);
} }
old_ghosted = remove_deprecated_files(old_full, new_full, both_ghosted);
if (old_ghosted > 0) {
LOG(NULL, "", "Old ghosted files (%d) removed from full manifest", old_ghosted);
printf("Old ghosted files (%d) removed from full manifest", old_ghosted);
}
apply_heuristics(new_full); apply_heuristics(new_full);
#warning disabled rename detection for some simplicity
// rename_detection(new_full);
print_elapsed_time("full manifest creation", &previous_time, &current_time); print_elapsed_time("full manifest creation", &previous_time, &current_time);
@@ -342,7 +362,25 @@ int main(int argc, char **argv)
/* Step 2: Make a manifest for the os-core set */ /* Step 2: Make a manifest for the os-core set */
old_MoM = manifest_from_file(current_version, "MoM"); old_MoM = manifest_from_file(current_version, "MoM");
new_MoM = alloc_manifest(newversion, "MoM");
/* Verify that the new format is not older than the previous format. It
* never makes sense to decrease the format number for the next build. */
if (format < old_MoM->format) {
LOG(NULL, "", "Current format (%llu) must be greater than or equal to previous format (%llu). Exiting",
format, old_MoM->format);
printf("Current format (%llu) must be greater than or equal to previous format (%llu). Exiting\n",
format, old_MoM->format);
goto exit;
}
/* Detect a format bump and add the "update" action to the manifest
* "actions:" field */
GList *actions = NULL;
if (format > old_MoM->format) {
actions = g_list_prepend(actions, "update");
}
new_MoM = alloc_manifest(newversion, "MoM", actions);
old_core = manifest_from_file(manifest_subversion(old_MoM, "os-core"), "os-core"); old_core = manifest_from_file(manifest_subversion(old_MoM, "os-core"), "os-core");
new_core = sub_manifest_from_directory("os-core", newversion); new_core = sub_manifest_from_directory("os-core", newversion);
add_component_hashes_to_manifest(new_core, new_full); add_component_hashes_to_manifest(new_core, new_full);
@@ -365,21 +403,28 @@ int main(int argc, char **argv)
apply_heuristics(new_core); apply_heuristics(new_core);
/* Step 3c: ... else save the manifest */ /* Step 3c: ... else save the manifest */
type_change_detection(new_core); type_change_detection(new_core);
#ifdef RENAMES
/* Detect renamed files specifically for os-core */
rename_detection(new_core);
#endif
if (old_core->format < new_core->format) {
old_deleted = remove_deprecated_files(old_core, new_core, both_deleted);
} else {
old_deleted = 0;
}
#warning disabled rename detection for some simplicity old_ghosted = remove_deprecated_files(old_core, new_core, both_ghosted);
/* Detect renamed files specifically for each pack */ sort_manifest_by_version(new_core); /* sorts by filename */
// rename_detection(...); /* clean up orphaned renames by marking them as deleted */
clean_renames(new_core);
old_deleted = remove_old_deleted_files(old_core, new_core);
sort_manifest_by_version(new_core);
newfiles = prune_manifest(new_core); newfiles = prune_manifest(new_core);
if (newfiles <= 0) { if (newfiles <= 0) {
LOG(NULL, "", "Core component has not changed (after pruning), exiting"); LOG(NULL, "", "Core component has not changed (after pruning), exiting");
printf("Core component has not changed (after pruning), exiting\n"); printf("Core component has not changed (after pruning), exiting\n");
goto exit; goto exit;
} }
LOG(NULL, "", "Core component has changes (%d new, %d deleted), writing out new manifest", newfiles, old_deleted); LOG(NULL, "", "Core component has changes (%d new, %d deleted, %d ghosted), writing out new manifest", newfiles, old_deleted, old_ghosted);
printf("Core component has changes (%d new, %d deleted), writing out new manifest\n", newfiles, old_deleted); printf("Core component has changes (%d new, %d deleted, %d ghosted), writing out new manifest\n", newfiles, old_deleted, old_ghosted);
if (write_manifest(new_core) != 0) { if (write_manifest(new_core) != 0) {
LOG(NULL, "", "Core component manifest write failed"); LOG(NULL, "", "Core component manifest write failed");
printf("Core component manifest write failed\n"); printf("Core component manifest write failed\n");
@@ -486,15 +531,26 @@ int main(int argc, char **argv)
newm->version = oldm->version; newm->version = oldm->version;
} else { } else {
apply_heuristics(newm); apply_heuristics(newm);
#warning missing rename_detection here #ifdef RENAMES
/* Detect renamed files specifically for this bundle */
rename_detection(newm);
#endif
/* Step 6b: otherwise, write out the manifest */ /* Step 6b: otherwise, write out the manifest */
old_deleted = remove_old_deleted_files(oldm, newm); if (oldm->format < newm->format) {
old_deleted = remove_deprecated_files(oldm, newm, both_deleted);
} else {
old_deleted = 0;
}
old_ghosted = remove_deprecated_files(oldm, newm, both_ghosted);
sort_manifest_by_version(newm); sort_manifest_by_version(newm);
type_change_detection(newm); type_change_detection(newm);
/* clean up orphaned renames by marking them as deleted */
clean_renames(newm);
newfiles = prune_manifest(newm); newfiles = prune_manifest(newm);
if (newfiles > 0 || old_deleted > 0 || changed_includes(oldm, newm)) { if (newfiles > 0 || old_deleted > 0 || old_ghosted > 0 || changed_includes(oldm, newm)) {
LOG(NULL, "", "%s component has changes (%d new, %d deleted), writing out new manifest", group, newfiles, old_deleted); LOG(NULL, "", "%s component has changes (%d new, %d deleted, %d ghosted), writing out new manifest", group, newfiles, old_deleted, old_ghosted);
printf("%s component has changes (%d new, %d deleted), writing out new manifest\n", group, newfiles, old_deleted); printf("%s component has changes (%d new, %d deleted, %d ghosted), writing out new manifest\n", group, newfiles, old_deleted, old_ghosted);
if (write_manifest(newm) != 0) { if (write_manifest(newm) != 0) {
LOG(NULL, "", "%s component manifest write failed", group); LOG(NULL, "", "%s component manifest write failed", group);
printf("%s component manifest write failed\n", group); printf("%s component manifest write failed\n", group);
@@ -531,6 +587,8 @@ int main(int argc, char **argv)
maximize_to_full(new_MoM, new_full); maximize_to_full(new_MoM, new_full);
sort_manifest_by_version(new_full); sort_manifest_by_version(new_full);
/* clean up orphaned renames by marking them as deleted */
clean_renames(new_full);
prune_manifest(new_full); prune_manifest(new_full);
if (write_manifest(new_full) != 0) { if (write_manifest(new_full) != 0) {
goto exit; goto exit;
+12 -14
View File
@@ -50,7 +50,6 @@ static void create_fullfile(struct file *file)
struct stat sbuf; struct stat sbuf;
char *empty, *indir, *outdir; char *empty, *indir, *outdir;
char *param1, *param2; char *param1, *param2;
int stderrfd;
if (file->is_deleted) { if (file->is_deleted) {
return; /* file got deleted -> by definition we cannot tar it up */ return; /* file got deleted -> by definition we cannot tar it up */
@@ -98,17 +97,13 @@ static void create_fullfile(struct file *file)
char *const tarcfcmd[] = { TAR_COMMAND, "-C", dir, TAR_PERM_ATTR_ARGS_STRLIST, "-cf", "-", param1, param2, NULL }; 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 }; char *const tarxfcmd[] = { TAR_COMMAND, "-C", rename_tmpdir, TAR_PERM_ATTR_ARGS_STRLIST, "-xf", "-", NULL };
stderrfd = open("/dev/null", O_WRONLY); int tarcmdresult = system_argv_pipe(tarcfcmd, tarxfcmd);
if (stderrfd == -1) { if (tarcmdresult != 0) {
LOG(NULL, "Failed to open /dev/null", ""); LOG(NULL, "Tar command for copying directory full file failed with code %d", tarcmdresult);
assert(0);
}
if (system_argv_pipe(tarcfcmd, -1, stderrfd, tarxfcmd, -1, stderrfd) != 0) {
assert(0); assert(0);
} }
free(param1); free(param1);
free(param2); free(param2);
close(stderrfd);
string_or_die(&rename_source, "%s/%s", rename_tmpdir, base); string_or_die(&rename_source, "%s/%s", rename_tmpdir, base);
string_or_die(&rename_target, "%s/%s", rename_tmpdir, file->hash); string_or_die(&rename_target, "%s/%s", rename_tmpdir, file->hash);
@@ -249,22 +244,20 @@ static GList *get_deduplicated_fullfile_list(struct manifest *manifest)
struct file *tmp; struct file *tmp;
// presort by hash for easy deduplication // presort by hash for easy deduplication
manifest->files = g_list_sort(manifest->files, file_sort_hash); list = manifest->files = g_list_sort(manifest->files, file_sort_hash);
list = g_list_first(manifest->files); for (; list; list = g_list_next(list)) {
while (prev == NULL && list != NULL) {
tmp = list->data; tmp = list->data;
list = g_list_next(list);
// find first new file // find first new file
if (tmp->last_change == manifest->version) { if (tmp->last_change == manifest->version) {
prev = tmp; prev = tmp;
outfiles = g_list_prepend(outfiles, tmp); outfiles = g_list_prepend(outfiles, tmp);
break;
} }
} }
while (list) { for (; list; list = g_list_next(list)) {
file = list->data; file = list->data;
list = g_list_next(list);
// add any new file having a unique hash // add any new file having a unique hash
//FIXME: rename logic will be needed here //FIXME: rename logic will be needed here
@@ -305,6 +298,11 @@ static void submit_fullfile_tasks(GList *files)
file = item->data; file = item->data;
item = g_list_next(item); item = g_list_next(item);
/* do not push ghosted files */
if (file->is_ghosted) {
continue;
}
ret = g_thread_pool_push(threadpool, file, &err); ret = g_thread_pool_push(threadpool, file, &err);
if (ret == FALSE) { if (ret == FALSE) {
printf("GThread create_fullfile_task push error\n"); printf("GThread create_fullfile_task push error\n");
+2
View File
@@ -37,6 +37,7 @@ static char **groups = NULL;
static gsize groupcount = 0; static gsize groupcount = 0;
static unsigned int groupcursor = 0; static unsigned int groupcursor = 0;
#if 0
char *group_packages(char *group) char *group_packages(char *group)
{ {
assert(groupfile != NULL); assert(groupfile != NULL);
@@ -50,6 +51,7 @@ char *group_groups(char *group)
return g_key_file_get_value(groupfile, group, "groups", NULL); return g_key_file_get_value(groupfile, group, "groups", NULL);
} }
#endif
char *group_status(char *group) char *group_status(char *group)
{ {
+128 -113
View File
@@ -149,130 +149,117 @@ void concat_str_array(char **output, char *const argv[])
} }
} }
int system_argv_pipe(char *const lhscmd[], char *const rhscmd[])
{
return system_argv_pipe_fd(-1, -1, lhscmd, -1, -1, rhscmd);
}
int system_argv_pipe_fd(int lnewstdinfd, int lnewstderrfd, char *const lhscmd[],
int rnewstdoutfd, int rnewstderrfd, char *const rhscmd[])
{
pid_t monitorpid = fork();
if (monitorpid == -1) {
LOG(NULL, "Failed to create child process to monitor pipe between", "command %s and command %s", lhscmd[0], rhscmd[0]);
return -1;
} else if (monitorpid == 0) {
pipe_monitor(lnewstdinfd, lnewstderrfd, lhscmd, rnewstdoutfd, rnewstderrfd, rhscmd);
}
return wait_process_terminate(monitorpid);
}
void pipe_monitor(int lnewstdinfd, int lnewstderrfd, char *const lhscmd[],
int rnewstdoutfd, int rnewstderrfd, char *const rhscmd[])
{
int pipefd[2];
if (pipe(pipefd) == -1) {
LOG(NULL, "Failed to create a pipe between", "command %s and command %s", lhscmd[0], rhscmd[0]);
assert(0);
}
pid_t lhspid = system_argv_fd_nowait(lnewstdinfd, pipefd[1], lnewstderrfd, pipefd[0], lhscmd);
pid_t rhspid = system_argv_fd_nowait(pipefd[0], rnewstdoutfd, rnewstderrfd, pipefd[1], rhscmd);
if (close(pipefd[1]) == -1) {
LOG(NULL, "Could not close write end of pipe file descriptor", "%d", pipefd[1]);
assert(0);
}
if (close(pipefd[0]) == -1) {
LOG(NULL, "Could not close read end of pipe file descriptor", "%d", pipefd[0]);
assert(0);
}
int lhsresult = wait_process_terminate(lhspid);
int rhsresult = wait_process_terminate(rhspid);
exit(rhsresult != EXIT_SUCCESS ? rhsresult : lhsresult);
}
int system_argv(char *const argv[]) int system_argv(char *const argv[])
{ {
int child_exit_status; return system_argv_fd(-1, -1, -1, argv);
pid_t pid;
int status = -1;
pid = fork();
if (pid == 0) { /* child */
execvp(*argv, argv);
LOG(NULL, "This line must not be reached", "");
assert(0);
} else if (pid < 0) {
LOG(NULL, "Failed to fork a child process", "");
assert(0);
} else {
pid_t ws = waitpid(pid, &child_exit_status, 0);
if (ws == -1) {
LOG(NULL, "Failed to wait for child process", "");
assert(0);
}
if (WIFEXITED(child_exit_status)) {
status = WEXITSTATUS(child_exit_status);
} else {
LOG(NULL, "Child process didn't exit", "");
assert(0);
}
if (status != 0) {
char *cmdline = NULL;
concat_str_array(&cmdline, argv);
LOG(NULL, "Failed to run command:", "%s", cmdline);
free(cmdline);
}
}
return status;
} }
int system_argv_fd(char *const argv[], int newstdin, int newstdout, int newstderr) int system_argv_fd(int newstdinfd, int newstdoutfd, int newstderrfd, char *const cmd[])
{ {
int child_exit_status; pid_t cmdpid = system_argv_fd_nowait(newstdinfd, newstdoutfd, newstderrfd, -1, cmd);
pid_t pid; return wait_process_terminate(cmdpid);
int status = -1;
pid = fork();
if (pid == 0) { /* child */
if (newstdin >= 0) {
if (dup2(newstdin, STDIN_FILENO) == -1) {
LOG(NULL, "Could not redirect stdin", "");
assert(0);
}
close(newstdin);
}
if (newstdout >= 0) {
if (dup2(newstdout, STDOUT_FILENO) == -1) {
LOG(NULL, "Could not redirect stdout", "");
assert(0);
}
close(newstdout);
}
if (newstderr >= 0) {
if (dup2(newstderr, STDERR_FILENO) == -1) {
LOG(NULL, "Could not redirect stderr", "");
assert(0);
}
close(newstderr);
}
execvp(*argv, argv);
LOG(NULL, "This line must not be reached", "");
assert(0);
} else if (pid < 0) {
LOG(NULL, "Failed to fork a child process", "");
assert(0);
} else {
pid_t ws = waitpid(pid, &child_exit_status, 0);
if (ws == -1) {
LOG(NULL, "Failed to wait for child process", "");
assert(0);
}
if (WIFEXITED(child_exit_status)) {
status = WEXITSTATUS(child_exit_status);
} else {
LOG(NULL, "Child process didn't exit", "");
assert(0);
}
if (status != 0) {
char *cmdline = NULL;
concat_str_array(&cmdline, argv);
LOG(NULL, "Failed to run command:", "%s", cmdline);
free(cmdline);
}
}
return status;
} }
int system_argv_pipe(char *const argvp1[], int stdinp1, int stderrp1, pid_t system_argv_fd_nowait(int newstdinfd, int newstdoutfd, int newstderrfd, int closefd, char *const cmd[])
char *const argvp2[], int stdoutp2, int stderrp2)
{ {
int statusp2; pid_t cmdpid = fork();
int pipefd[2]; if (cmdpid == -1) {
LOG(NULL, "Failed to fork to execute command", "%s", cmd[0]);
assert(0);
} else if (cmdpid == 0) {
exec_cmd_fd(newstdinfd, newstdoutfd, newstderrfd, closefd, cmd);
}
return cmdpid;
}
if (pipe(pipefd)) { void exec_cmd_fd(int newstdinfd, int newstdoutfd, int newstderrfd, int closefd, char *const cmd[])
LOG(NULL, "Failed to create a pipe", ""); {
move_fd(newstdinfd, STDIN_FILENO);
move_fd(newstdoutfd, STDOUT_FILENO);
move_fd(newstderrfd, STDERR_FILENO);
if (closefd >= 0 && close(closefd) == -1) {
LOG(NULL, "Could not close file descriptor", "%d", closefd);
assert(0);
}
execvp(*cmd, cmd);
LOG(NULL, "Command", "%s failed", cmd[0]);
assert(0);
}
void move_fd(int oldfd, int newfd)
{
if (oldfd < 0 || newfd < 0 || oldfd == newfd) {
return;
}
if (dup2(oldfd, newfd) == -1) {
LOG(NULL, "Could not create duplicate file descriptor", "%d from %d", newfd, oldfd);
assert(0);
}
if (close(oldfd) == -1) {
LOG(NULL, "Could not close file descriptor", "%d", oldfd);
assert(0);
}
}
int wait_process_terminate(pid_t pid)
{
int status;
do {
if (waitpid(pid, &status, 0) == -1) {
LOG(NULL, "Failed to wait for PID", "%d", pid);
return -1; return -1;
} }
system_argv_fd(argvp1, stdinp1, pipefd[1], stderrp1); } while (!WIFEXITED(status) && !WIFSIGNALED(status));
close(pipefd[1]); // Exit statuses fall in the range of [0, 255]. Make signal statuses fall in a non-overlapping range starting with 256.
statusp2 = system_argv_fd(argvp2, pipefd[0], stdoutp2, stderrp2); if (WIFEXITED(status)) {
close(pipefd[0]); return WEXITSTATUS(status);
} else {
/* Returns the status of the failed process if any return 256 + WTERMSIG(status);
If both processes failed returns the status of first one */ }
return statusp2;
} }
void check_root(void) void check_root(void)
@@ -309,3 +296,31 @@ int num_threads(float scaling)
return result; return result;
} }
/* This function is called when configuration specifies a ban on debuginfo files
* from manifests. Returns true if the passed file path matches the src or lib
* debuginfo configuration. */
bool file_is_debuginfo(const char *path)
{
bool ret = false;
char *lib;
char *src;
lib = config_debuginfo_path("lib");
src = config_debuginfo_path("src");
if (lib && (strncmp(path, lib, strlen(lib)) == 0)) {
ret = true;
goto out;
}
if (src && (strncmp(path, src, strlen(src)) == 0)) {
ret = true;
goto out;
}
out:
free(lib);
free(src);
return ret;
}
+8 -1
View File
@@ -45,6 +45,7 @@ static void runtime_state_heuristics(struct file *file)
/* these are shipped directories that are not themselves state, /* these are shipped directories that are not themselves state,
* rather only their contents are state */ * rather only their contents are state */
if ((strcmp(file->filename, "/usr/src/debug") == 0) || if ((strcmp(file->filename, "/usr/src/debug") == 0) ||
(strcmp(file->filename, "/usr/src/kernel") == 0) ||
(strcmp(file->filename, "/dev") == 0) || (strcmp(file->filename, "/dev") == 0) ||
(strcmp(file->filename, "/home") == 0) || (strcmp(file->filename, "/home") == 0) ||
(strcmp(file->filename, "/proc") == 0) || (strcmp(file->filename, "/proc") == 0) ||
@@ -56,6 +57,12 @@ static void runtime_state_heuristics(struct file *file)
return; return;
} }
/* the contents of these directory are not state,
* but it belongs to a state directory */
if ((strncmp(file->filename, "/usr/src/kernel/", 16) == 0)) {
return;
}
/* the contents of these directory are state, ideally this never /* the contents of these directory are state, ideally this never
* triggers if our package builds are clean */ * triggers if our package builds are clean */
if ((strncmp(file->filename, "/dev/", 5) == 0) || if ((strncmp(file->filename, "/dev/", 5) == 0) ||
@@ -72,7 +79,7 @@ static void runtime_state_heuristics(struct file *file)
return; return;
} }
/* these are commonly added directories for user customizatio, /* these are commonly added directories for user customization,
* ideally this never triggers if our package builds are clean */ * ideally this never triggers if our package builds are clean */
if ((strncmp(file->filename, "/acct", 5) == 0) || if ((strncmp(file->filename, "/acct", 5) == 0) ||
(strncmp(file->filename, "/cache", 6) == 0) || (strncmp(file->filename, "/cache", 6) == 0) ||
-1
View File
@@ -25,7 +25,6 @@
#include <dirent.h> #include <dirent.h>
#include <errno.h> #include <errno.h>
#include <getopt.h> #include <getopt.h>
#include <getopt.h>
#include <glib.h> #include <glib.h>
#include <locale.h> #include <locale.h>
#include <stdio.h> #include <stdio.h>
+195 -148
View File
@@ -51,6 +51,10 @@ int file_sort_hash(gconstpointer a, gconstpointer b)
return memcmp(A->hash, B->hash, SWUPD_HASH_LEN - 1); return memcmp(A->hash, B->hash, SWUPD_HASH_LEN - 1);
} }
/* Standard sort compare function which sorts
* first on the version where the file changed and then
* on the name
*/
int file_sort_version(gconstpointer a, gconstpointer b) int file_sort_version(gconstpointer a, gconstpointer b)
{ {
struct file *A, *B; struct file *A, *B;
@@ -88,7 +92,7 @@ int file_sort_filename(gconstpointer a, gconstpointer b)
return 0; return 0;
} }
struct manifest *alloc_manifest(int version, char *component) struct manifest *alloc_manifest(int version, char *component, GList *actions)
{ {
struct manifest *manifest; struct manifest *manifest;
@@ -100,6 +104,7 @@ struct manifest *alloc_manifest(int version, char *component)
manifest->version = version; manifest->version = version;
manifest->component = strdup(component); manifest->component = strdup(component);
manifest->format = format; manifest->format = format;
manifest->actions = actions;
return manifest; return manifest;
} }
@@ -129,7 +134,7 @@ struct manifest *manifest_from_file(int version, char *component)
if (infile == NULL) { if (infile == NULL) {
LOG(NULL, "Cannot read manifest", "%s (%s)\n", filename, strerror(errno)); LOG(NULL, "Cannot read manifest", "%s (%s)\n", filename, strerror(errno));
free(filename); free(filename);
return alloc_manifest(version, component); return alloc_manifest(version, component, NULL);
} }
/* line 1: MANIFEST\t<version> */ /* line 1: MANIFEST\t<version> */
@@ -189,7 +194,7 @@ struct manifest *manifest_from_file(int version, char *component)
} }
} }
manifest = alloc_manifest(version, component); manifest = alloc_manifest(version, component, NULL);
manifest->format = format_number; manifest->format = format_number;
manifest->prevversion = previous; manifest->prevversion = previous;
manifest->includes = includes; manifest->includes = includes;
@@ -235,10 +240,22 @@ struct manifest *manifest_from_file(int version, char *component)
assert(0); /* unknown file type */ assert(0); /* unknown file type */
} }
if (c[1] == 'd') { switch (c[1]) {
case 'd':
/* file is deleted */
file->is_deleted = 1; file->is_deleted = 1;
} else if (c[1] != '.') { break;
assert(0); /* unknown deleted status */ case 'g':
/* file is ghosted */
file->is_ghosted = 1;
break;
case '.':
/* no flag at this index */
break;
default:
/* unknown deleted status */
LOG(NULL, "Invalid flag at index 1", "%s", c[1]);
assert(0);
} }
if (c[2] == 'C') { if (c[2] == 'C') {
@@ -306,6 +323,21 @@ struct manifest *manifest_from_file(int version, char *component)
return manifest; return manifest;
} }
/* do not set peer for the case where the old file was deleted and the new file
* was added back. This peer is normally set when the names match up and this
* triggers an attempt at creating a delta during pack creation. For this case
* where the old file is deleted or ghosted and is brought back we do not want
* this to be attempted, as the old file will not be present when trying to
* create the delta.
*
* We can go even further and not set peers for files when the old one did not
* exist (deleted or ghosted). It is not necessary to check the status of the
* new file */
bool should_have_peer(struct file *file1, struct file *file2)
{
return (!(file1->is_deleted || file1->is_ghosted));
}
void free_manifest(struct manifest *manifest) void free_manifest(struct manifest *manifest)
{ {
struct file *file; struct file *file;
@@ -323,6 +355,62 @@ void free_manifest(struct manifest *manifest)
free(manifest); free(manifest);
} }
static bool same_file_contents(struct file *file1, struct file *file2)
{
return (file1->is_dir == file2->is_dir &&
file1->is_link == file2->is_link &&
file1->is_deleted == file2->is_deleted &&
file1->is_file == file2->is_file &&
file1->is_config == file2->is_config &&
file1->is_state == file2->is_state &&
file1->is_boot == file2->is_boot &&
hash_compare(file1->hash, file2->hash));
}
/*
* Add a deleted file entry for it in the target list. However, since we're
* currently walking the list we HAVE to prepend the entry. Calling function
* should track to sort at the end.
*/
static void add_deleted_file(struct file *source, struct manifest *manifest)
{
struct file *deleted;
deleted = calloc(1, sizeof(struct file));
if (deleted == NULL) {
assert(0);
}
deleted->filename = strdup(source->filename);
hash_set_zeros(deleted->hash);
deleted->is_deleted = 1;
deleted->is_config = source->is_config;
deleted->is_state = source->is_state;
deleted->is_boot = source->is_boot;
/* ghost deleted boot files */
deleted->is_ghosted = source->is_ghosted | source->is_boot & source->is_deleted;
if (deleted->is_ghosted || source->is_deleted) {
/* if the new file is ghosted or the file was deleted, preserve
* hash (all zeros if source->is_deleted) and rename status */
deleted->is_rename = source->is_rename;
hash_assign(source->hash, deleted->hash);
}
/* for deleted files last_change remains the same.
* otherwise last change is now */
deleted->last_change = source->is_deleted ? source->last_change : manifest->version;
deleted->peer = source;
source->peer = deleted;
/* if we are adding a deleted file we are walking the old and new
* manifest files in-sync. we need to prepend this file in order to
* not process it twice. This is why it is important for the calling
* function to sort the list again at the end */
manifest->files = g_list_prepend(manifest->files, deleted);
manifest->count++;
}
/* /*
backfill the "last changed" of each file in a manifest backfill the "last changed" of each file in a manifest
by comparing the hash against the same file in the previous manifest by comparing the hash against the same file in the previous manifest
@@ -337,7 +425,6 @@ int match_manifests(struct manifest *m1, struct manifest *m2)
struct file *file1, *file2; struct file *file1, *file2;
int must_sort = 0; int must_sort = 0;
int count = 0; int count = 0;
int first = 1;
if (!m1) { if (!m1) {
printf("Matching manifests up failed: No old manifest!\n"); printf("Matching manifests up failed: No old manifest!\n");
@@ -365,158 +452,72 @@ int match_manifests(struct manifest *m1, struct manifest *m2)
ret = strcmp(file1->filename, file2->filename); ret = strcmp(file1->filename, file2->filename);
if (ret == 0) { if (ret == 0) {
if (file1->is_deleted && file2->is_deleted && file1->is_rename) { /* file is present in both manifests */
file2->is_rename = file1->is_rename; if (same_file_contents(file1, file2) && file1->last_change >= minversion) {
hash_assign(file1->hash, file2->hash); /* file did not change */
}
if (hash_compare(file1->hash, file2->hash) &&
file1->is_dir == file2->is_dir &&
file1->is_link == file2->is_link &&
file1->is_deleted == file2->is_deleted &&
file1->is_file == file2->is_file &&
file1->is_config == file2->is_config &&
file1->is_state == file2->is_state &&
file1->is_boot == file2->is_boot &&
file1->last_change >= minversion) {
file2->last_change = file1->last_change; file2->last_change = file1->last_change;
file2->is_rename = file1->is_rename; file2->is_rename = file1->is_rename;
} else { } else {
/* file changed */
account_changed_file(); account_changed_file();
if (first) {
LOG(file1, "file changed", "");
first = 0;
}
count++; count++;
} }
if (!file1->is_deleted || file2->is_deleted) { /* check if these files should be peers */
if (should_have_peer(file1, file2)) {
file1->peer = file2; file1->peer = file2;
file2->peer = file1; file2->peer = file1;
} }
/* there was a match, advance both lists */
list1 = g_list_next(list1); list1 = g_list_next(list1);
list2 = g_list_next(list2); list2 = g_list_next(list2);
continue; } else if (ret < 0) {
} /* file1 was deleted, create entry for deleted file */
if (first) { add_deleted_file(file1, m2);
LOG(file1, "file added? ", "(file2 is %s)", file2->filename);
first = 0;
}
if (ret < 0) {
struct file *file3;
/*
* if we get here, file1 got deleted... what we must do
* is add a file entry for it in the target list.
* However, since we're currently walking the list we
* HAVE to prepend the entry.. and mark for sort at the
* end.
*/
file3 = calloc(1, sizeof(struct file));
if (file3 == NULL) {
assert(0);
}
file3->filename = strdup(file1->filename);
hash_set_zeros(file3->hash);
file3->is_deleted = 1;
file3->is_config = file1->is_config;
file3->is_state = file1->is_state;
file3->is_boot = file1->is_boot;
if (!file1->is_deleted) {
file3->last_change = m2->version;
} else {
file3->last_change = file1->last_change;
file3->is_rename = file1->is_rename;
hash_assign(file1->hash, file3->hash);
}
file3->peer = file1;
file1->peer = file3;
list1 = g_list_next(list1);
m2->files = g_list_prepend(m2->files, file3);
m2->count++;
if (!file1->is_deleted) { if (!file1->is_deleted) {
account_deleted_file(); account_deleted_file();
count++; count++;
if (first) {
LOG(file1, "file got deleted", "");
first = 0;
}
} }
must_sort = 1; must_sort = 1;
continue; /* advance list1 for next file */
} list1 = g_list_next(list1);
} else {
/* if we get here, ret is > 0, which means this is a new file added */ /* if we get here, ret is > 0, which means this is a new file added */
/* all we do is advance the pointer */ /* all we do is advance the pointer */
account_new_file(); account_new_file();
list2 = g_list_next(list2);
count++; count++;
/* advance list2 to check against same file in list1 */
list2 = g_list_next(list2);
}
} }
/* now deal with the tail ends */ /* now deal with the tail ends */
while (list1) { /* deleted files from list1 */
for (; list1; list1 = g_list_next(list1)) {
file1 = list1->data; file1 = list1->data;
add_deleted_file(file1, m2);
struct file *file3;
if (first) {
LOG(file1, "file changed tail", "");
first = 0;
}
count++;
/*
* if we get here, file1 got deleted... what we must do is add
* a file entry for it in the target list. However, since
* we're currently walking the list we HAVE to prepend the
* entry.. and mark for sort at the end.
*/
file3 = calloc(1, sizeof(struct file));
if (file3 == NULL) {
assert(0);
}
file3->filename = strdup(file1->filename);
hash_set_zeros(file3->hash);
file3->is_deleted = 1;
file3->is_config = file1->is_config;
file3->is_state = file1->is_state;
file3->is_boot = file1->is_boot;
if (!file1->is_deleted) {
file3->last_change = m2->version;
} else {
file3->last_change = file1->last_change;
file3->is_rename = file1->is_rename;
hash_assign(file1->hash, file3->hash);
}
file3->peer = file1;
file1->peer = file3;
list1 = g_list_next(list1);
m2->files = g_list_prepend(m2->files, file3);
m2->count++;
if (!file1->is_deleted) { if (!file1->is_deleted) {
account_deleted_file(); account_deleted_file();
count++;
} }
must_sort = 1; must_sort = 1;
} }
while (list2) { /* added files from list2 */
for (; list2; list2 = g_list_next(list2)) {
account_new_file(); account_new_file();
list2 = g_list_next(list2);
if (first) {
first = 0;
}
count++; count++;
} }
/* finally, sort the list if necessary */
if (must_sort) { if (must_sort) {
m2->files = g_list_sort(m2->files, file_sort_filename); m2->files = g_list_sort(m2->files, file_sort_filename);
} }
/* returned count of changed files */
return count; return count;
} }
@@ -653,6 +654,10 @@ char *file_type_to_string(struct file *file)
type[1] = 'd'; type[1] = 'd';
} }
if (file->is_ghosted) {
type[1] = 'g';
}
if (file->is_config) { if (file->is_config) {
type[2] = 'C'; type[2] = 'C';
} }
@@ -670,19 +675,29 @@ char *file_type_to_string(struct file *file)
return type; return type;
} }
/* Calculate the contentsize for the manifest based on file sizes.
*
* This should calculate the files uniquely included in this manifest, but none
* of its submanifests, which will allow calculation of sizes of all bundles on
* a system by adding all manifest->contentsizes of installed bundles.
*
* However, if two bundles not in the same include chain have overlapping
* content, summing the include chain of each bundle in the client will result
* in an over-estimation of the total size on the system. The more content is
* shared, the higher the over-estimation. In reality this overlap will not be
* large, but it is currently impossible to calculate the exact installed size
* using just the contentsize.
*/
static void compute_content_size(struct manifest *manifest) static void compute_content_size(struct manifest *manifest)
{ {
/* FIXME: this is a temporary implementation based on worst case */
GList *list; GList *list;
struct file *file; struct file *file;
struct manifest *submanifest;
list = g_list_first(manifest->files); list = g_list_first(manifest->files);
while (list) { while (list) {
file = list->data; file = list->data;
list = g_list_next(list); list = g_list_next(list);
if (!file->is_deleted && (file->last_change == manifest->version)) { if (!file->is_deleted) {
if (file->is_file) { if (file->is_file) {
manifest->contentsize += file->stat.st_size; manifest->contentsize += file->stat.st_size;
} else if (file->is_link) { } else if (file->is_link) {
@@ -692,17 +707,6 @@ static void compute_content_size(struct manifest *manifest)
} }
} }
} }
list = g_list_first(manifest->submanifests);
while (list) {
submanifest = list->data;
list = g_list_next(list);
/* Do not take into account groups not included in download content */
if (create_download_content_for_group(submanifest->component)) {
manifest->contentsize += submanifest->contentsize;
}
}
} }
/* Returns 0 == success, -1 == failure */ /* Returns 0 == success, -1 == failure */
@@ -710,6 +714,7 @@ static int write_manifest_plain(struct manifest *manifest)
{ {
GList *includes; GList *includes;
GList *list; GList *list;
GList *actions;
struct file *file; struct file *file;
FILE *out = NULL; FILE *out = NULL;
char *base = NULL, *dir; char *base = NULL, *dir;
@@ -759,6 +764,14 @@ static int write_manifest_plain(struct manifest *manifest)
includes = g_list_next(includes); includes = g_list_next(includes);
fprintf(out, "includes:\t%s\n", sub->component); fprintf(out, "includes:\t%s\n", sub->component);
} }
actions = manifest->actions;
while (actions) {
char *action = actions->data;
fprintf(out, "actions:\t%s\n", action);
actions = g_list_next(actions);
}
fprintf(out, "\n"); fprintf(out, "\n");
list = g_list_first(manifest->files); list = g_list_first(manifest->files);
@@ -949,13 +962,12 @@ bool changed_includes(struct manifest *old, struct manifest *new)
return false; return false;
} }
/* For a format bump, it's convenient to remove deleted files from manifests /* Remove files deprecated in this version as identified by the compfunc
* that last changed prior to the format bump, since they are no longer * function. This function can be used to remove old deleted files over a format
* considered for deletion as part of an update. * bump and remove deprecated ghosted files.
*
* Note: this function should be called after match_manifests(). * Note: this function should be called after match_manifests().
*/ */
int remove_old_deleted_files(struct manifest *m1, struct manifest *m2) int remove_deprecated_files(struct manifest *m1, struct manifest *m2, bool (*compfunc)(struct file *file1, struct file *file2))
{ {
GList *list1, *list2; GList *list1, *list2;
struct file *file1, *file2; struct file *file1, *file2;
@@ -971,11 +983,6 @@ int remove_old_deleted_files(struct manifest *m1, struct manifest *m2)
return -1; return -1;
} }
/* This is the common case, so bail early, reporting no deletions */
if (m1->format == m2->format) {
return 0;
}
/* At this point, the manifest formats mismatch, and it's assumed that /* At this point, the manifest formats mismatch, and it's assumed that
* m1 is the old manifest, and m2 is the new. * m1 is the old manifest, and m2 is the new.
*/ */
@@ -993,7 +1000,9 @@ int remove_old_deleted_files(struct manifest *m1, struct manifest *m2)
ret = strcmp(file1->filename, file2->filename); ret = strcmp(file1->filename, file2->filename);
if (ret == 0) { if (ret == 0) {
if (file1->is_deleted && file2->is_deleted) { /* use the comparison function passed in to determine if this file
* should be removed */
if (compfunc(file1, file2)) {
GList *to_delete = list2; GList *to_delete = list2;
list1 = g_list_next(list1); list1 = g_list_next(list1);
list2 = g_list_next(list2); list2 = g_list_next(list2);
@@ -1014,6 +1023,36 @@ int remove_old_deleted_files(struct manifest *m1, struct manifest *m2)
return count; return count;
} }
/* Performs a final link of renames in the manifest to catch any renames from
* old versions and then removes any orphaned renames */
void clean_renames(struct manifest *manifest)
{
GList *list;
struct file *file;
/* make sure all renames are linked, this is necessary for renames
* from old manifests that carry over to the current one */
final_link(manifest->files);
list = g_list_first(manifest->files);
while (list) {
file = list->data;
/* if a file is marked as a rename but has lost its rename_peer
* it needs to be cleaned up */
if (file->is_rename && !file->rename_peer) {
/* no longer a rename */
file->is_rename = 0;
/* if the file is marked as deleted and renamed it is a
* renamed-from file. Mark these as deleted now */
if (file->is_deleted) {
hash_set_zeros(file->hash);
}
}
list = g_list_next(list);
}
}
/* Conditionally remove some things from a manifest. /* Conditionally remove some things from a manifest.
* Returns > 0 when the pruned manifest has new files. * Returns > 0 when the pruned manifest has new files.
* Returns 0 when the pruned manifest no longer has new files. * Returns 0 when the pruned manifest no longer has new files.
@@ -1038,8 +1077,16 @@ int prune_manifest(struct manifest *manifest)
manifest->files = g_list_delete_link(manifest->files, list); manifest->files = g_list_delete_link(manifest->files, list);
manifest->count--; manifest->count--;
} else if (file->is_boot && file->is_deleted) { } else if (file->is_boot && file->is_deleted) {
// only expose the current best boot files, a client side entity can manage /boot's actual contents /* mark boot files that are going away as ghosted, these will be
// LOG(file, "Skipping deleted boot file in manifest write", "component %s", manifest->component); * cleaned up with the next update */
file->is_deleted = 0;
file->is_ghosted = 1;
} else if (config_ban_debuginfo() && file_is_debuginfo(file->filename)) {
/* The configuration option to ban debuginfo from the manifests was
* set in server.ini via the [Debuginfo][banned] option. Although
* debuginfo additions are banned via analyze_fs, prune it here
* to insure mistakenly included debuginfo from old versions is
* removed from the manifests. */
manifest->files = g_list_delete_link(manifest->files, list); manifest->files = g_list_delete_link(manifest->files, list);
manifest->count--; manifest->count--;
} }
+18 -8
View File
@@ -58,11 +58,11 @@ static void empty_pack_stage(int full, int from_version, int to_version, char *m
// (re)create module/version/{delta,staged} // (re)create module/version/{delta,staged}
string_or_die(&path, "%s/%s/%i_to_%i/delta", packstage_dir, module, string_or_die(&path, "%s/%s/%i_to_%i/delta", packstage_dir, module,
from_version, to_version); from_version, to_version);
g_mkdir_with_parents(path, S_IRWXU | S_IRWXG); g_mkdir_with_parents(path, S_IRWXU);
free(path); free(path);
string_or_die(&path, "%s/%s/%i_to_%i/staged", packstage_dir, module, string_or_die(&path, "%s/%s/%i_to_%i/staged", packstage_dir, module,
from_version, to_version); from_version, to_version);
g_mkdir_with_parents(path, S_IRWXU | S_IRWXG); g_mkdir_with_parents(path, S_IRWXU);
free(path); free(path);
} }
} }
@@ -79,7 +79,7 @@ static void explode_pack_stage(int from_version, int to_version, char *module)
string_or_die(&path, "%s/%s/%i_to_%i/staged", packstage_dir, module, string_or_die(&path, "%s/%s/%i_to_%i/staged", packstage_dir, module,
from_version, to_version); from_version, to_version);
g_mkdir_with_parents(path, S_IRWXU | S_IRWXG); g_mkdir_with_parents(path, S_IRWXU);
dir = opendir(path); dir = opendir(path);
if (!dir) { if (!dir) {
fprintf(stderr, "There are problems accessing %s, exiting\n", path); fprintf(stderr, "There are problems accessing %s, exiting\n", path);
@@ -138,13 +138,14 @@ static void prepare_pack(struct packdata *pack)
return; return;
} }
/* read in manifest from file */
pack->end_manifest = manifest_from_file(pack->to, pack->module); pack->end_manifest = manifest_from_file(pack->to, pack->module);
/* wipe any old packs (failed) and re-create pack directory structure */
empty_pack_stage(0, pack->from, pack->to, pack->module); empty_pack_stage(0, pack->from, pack->to, pack->module);
/* match up old and new manifests */
match_manifests(manifest, pack->end_manifest); match_manifests(manifest, pack->end_manifest);
/* link renames together */
link_renames(pack->end_manifest->files, manifest); link_renames(pack->end_manifest->files, pack->to);
} }
static void make_pack_full_files(struct packdata *pack) static void make_pack_full_files(struct packdata *pack)
@@ -161,7 +162,11 @@ static void make_pack_full_files(struct packdata *pack)
while (item) { while (item) {
file = item->data; file = item->data;
item = g_list_next(item); item = g_list_next(item);
if ((!file->peer || file->peer->is_deleted) && !file->is_deleted && !file->rename_peer) { /* only create full files if renames or deltas are not appropriate */
if ((!file->peer || file->peer->is_deleted || file->peer->is_ghosted) &&
!file->is_deleted && /* no full-files for deletes */
!file->is_ghosted && /* no full-files for ghosts */
!file->rename_peer) { /* no full-files for renames */
char *from, *to; char *from, *to;
char *fullfrom, *fullto; char *fullfrom, *fullto;
@@ -248,6 +253,8 @@ static GList *consolidate_packs_delta_files(GList *files, struct packdata *pack)
file = item->data; file = item->data;
item = g_list_next(item); item = g_list_next(item);
/* skip old files, files without a peer, and files that are not
* files, directories, or links */
if ((file->last_change <= pack->from) || if ((file->last_change <= pack->from) ||
(!file->peer) || (!file->peer) ||
(!file->is_file && !file->is_dir && !file->is_link)) { (!file->is_file && !file->is_dir && !file->is_link)) {
@@ -257,7 +264,10 @@ static GList *consolidate_packs_delta_files(GList *files, struct packdata *pack)
string_or_die(&from, "%s/%i/delta/%i-%i-%s-%s", staging_dir, file->last_change, string_or_die(&from, "%s/%i/delta/%i-%i-%s-%s", staging_dir, file->last_change,
file->peer->last_change, file->last_change, file->peer->hash, file->hash); file->peer->last_change, file->last_change, file->peer->hash, file->hash);
/* check for existence */
ret = stat(from, &stat_delta); ret = stat(from, &stat_delta);
/* only add if delta does not already exist and the file is not
* in files */
if (ret && !find_file_in_list(files, file)) { if (ret && !find_file_in_list(files, file)) {
files = g_list_prepend(files, file); files = g_list_prepend(files, file);
} }
+246 -140
View File
@@ -21,6 +21,9 @@
* *
*/ */
/* Rename detection and support.
*/
#define _GNU_SOURCE #define _GNU_SOURCE
#include <assert.h> #include <assert.h>
#include <ctype.h> #include <ctype.h>
@@ -37,8 +40,55 @@
#include <magic.h> #include <magic.h>
static magic_t mcookie; static bool samefiletype(char *t1, char *t2)
{
if (t1 && (t1 == t2) && *t1) {
return true;
}
return false;
}
/* For an elf binary, we get the BuildID in it, which
* is essentially a hash of the original loaded sections
* and hence is unique. This hardly makes for a 'type'
*/
static char *getmagic(char *filename)
{
static magic_t mcookie;
static GStringChunk *typestore;
char *c2;
char *c1;
if (mcookie == NULL) {
mcookie = magic_open(MAGIC_NO_CHECK_COMPRESS);
magic_load(mcookie, NULL);
typestore = g_string_chunk_new(200);
}
c1 = (char *)magic_file(mcookie, filename);
if (!c1) {
LOG(NULL, "Cannot find file type", "%s", filename);
c1 = "";
}
c1 = strdup(c1);
c2 = strstr(c1, ", BuildID[");
if (c2) {
*c2 = 0;
}
c2 = strstr(c1, "not stripped");
if (c2) {
*c2 = 0;
}
c2 = strstr(c1, "stripped");
if (c2) {
*c2 = 0;
}
c2 = g_string_chunk_insert_const(typestore, c1);
free(c1);
return c2;
}
/* Assign a score roughly in the range -100 to 1000 to express how similar
* two files are.
*/
double rename_score(struct file *old, struct file *new) double rename_score(struct file *old, struct file *new)
{ {
double score = 0.0; double score = 0.0;
@@ -50,6 +100,16 @@ double rename_score(struct file *old, struct file *new)
score += 400; score += 400;
} }
/* If the files are smaller than about 200 bytes then even a
* single byte change using bsdiff is going to work out as
* bigger than just shipping the new file, so stop if they are
* not the same. No point in running up bsdiff just for the
* sake of it.
*/
if (new->stat.st_size < BSDIFFSIZE) {
return -99.0;
}
/* points for being in the same directory */ /* points for being in the same directory */
if (strcmp(old->dirname, new->dirname) == 0) { if (strcmp(old->dirname, new->dirname) == 0) {
score += 10; score += 10;
@@ -86,12 +146,14 @@ double rename_score(struct file *old, struct file *new)
in--; in--;
} }
#if 0
/* if both start with /boot/vmlinuz give it a boost; this is a local hack due to vmlinuz being very short */ /* if both start with /boot/vmlinuz give it a boost; this is a local hack due to vmlinuz being very short */
if (strncmp(old->filename, "/boot/vmlinuz", 13) == 0 && strncmp(new->filename, "/boot/vmlinuz", 13) == 0) { if (strncmp(old->filename, "/boot/vmlinuz", 13) == 0 && strncmp(new->filename, "/boot/vmlinuz", 13) == 0) {
score += 80; score += 80;
} }
/* if ELF, points for sharing the same soname to the first dot */ /* if ELF, points for sharing the same soname to the first dot */
#endif
/* negative points for not being within 25%+/-1Kb of the same file size */ /* negative points for not being within 25%+/-1Kb of the same file size */
if (old->stat.st_size > ((new->stat.st_size * 1.25) + 1024)) { if (old->stat.st_size > ((new->stat.st_size * 1.25) + 1024)) {
@@ -115,66 +177,40 @@ double rename_score(struct file *old, struct file *new)
} }
/* negative points for not having the same 'file' type */ /* negative points for not having the same 'file' type */
if (old->filetype && new->filetype && strcmp(old->filetype, new->filetype) != 0) { if (!samefiletype(old->filetype, new->filetype)) {
score -= 60; score -= 60;
} }
return score; return score;
} }
static void precompute_file_data(struct manifest *manifest, struct file *file, int old_rename, GList *last_versions_list) static void precompute_file_data(int version, const char *component, struct file *file)
{ {
GList *item;
char *c1, *c2; char *c1, *c2;
char *filename = NULL; char *filename = NULL;
int last_change;
struct stat buf;
assert(file);
/* fill in the filename-minus-the-numbers field */ /* fill in the filename-minus-the-numbers field */
file->alpha_only_filename = calloc(strlen(file->filename) + 1, sizeof(char)); file->alpha_only_filename = calloc(strlen(file->filename) + 1, sizeof(char));
c1 = file->filename; c1 = file->filename;
c2 = file->alpha_only_filename; c2 = file->alpha_only_filename;
while (*c1) { if (c2) {
while (*c1 && !isalpha(*c1)) for (; *c1; c1++) {
c1++; if (isalpha(*c1)) { /* Only copy letters */
if (!*c1) { *c2++ = *c1;
break;
}
if (c2 == NULL) {
break;
}
*c2 = *c1;
c1++;
c2++;
}
if (manifest) {
string_or_die(&filename, "%s/%i/%s/%s", image_dir, manifest->version, manifest->component, file->filename);
} else if (old_rename) {
item = g_list_first(last_versions_list);
while (item) {
last_change = GPOINTER_TO_INT(item->data);
item = g_list_next(item);
free(filename);
string_or_die(&filename, "%s/%i/full/%s", image_dir, last_change, file->filename);
if (!lstat(filename, &buf)) {
break;
} }
} }
} else { /* alpha_only_filename is NUL terminated by calloc */
string_or_die(&filename, "%s/%i/full/%s", image_dir, file->last_change, file->filename);
} }
string_or_die(&filename, "%s/%i/%s/%s", image_dir, version, component, file->filename);
/* make sure file->stat.st_size is valid */ /* make sure file->stat.st_size is valid */
if (file->stat.st_size == 0) { if (file->stat.st_size == 0) {
int ret; int ret;
struct stat buf;
if (filename == NULL) { assert(filename);
printf("filename is null...impossible to stat\n");
assert(0);
}
ret = lstat(filename, &buf); ret = lstat(filename, &buf);
if (!ret) { if (!ret) {
file->stat.st_size = buf.st_size; file->stat.st_size = buf.st_size;
@@ -182,27 +218,19 @@ static void precompute_file_data(struct manifest *manifest, struct file *file, i
printf("Stat failure on %s\n", filename); printf("Stat failure on %s\n", filename);
} }
} }
if (file->stat.st_size < BSDIFFSIZE) {
c1 = (char *)magic_file(mcookie, filename); /* thing is too small, always will be regenerated
if (c1) { * so no point in trying to figure out what kind
char *c2; * of file it is
file->filetype = strdup(c1); */
c2 = strstr(file->filetype, "not stripped");
if (c2) {
*c2 = 0;
}
c2 = strstr(file->filetype, "stripped");
if (c2) {
*c2 = 0;
}
} else { } else {
LOG(file, "Cannot find file type", "%s", filename); file->filetype = getmagic(filename);
} }
free(filename); free(filename);
file->basename = strdup(basename(file->filename)); file->basename = g_path_get_basename(file->filename);
file->dirname = strdup(dirname(file->filename)); file->dirname = g_path_get_dirname(file->filename);
} }
int file_sort_score(gconstpointer a, gconstpointer b) int file_sort_score(gconstpointer a, gconstpointer b)
@@ -222,6 +250,10 @@ int file_sort_score(gconstpointer a, gconstpointer b)
return 0; return 0;
} }
/* compare file to each deleted file.
* set file->rename_peer to best matched deleted file
* set file->rename_score to the score
*/
static void score_file(GList *deleted_files, struct file *file) static void score_file(GList *deleted_files, struct file *file)
{ {
GList *list2; GList *list2;
@@ -246,61 +278,107 @@ static void score_file(GList *deleted_files, struct file *file)
} }
} }
void rename_detection(struct manifest *manifest, int last_change, GList *last_versions_list) /* delete the first element of the list and return the new head */
static GList *del_first(GList *list)
{ {
GList *new_files = NULL; /* The first list is the pointer to the list, the second is
GList *deleted_files = NULL; * the pointer to what to delete */
return g_list_delete_link(list, list);
GList *list; }
struct file *file;
int old_rename = 0;
if (last_change != manifest->version) {
old_rename = 1;
}
if (mcookie == NULL) {
mcookie = magic_open(MAGIC_NO_CHECK_COMPRESS);
magic_load(mcookie, NULL);
}
/* Take a list, return a new list where the filter function returns true */
static GList *new_filtered_list(GList *list, int version, int (*f)(struct file *file, int version))
{
/* make a list of new files, no peer */ /* make a list of new files, no peer */
list = g_list_first(manifest->files); GList *newlist = NULL;
while (list) { list = g_list_first(list);
file = list->data; for (; list; list = g_list_next(list)) {
list = g_list_next(list); struct file *file = list->data;
if ((file->last_change != manifest->version) || if (f(file, version)) {
newlist = g_list_prepend(newlist, file);
}
}
return newlist;
}
static int renamed_file_p(struct file *file, int unused __attribute__((unused)))
{
return file->is_rename;
}
/* Return a new list of renamed files */
static GList *new_list_renamed_files(GList *infiles)
{
return new_filtered_list(infiles, 0, renamed_file_p);
}
/* Predicate that returns true if this is a new file in the stated version */
static int new_file_p(struct file *file, int version)
{
if ((file->last_change != version) ||
(file->is_deleted) || (file->is_deleted) ||
(!file->is_file) || (!file->is_file) ||
(file->peer)) { (file->peer)) {
continue; return 0;
} }
return 1;
}
new_files = g_list_prepend(new_files, file); /* return a new list of the new files */
precompute_file_data(manifest, file, old_rename, last_versions_list); static GList *list_new_files(struct manifest *manifest)
{
GList *list = new_filtered_list(manifest->files, manifest->version, new_file_p);
/* call precompute_file_data for each file on list, return the list */
GList *ret = list;
for (list = g_list_first(list); list; list = g_list_next(list)) {
struct file *file = list->data;
precompute_file_data(manifest->version, manifest->component, file);
} }
return ret;
}
/* if there are no new files, we're not having any renames -- early exit */ static int deleted_p(struct file *file, int version)
{
if ((!file->is_deleted) ||
(!file->peer) ||
(file->last_change != version) ||
(file->peer->is_dir || file->peer->is_link)) {
return 0;
}
return 1;
}
static GList *list_deleted_files(struct manifest *manifest)
{
GList *list = new_filtered_list(manifest->files, manifest->version, deleted_p);
GList *ret = list;
/* call precompute_file_data for each peer of file on list */
for (list = g_list_first(list); list; list = g_list_next(list)) {
struct file *file = list->data;
struct file *peer = file->peer;
/* Need to get things from the /full/ as we do not know
* which component may be coming from? */
precompute_file_data(peer->last_change, "full", peer);
}
return ret;
}
void rename_detection(struct manifest *manifest)
{
GList *new_files;
GList *deleted_files;
GList *list;
struct file *file;
new_files = list_new_files(manifest);
/* no new files --> no renames -- early exit */
if (!new_files) { if (!new_files) {
LOG(NULL, "No new files, no rename detection", "%s", manifest->component); LOG(NULL, "No new files, no rename detection", "%s", manifest->component);
return; return;
} }
/* make a list of newly deleted files that have a peer */
list = g_list_first(manifest->files); deleted_files = list_deleted_files(manifest);
while (list) {
file = list->data;
list = g_list_next(list);
if ((!file->is_deleted) ||
(!file->peer) ||
(file->last_change != last_change) ||
(file->peer->is_dir || file->peer->is_link)) {
continue;
}
deleted_files = g_list_prepend(deleted_files, file);
precompute_file_data(NULL, file->peer, old_rename, last_versions_list);
}
/* nothing got deleted --> no renames --> early exit */ /* nothing got deleted --> no renames --> early exit */
if (!deleted_files) { if (!deleted_files) {
@@ -309,7 +387,8 @@ void rename_detection(struct manifest *manifest, int last_change, GList *last_ve
return; return;
} }
/* for each new file, find the deleted file with the highest score, and store the score */ /* for each new file, find the deleted file with the highest score,
* store it in file->rename_peer and store the score */
list = g_list_first(new_files); list = g_list_first(new_files);
while (list) { while (list) {
file = list->data; file = list->data;
@@ -317,20 +396,24 @@ void rename_detection(struct manifest *manifest, int last_change, GList *last_ve
score_file(deleted_files, file); score_file(deleted_files, file);
} }
redo:
/* sort all new files by score */ /* sort all new files by score */
/* walk the sorted score list.
* pick the top score,
* check if the score is still valid,
* if not, recompute the score and resort
* This is probably an O(n^3).
*/
new_files = g_list_sort(new_files, file_sort_score); new_files = g_list_sort(new_files, file_sort_score);
/* pick the top score, check if the score is still valid, if not, recompute the score and resort */ for (; new_files; new_files = del_first(new_files)) {
while (new_files) {
file = new_files->data; file = new_files->data;
if (file->rename_peer == NULL) {
if (file->rename_score < 15.0 || file->rename_peer == NULL) { continue;
new_files = g_list_delete_link(new_files, new_files);
if (file->rename_peer) {
LOG(NULL, "Rename not done due to insufficient high score", "%s -> %s score %4.1f", file->rename_peer->filename, file->filename, file->rename_score);
} }
if (file->rename_score < 15.0) {
LOG(NULL, "Rename not done due to insufficient high score", "%s -> %s score %4.1f", file->rename_peer->filename, file->filename, file->rename_score);
continue; continue;
} }
@@ -338,10 +421,8 @@ void rename_detection(struct manifest *manifest, int last_change, GList *last_ve
/* the candidate peer got already taken by another file! */ /* the candidate peer got already taken by another file! */
LOG(NULL, "Rename not done due to target already taken", "%s -> %s score %4.1f", file->rename_peer->filename, file->filename, file->rename_score); LOG(NULL, "Rename not done due to target already taken", "%s -> %s score %4.1f", file->rename_peer->filename, file->filename, file->rename_score);
file->rename_peer = NULL; file->rename_peer = NULL;
file->rename_score = -100;
score_file(deleted_files, file); score_file(deleted_files, file);
new_files = g_list_sort(new_files, file_sort_score); goto redo;
continue;
} }
/* if valid and score is high enough, make the link by setting the flag and storing the hash */ /* if valid and score is high enough, make the link by setting the flag and storing the hash */
@@ -349,13 +430,14 @@ void rename_detection(struct manifest *manifest, int last_change, GList *last_ve
LOG(NULL, "Rename detected!", "%s -> %s score %4.1f", file->rename_peer->filename, file->filename, file->rename_score); LOG(NULL, "Rename detected!", "%s -> %s score %4.1f", file->rename_peer->filename, file->filename, file->rename_score);
file->rename_peer->rename_peer = file; file->rename_peer->rename_peer = file;
/* must delete the file from the deleted list */ /* must remove the file from the deleted list */
deleted_files = g_list_remove(deleted_files, file->rename_peer); deleted_files = g_list_remove(deleted_files, file->rename_peer);
hash_assign(file->hash, file->rename_peer->hash); hash_assign(file->hash, file->rename_peer->hash);
file->is_rename = 1; file->is_rename = 1;
file->rename_peer->is_rename = 1; file->rename_peer->is_rename = 1;
if (!deleted_files) {
new_files = g_list_delete_link(new_files, new_files); break; /* No more deleted files to rename */
}
} /* lather, rinse, repeat until all files have a target */ } /* lather, rinse, repeat until all files have a target */
@@ -364,65 +446,89 @@ void rename_detection(struct manifest *manifest, int last_change, GList *last_ve
g_list_free(deleted_files); g_list_free(deleted_files);
} }
static int file_found_in_older_manifest(struct manifest *from_manifest, struct file *searched_file) void final_link(GList *files)
{ {
GList *list; GList *list1, *list2;
struct file *file; struct file *file1, *file2;
list = g_list_first(from_manifest->files); list1 = new_list_renamed_files(files);
while (list) { list1 = list2 = g_list_sort(list1, file_sort_version);
file = list->data;
list = g_list_next(list);
if (file->is_deleted) { for (; list1; list1 = g_list_next(list1)) {
file1 = list1->data;
/* do not re-link already-linked renames */
if (file1->rename_peer != NULL) {
continue; continue;
} }
if (!strcmp(file->filename, searched_file->filename)) {
return 1; /* check the rest of the list */
} list2 = g_list_next(list1);
for (; list2; list2 = g_list_next(list2)) {
file2 = list2->data;
/* must be the same version to link */
if (file1->last_change != file2->last_change) {
continue;
} }
return 0; /* need one and only one to be deleted to link as this
* indicates a renamed-from/renamed-to relationship
* (.d.r -> F..r) */
if (file1->is_deleted == file2->is_deleted) {
continue;
}
/* must have same hash to link */
if (!hash_compare(file2->hash, file1->hash)) {
continue;
}
file1->rename_peer = file2;
file2->rename_peer = file1;
}
}
} }
void link_renames(GList *newfiles, struct manifest *from_manifest) /* What do we need this for?
*
* rename_detection has already set up the links in the manifest it
* was given.
*
*/
void link_renames(GList *newfiles, int to_version)
{ {
GList *list1, *list2; GList *list1, *list2;
GList *targets; GList *targets;
struct file *file1, *file2; struct file *file1, *file2;
targets = newfiles = g_list_sort(newfiles, file_sort_version); targets = new_list_renamed_files(newfiles);
/* TODO: Check that g_list_sort is reasonable speed */
targets = newfiles = g_list_sort(targets, file_sort_version);
list1 = g_list_first(newfiles); for (list1 = newfiles; list1; list1 = g_list_next(list1)) {
/* todo: sort newfiles and targets by hash */
while (list1) {
file1 = list1->data; file1 = list1->data;
list1 = g_list_next(list1);
if ((file1->peer || !file1->is_rename) || if (file1->peer || file1->is_deleted) {
(file1->is_deleted)) {
continue; continue;
} }
/* now, file1 is the new file that got renamed. time to search the rename targets */ /* now, file1 is the new file that got renamed.
* time to search the rename targets */
list2 = g_list_first(targets); list2 = g_list_first(targets);
while (list2) { for (; list2; list2 = g_list_next(list2)) {
file2 = list2->data; file2 = list2->data;
list2 = g_list_next(list2); /* This is like deleted_p but not quite */
/* deleted_p returns false for directories and links */
if ((!file2->peer || !file2->is_rename) || if (!file2->peer ||
(!file2->is_deleted) || (!file2->is_deleted) ||
(!file_found_in_older_manifest(from_manifest, file2))) { (file2->last_change != to_version)) {
continue; continue;
} }
if (hash_compare(file2->hash, file1->hash)) { if (hash_compare(file2->hash, file1->hash)) {
file1->rename_peer = file2->peer; file1->rename_peer = file2->peer;
file1->peer = file2->peer; file1->peer = file2->peer;
file2->peer->rename_peer = file1; file2->peer->rename_peer = file1;
list2 = NULL; break;
} }
} }
} }
free(from_manifest); g_list_free(targets);
} }
+6 -7
View File
@@ -235,8 +235,7 @@ GList *get_last_versions_list(int next_version, int max_versions)
DIR *dir; DIR *dir;
GList *list = NULL; GList *list = NULL;
GList *cur_item, *next_item; GList *cur_item, *next_item;
struct dirent entry; struct dirent *entry;
struct dirent *result;
struct stat stat; struct stat stat;
char *filename = NULL; char *filename = NULL;
int idx, build_num, build_type, jump_point; int idx, build_num, build_type, jump_point;
@@ -249,13 +248,13 @@ GList *get_last_versions_list(int next_version, int max_versions)
return NULL; return NULL;
} }
while (readdir_r(dir, &entry, &result) == 0 && result != NULL) { while ((entry = readdir(dir))) {
if (strspn(entry.d_name, "0123456789") != strlen(entry.d_name)) { if (strspn(entry->d_name, "0123456789") != strlen(entry->d_name)) {
continue; continue;
} }
free(filename); free(filename);
string_or_die(&filename, "%s/%s", staging_dir, entry.d_name); string_or_die(&filename, "%s/%s", staging_dir, entry->d_name);
if (lstat(filename, &stat)) { if (lstat(filename, &stat)) {
LOG(NULL, "lstat failed", "path= %s, strerror= %s", LOG(NULL, "lstat failed", "path= %s, strerror= %s",
@@ -267,11 +266,11 @@ GList *get_last_versions_list(int next_version, int max_versions)
continue; continue;
} }
if (atoi(entry.d_name) >= next_version) { if (atoi(entry->d_name) >= next_version) {
continue; continue;
} }
list = g_list_prepend(list, GINT_TO_POINTER(atoi(entry.d_name))); list = g_list_prepend(list, GINT_TO_POINTER(atoi(entry->d_name)));
} }
free(filename); free(filename);
closedir(dir); closedir(dir);
+1 -2
View File
@@ -60,8 +60,7 @@ static int xattr_get_value(const char *path, const char *name, char **blob,
/* realloc needed len + 1 in case we need to add final zero /* realloc needed len + 1 in case we need to add final zero
* to ensure consistent blob */ * to ensure consistent blob */
value = realloc(*blob, *blob_len + len + value = realloc(*blob, *blob_len + len + (action == XATTRS_ACTION_GET_BLOB ? 1 : 0));
(action == XATTRS_ACTION_GET_BLOB ? 1 : 0));
assert(value); assert(value);
*blob = value; *blob = value;
@@ -0,0 +1,64 @@
#!/usr/bin/env bats
# common functions
load "../swupdlib"
setup() {
clean_test_dir
init_test_dir
init_server_ini
init_groups_ini os-core test-bundle1 test-bundle2
set_os_release 10 os-core
track_bundle 10 os-core
track_bundle 10 test-bundle1
track_bundle 10 test-bundle2
gen_file_plain 10 test-bundle1 foo
gen_file_plain 10 test-bundle1 foobar
gen_file_plain 10 test-bundle2 foo2
gen_includes_file test-bundle2 10 test-bundle1
set_os_release 20 os-core
track_bundle 20 os-core
track_bundle 20 test-bundle1
track_bundle 20 test-bundle2
gen_file_plain 20 test-bundle1 foo
gen_file_plain 20 test-bundle1 foobar
gen_file_plain 20 test-bundle1 foobarbaz
gen_file_plain 20 test-bundle2 foo2
gen_file_plain 20 test-bundle2 foo2bar
gen_includes_file test-bundle2 20 test-bundle1
}
@test "correct contentsize" {
# create a couple updates to both check that contentsize does not add included
# bundles and to verify that files changed in previous updates are counted.
sudo $CREATE_UPDATE --osversion 10 --statedir $DIR --format 3
set_latest_ver 10
# contentsize for test-bundle2 should not include test-bundle1's contentsize
[[ 1 -eq $(grep '^contentsize: 11$' $DIR/www/10/Manifest.test-bundle1 | wc -l) ]]
[[ 1 -eq $(grep '^contentsize: 5$' $DIR/www/10/Manifest.test-bundle2 | wc -l) ]]
# os-core is large because it includes /usr/*
[[ 1 -eq $(grep '^contentsize: 5134$' $DIR/www/10/Manifest.os-core | wc -l) ]]
# 5134 + 11 + 5 = 5150
[[ 1 -eq $(grep '^contentsize: 5150$' $DIR/www/10/Manifest.full | wc -l) ]]
sudo $CREATE_UPDATE --osversion 20 --statedir $DIR --format 3
set_latest_ver 20
# one new file: foobarbaz (10 bytes)
[[ 1 -eq $(grep '^contentsize: 21$' $DIR/www/20/Manifest.test-bundle1 | wc -l) ]]
# one new file: foo2bar (8 bytes)
[[ 1 -eq $(grep '^contentsize: 13$' $DIR/www/20/Manifest.test-bundle2 | wc -l) ]]
# os-core should not change size
[[ 1 -eq $(grep '^contentsize: 5134$' $DIR/www/10/Manifest.os-core | wc -l) ]]
# contentsize for full should be all files, including ones not changed in this release
# two new files: foo2bar (8 bytes) and foobarbaz (10 bytes)
# 5150 + 10 + 8 = 5168
# 5134 + 21 + 13 = 5168
[[ 1 -eq $(grep '^contentsize: 5168$' $DIR/www/20/Manifest.full | wc -l) ]]
}
# vi: ft=sh ts=8 sw=2 sts=2 et tw=80
+56
View File
@@ -0,0 +1,56 @@
#!/usr/bin/env bats
# common functions
load "../swupdlib"
setup() {
clean_test_dir
init_test_dir
init_server_ini
# unban debuginfo
sed -i "s|banned=true|banned=false|" $DIR/server.ini
set_latest_ver 0
init_groups_ini os-core
init_groups_ini test-bundle
set_os_release 10 os-core
track_bundle 10 os-core
set_os_release 10 test-bundle
track_bundle 10 test-bundle
gen_file_plain 10 test-bundle "/usr/lib/debug/foo"
gen_file_plain 10 test-bundle "/usr/src/debug/bar"
gen_file_plain 10 test-bundle "/usr/bin/foobar"
track_bundle 20 os-core
set_os_release 20 test-bundle
track_bundle 20 test-bundle
gen_file_plain 20 test-bundle "/usr/foo"
}
@test "debuginfo files pruned" {
run sudo sh -c "$CREATE_UPDATE --osversion 10 --statedir $DIR --format 3"
echo "$output"
# This should not be pruned
[[ 1 -eq $(grep '/usr/bin/foobar$' $DIR/www/10/Manifest.test-bundle | wc -l) ]]
[[ 1 -eq $(grep '/usr/lib/debug$' $DIR/www/10/Manifest.test-bundle | wc -l) ]]
[[ 1 -eq $(grep '/usr/src/debug$' $DIR/www/10/Manifest.test-bundle | wc -l) ]]
# These should not be pruned right now, but should be pruned with the next
# update after being banned in server.ini again
[[ 1 -eq $(grep '/usr/src/debug/bar$' $DIR/www/10/Manifest.test-bundle | wc -l) ]]
[[ 1 -eq $(grep '/usr/lib/debug/foo$' $DIR/www/10/Manifest.test-bundle | wc -l) ]]
# grab the server.ini that bans debuginfo again
init_server_ini
set_os_release 20 os-core
run sudo sh -c "$CREATE_UPDATE --osversion 20 --statedir $DIR --format 3"
# This should not be pruned
[[ 1 -eq $(grep '/usr/foo$' $DIR/www/20/Manifest.test-bundle | wc -l) ]]
# These should be pruned
[[ 0 -eq $(grep '/usr/src/debug/bar$' $DIR/www/20/Manifest.test-bundle | wc -l) ]]
[[ 0 -eq $(grep '/usr/lib/debug/foo$' $DIR/www/20/Manifest.test-bundle | wc -l) ]]
}
# vi: ft=sh ts=8 sw=2 sts=2 et tw=80
+60
View File
@@ -0,0 +1,60 @@
#!/usr/bin/env bats
# common functions
load "../swupdlib"
setup() {
clean_test_dir
init_test_dir
init_server_ini
set_latest_ver 0
init_groups_ini os-core
set_os_release 10 os-core
track_bundle 10 os-core
set_os_release 20 os-core
track_bundle 20 os-core
set_os_release 30 os-core
track_bundle 30 os-core
}
@test "full run update creation with delta packs over format bump" {
# build the first version
echo $CREATE_UPDATE
echo $DIR
sudo $CREATE_UPDATE --osversion 10 --statedir $DIR --format 3
sudo $MAKE_FULLFILES --statedir $DIR 10
sudo $MAKE_PACK --statedir $DIR 0 10 os-core
set_latest_ver 10
# then the second version...
echo $CREATE_UPDATE
echo $DIR
sudo $CREATE_UPDATE --osversion 20 --statedir $DIR --format 3
sudo $MAKE_FULLFILES --statedir $DIR 20
sudo $MAKE_PACK --statedir $DIR 0 20 os-core
set_latest_ver 20
# then the third version...
echo $CREATE_UPDATE
echo $DIR
sudo $CREATE_UPDATE --osversion 30 --statedir $DIR --format 4
sudo $MAKE_FULLFILES --statedir $DIR 30
sudo $MAKE_PACK --statedir $DIR 0 30 os-core
# zero packs should exist (non-zero size) for all versions
[ -s $DIR/www/10/pack-os-core-from-0.tar ]
[ -s $DIR/www/20/pack-os-core-from-0.tar ]
[ -s $DIR/www/30/pack-os-core-from-0.tar ]
[[ 0 -eq $(grep '^actions: update$' $DIR/www/10/Manifest.MoM | wc -l) ]]
[[ 0 -eq $(grep '^actions: update$' $DIR/www/20/Manifest.MoM | wc -l) ]]
[[ 1 -eq $(grep '^actions: update$' $DIR/www/30/Manifest.MoM | wc -l) ]]
}
# vi: ft=sh ts=8 sw=2 sts=2 et tw=80
+35
View File
@@ -0,0 +1,35 @@
#!/usr/bin/env bats
# common functions
load "../swupdlib"
setup() {
clean_test_dir
init_test_dir
init_server_ini
set_latest_ver 0
init_groups_ini os-core
set_os_release 10 os-core
track_bundle 10 os-core
set_os_release 20 os-core
track_bundle 20 os-core
gen_file_plain 10 os-core foo
gen_file_plain 10 os-core bar
gen_file_plain 20 os-core foo
gen_file_plain 20 os-core baz
}
@test "ensure format numbers cannot be decremented" {
sudo $CREATE_UPDATE --osversion 10 --statedir $DIR --format 3
set_latest_ver 10
# Now, decrement the format number. Should result in an EXIT_FAILURE.
run sudo $CREATE_UPDATE --osversion 20 --statedir $DIR --format 2
echo "$output"
[ $status -eq 1 ]
}
# vi: ft=sh ts=8 sw=2 sts=2 et tw=80
+6
View File
@@ -40,6 +40,12 @@ setup() {
[[ 1 -eq $(grep '/usr/share/clear/bundles$' $DIR/www/10/Manifest.os-core | wc -l) ]] [[ 1 -eq $(grep '/usr/share/clear/bundles$' $DIR/www/10/Manifest.os-core | wc -l) ]]
[[ 4 -eq $(tar -tf $DIR/www/10/pack-test-bundle-from-0.tar | wc -l) ]] [[ 4 -eq $(tar -tf $DIR/www/10/pack-test-bundle-from-0.tar | wc -l) ]]
[[ 5 -eq $(tar -tf $DIR/www/10/pack-os-core-from-0.tar | wc -l) ]] [[ 5 -eq $(tar -tf $DIR/www/10/pack-os-core-from-0.tar | wc -l) ]]
# extract test-bundle pack to make sure staged and delta are created with the
# correct permissions
sudo tar -xf $DIR/www/10/pack-test-bundle-from-0.tar --directory $DIR/www/10/
[[ $(stat -c %a $DIR/www/10/staged) -eq 700 ]]
[[ $(stat -c %a $DIR/www/10/delta) -eq 700 ]]
} }
# vi: ft=sh ts=8 sw=2 sts=2 et tw=80 # vi: ft=sh ts=8 sw=2 sts=2 et tw=80
+60
View File
@@ -0,0 +1,60 @@
#!/usr/bin/env bats
# common functions
load "../swupdlib"
setup() {
clean_test_dir
init_test_dir
init_server_ini
set_latest_ver 0
init_groups_ini os-core test-bundle
set_os_release 10 os-core
set_os_release 10 test-bundle
track_bundle 10 os-core
track_bundle 10 test-bundle
set_os_release 20 os-core
set_os_release 20 test-bundle
track_bundle 20 os-core
track_bundle 20 test-bundle
set_os_release 30 os-core
set_os_release 30 test-bundle
track_bundle 30 os-core
track_bundle 30 test-bundle
gen_file_plain_with_content 10 test-bundle usr/lib/kernel/bar testfile_contents
gen_file_plain_with_content 20 test-bundle usr/lib/kernel/baz new_testfile_contents
# make sure /usr/lib/kernel stays around
mkdir $DIR/image/30/test-bundle/usr/lib/kernel
}
@test "create updates while ghosting boot files before removing them" {
sudo $CREATE_UPDATE --osversion 10 --statedir $DIR --format 3
sudo $MAKE_FULLFILES --statedir $DIR 10
set_latest_ver 10
sudo $CREATE_UPDATE --osversion 20 --statedir $DIR --format 3
sudo $MAKE_FULLFILES --statedir $DIR 20
set_latest_ver 20
sudo $CREATE_UPDATE --osversion 30 --statedir $DIR --format 3
sudo $MAKE_FULLFILES --statedir $DIR 30
# version 10: add boot file to 10
[ 1 -eq $(grep 'F\.b\. .* 10 /usr/lib/kernel/bar' $DIR/www/10/Manifest.full | wc -l) ]
# version 20: ghost boot file instead of deleting it when it doesn't exist
[ 1 -eq $(grep '\.gb\. .* 20 /usr/lib/kernel/bar' $DIR/www/20/Manifest.full | wc -l) ]
# version 20: add a new boot file
[ 1 -eq $(grep 'F\.b\. .* 20 /usr/lib/kernel/baz' $DIR/www/20/Manifest.full | wc -l) ]
# version 30: old ghosted file /usr/lib/kernel/bar cleaned up
[ 0 -eq $(grep '/usr/lib/kernel/bar' $DIR/www/30/Manifest.full | wc -l) ]
# version 30: boot file added in version 20 ghosted
[ 1 -eq $(grep '\.gb\. .* 30 /usr/lib/kernel/baz' $DIR/www/30/Manifest.full | wc -l) ]
}
# vi: ft=sh ts=8 sw=2 sts=2 et tw=80
+105
View File
@@ -0,0 +1,105 @@
#!/usr/bin/env bats
# common functions
load "../swupdlib"
setup() {
clean_test_dir
init_test_dir
init_server_ini
set_latest_ver 0
init_groups_ini os-core test-bundle
set_os_release 10 os-core
set_os_release 10 test-bundle
track_bundle 10 os-core
track_bundle 10 test-bundle
set_os_release 20 os-core
set_os_release 20 test-bundle
track_bundle 20 os-core
track_bundle 20 test-bundle
set_os_release 30 os-core
set_os_release 30 test-bundle
track_bundle 30 os-core
track_bundle 30 test-bundle
set_os_release 40 os-core
set_os_release 40 test-bundle
track_bundle 40 os-core
track_bundle 40 test-bundle
# /usr/lib/bar and /one will be renamed to /usr/lib/baz and /two
gen_file_plain_with_content 10 test-bundle /usr/lib/bar "$(seq 100)"
gen_file_plain_with_content 10 test-bundle /one "$(printf 'a%.0s' {1..200})"
gen_file_plain_with_content 10 test-bundle /usr/lib/kernel/a "$(printf 'a%.0s' {1..210})"
gen_file_plain_with_content 20 test-bundle /usr/lib/baz "$(seq 100)"
gen_file_plain_with_content 20 test-bundle /two "$(printf 'a%.0s' {1..200})"
gen_file_plain_with_content 20 test-bundle /usr/lib/kernel/ab "$(printf 'a%.0s' {1..210})"
# different content just to make sure this works with delta renames as well as
# direct renames
gen_file_plain_with_content 30 test-bundle /usr/lib/foo "$(seq 100) new"
gen_file_plain_with_content 30 test-bundle /usr/lib/kernel/abc "$(printf 'a%.0s' {1..210})"
gen_file_plain_with_content 40 test-bundle /usr/lib/foo "$(seq 100) new"
gen_file_plain_with_content 40 test-bundle /usr/lib/kernel/abc "$(printf 'a%.0s' {1..210})"
# new file to force manifest generation
gen_file_plain_with_content 40 test-bundle /a "testfile"
}
@test "create updates with renamed-to file getting deleted" {
sudo $CREATE_UPDATE --osversion 10 --statedir $DIR --format 3
sudo $MAKE_FULLFILES --statedir $DIR 10
set_latest_ver 10
sudo $CREATE_UPDATE --osversion 20 --statedir $DIR --format 3
sudo $MAKE_FULLFILES --statedir $DIR 20
set_latest_ver 20
sudo $CREATE_UPDATE --osversion 30 --statedir $DIR --format 3
sudo $MAKE_FULLFILES --statedir $DIR 30
set_latest_ver 30
sudo $CREATE_UPDATE --osversion 40 --statedir $DIR --format 3
sudo $MAKE_FULLFILES --statedir $DIR 40
# version 10: add files to 10
[ 1 -eq $(grep $'F\.\.\.\t.*\t10\t/usr/lib/bar' $DIR/www/10/Manifest.test-bundle | wc -l) ]
[ 1 -eq $(grep $'F\.\.\.\t.*\t10\t/one' $DIR/www/10/Manifest.test-bundle | wc -l) ]
[ 1 -eq $(grep $'F\.b\.\t.*\t10\t/usr/lib/kernel/a' $DIR/www/10/Manifest.test-bundle | wc -l) ]
# version 20: rename bar to baz and one to two
[ 1 -eq $(grep $'\.d\.r\t.*\t20\t/one' $DIR/www/20/Manifest.test-bundle | wc -l) ]
[ 1 -eq $(grep $'F\.\.r\t.*\t20\t/two' $DIR/www/20/Manifest.test-bundle | wc -l) ]
[ 1 -eq $(grep $'\.d\.r\t.*\t20\t/usr/lib/bar' $DIR/www/20/Manifest.test-bundle | wc -l) ]
[ 1 -eq $(grep $'F\.\.r\t.*\t20\t/usr/lib/baz' $DIR/www/20/Manifest.test-bundle | wc -l) ]
[ 1 -eq $(grep $'\.gbr\t.*\t20\t/usr/lib/kernel/a' $DIR/www/20/Manifest.test-bundle | wc -l) ]
[ 1 -eq $(grep $'F\.br\t.*\t20\t/usr/lib/kernel/ab' $DIR/www/20/Manifest.test-bundle | wc -l) ]
# version 30: original renamed-from file (bar) is now orphaned and therefore
# deleted, baz is now a renamed-from file and foo is a renamed-to file.
# /two was deleted in this version, so both /one and /two should be marked as
# deleted
[ 1 -eq $(grep $'\.d\.\.\t0\{64\}\t20\t/one' $DIR/www/30/Manifest.test-bundle | wc -l) ]
[ 1 -eq $(grep $'\.d\.\.\t0\{64\}\t30\t/two' $DIR/www/30/Manifest.test-bundle | wc -l) ]
[ 1 -eq $(grep $'\.d\.\.\t0\{64\}\t20\t/usr/lib/bar' $DIR/www/30/Manifest.test-bundle | wc -l) ]
[ 1 -eq $(grep $'\.d\.r\t.*\t30\t/usr/lib/baz' $DIR/www/30/Manifest.test-bundle | wc -l) ]
[ 1 -eq $(grep $'F\.\.r\t.*\t30\t/usr/lib/foo' $DIR/www/30/Manifest.test-bundle | wc -l) ]
[ 1 -eq $(grep $'\.gbr\t.*\t30\t/usr/lib/kernel/ab' $DIR/www/30/Manifest.test-bundle | wc -l) ]
[ 1 -eq $(grep $'F\.br\t.*\t30\t/usr/lib/kernel/abc' $DIR/www/30/Manifest.test-bundle | wc -l) ]
# version 40: the existing rename from baz -> foo must persist while all
# others remain deleted
[ 1 -eq $(grep $'\.d\.\.\t0\{64\}\t20\t/one' $DIR/www/40/Manifest.test-bundle | wc -l) ]
[ 1 -eq $(grep $'\.d\.\.\t0\{64\}\t30\t/two' $DIR/www/40/Manifest.test-bundle | wc -l) ]
[ 1 -eq $(grep $'\.d\.\.\t0\{64\}\t20\t/usr/lib/bar' $DIR/www/40/Manifest.test-bundle | wc -l) ]
[ 1 -eq $(grep $'\.d\.r\t.*\t30\t/usr/lib/baz' $DIR/www/40/Manifest.test-bundle | wc -l) ]
[ 1 -eq $(grep $'F\.\.r\t.*\t30\t/usr/lib/foo' $DIR/www/40/Manifest.test-bundle | wc -l) ]
[ 1 -eq $(grep $'F\.b\.\t.*\t30\t/usr/lib/kernel/abc' $DIR/www/40/Manifest.test-bundle | wc -l) ]
}
# vi: ft=sh ts=8 sw=2 sts=2 et tw=80
+13
View File
@@ -0,0 +1,13 @@
#!/bin/bash
export BATS_TEST_DIRNAME=$PWD
export TIMEFORMAT
TIMEFORMAT="time %R %P%% (%U+%S)"
source ../swupdlib.bash
exec 3< rundata
while read -r -u 3
do
read -i "$REPLY" -e CMD
eval "set -x ; time ( $CMD ) ; set +x "
done
+22
View File
@@ -0,0 +1,22 @@
sudo rm -r logs/ web-dir/
tar xf ~/data/test.tar
init_test_dir
init_server_ini
set_latest-ver 0
init_groups_ini os-core test-bundle
set_os_release 10 os-core
track_bundle 10 os-core
track_bundle 10 test-bundle
set_os_release 20 os-core
track_bundle 20 os-core
track_bundle 20 test-bundle
sudo $CREATE_UPDATE --osversion 10 --statedir $DIR --format 3
sudo $MAKE_FULLFILES --statedir $DIR 10
sudo $MAKE_PACK --statedir $DIR 0 10 os-core
sudo $MAKE_PACK --statedir $DIR 0 10 test-bundle
time sudo $MAKE_PACK --statedir $DIR 0 10 test-bundle
set_latest-ver 10
sudo $CREATE_UPDATE --osversion 20 --statedir $DIR --format 3
sudo $MAKE_FULLFILES --statedir $DIR 20
sudo $MAKE_PACK --statedir $DIR 10 20 os-core
sudo $MAKE_PACK --statedir $DIR 10 20 test-bundle
+246
View File
@@ -0,0 +1,246 @@
#!/usr/bin/env bats
# common functions
load "../swupdlib"
setup() {
maybeskip
clean_test_dir
init_test_dir
init_server_ini
set_latest_ver 0
init_groups_ini os-core test-bundle
set_os_release 10 os-core
track_bundle 10 os-core
track_bundle 10 test-bundle
set_os_release 20 os-core
track_bundle 20 os-core
track_bundle 20 test-bundle
}
# Generate data files of 3 types in test-bundle
# All files are big enough that they get rename detection
gendataA() {
gen_file_plain_with_content "$1" test-bundle "$2" "$(seq 100)"
}
gendataB() {
gen_file_plain_with_content "$1" test-bundle "$2" "$(seq 50) $(seq 52 101)"
}
gendataC() {
# cache string
[ -z "$dataC" ] || dataC="$(seq 1000 | gzip | uuencode wombat)"
gen_file_plain_with_content "$1" test-bundle "$2" "$dataC"
}
# Generate small data files
gendataAs() {
gen_file_plain_with_content "$1" test-bundle "$2" "$(seq 50)"
}
gendataBs() {
gen_file_plain_with_content "$1" test-bundle "$2" "$(seq 24) $(seq 26 49)"
}
gendataCs() {
# cache string
[ -z "$dataCs" ] || dataC="$(seq 50 | gzip | uuencode wombat)"
gen_file_plain_with_content "$1" test-bundle "$2" "$dataCs"
}
checkrenamed(){
local flags sha1 ver name fromsha1="bad" tosha1
# Check that $1 is renamed to $2
exec 9< $DIR/www/20/Manifest.test-bundle
# skip the header
while read -u9
do
[ -z "$REPLY" ] && break
done
while read -r -u9 flags sha1 ver name
do
case "$flags" in
(?"d.r") [ "$name" = "$1" ] && fromsha1=$sha1 ;;
(?"..r") [ "$name" = "$2" ] && tosha1=$sha1 ;;
esac
done
if [ "$fromsha1" = "$tosha1" ] ; then return 0 ; else return 1 ; fi
}
# Guts of doing an update
do_an_update() {
sudo $CREATE_UPDATE --osversion 10 --statedir $DIR --format 3
sudo $MAKE_FULLFILES --statedir $DIR 10
sudo $MAKE_PACK --statedir $DIR 0 10 os-core
sudo $MAKE_PACK --statedir $DIR 0 10 test-bundle
set_latest_ver 10
sudo $CREATE_UPDATE --osversion 20 --statedir $DIR --format 3
sudo $MAKE_FULLFILES --statedir $DIR 20
sudo $MAKE_PACK --statedir $DIR 0 20 os-core
sudo $MAKE_PACK --statedir $DIR 0 20 test-bundle
sudo $MAKE_PACK --statedir $DIR 10 20 os-core
sudo $MAKE_PACK --statedir $DIR 10 20 test-bundle
}
@test "basic rename detection support" {
gendataA 10 foo
gendataA 20 bar
do_an_update
checkrenamed /foo /bar
}
@test "ignore rename detection for small files" {
gendataAs 10 foo
gendataAs 20 bar
do_an_update
# A renamed file comprises a new file and a deleted file
[[ 0 -eq $(grep '^F\.\.r.*/bar$' $DIR/www/20/Manifest.test-bundle | wc -l) ]]
[[ 0 -eq $(grep '^\.d\.r.*/foo$' $DIR/www/20/Manifest.test-bundle | wc -l) ]]
}
@test "ignore rename detection for large to small files" {
gendataA 10 foo
gendataAs 20 bar
do_an_update
# A renamed file comprises a new file and a deleted file
[[ 0 -eq $(grep '^F\.\.r.*/bar$' $DIR/www/20/Manifest.test-bundle | wc -l) ]]
[[ 0 -eq $(grep '^\.d\.r.*/foo$' $DIR/www/20/Manifest.test-bundle | wc -l) ]]
}
@test "ignore rename detection for small to large files" {
gendataAs 10 foo
gendataA 20 bar
do_an_update
# A renamed file comprises a new file and a deleted file
[[ 0 -eq $(grep '^F\.\.r.*/bar$' $DIR/www/20/Manifest.test-bundle | wc -l) ]]
[[ 0 -eq $(grep '^\.d\.r.*/foo$' $DIR/www/20/Manifest.test-bundle | wc -l) ]]
}
@test "rename one file to two" {
gendataA 10 foo
gendataA 20 bar
gendataA 20 baz
do_an_update
# A renamed file comprises a new file and a deleted file
[[ 1 -eq $(grep '^F\.\.r.*/ba[rz]$' $DIR/www/20/Manifest.test-bundle | wc -l) ]]
[[ 1 -eq $(grep '^\.d\.r.*/foo$' $DIR/www/20/Manifest.test-bundle | wc -l) ]]
}
@test "rename two file to one" {
gendataA 10 foo
gendataA 10 foz
gendataA 20 baz
do_an_update
# A renamed file comprises a new file and a deleted file
[[ 1 -eq $(grep '^F\.\.r.*/baz$' $DIR/www/20/Manifest.test-bundle | wc -l) ]]
[[ 1 -eq $(grep '^\.d\.\..*/fo[oz]$' $DIR/www/20/Manifest.test-bundle | wc -l) ]]
[[ 1 -eq $(grep '^\.d\.r.*/fo[oz]$' $DIR/www/20/Manifest.test-bundle | wc -l) ]]
}
@test "rename two files to two" {
gendataA 10 foo
gendataA 10 foz
gendataA 20 bar
gendataA 20 baz
do_an_update
checkrenamed /foo /bar
checkrenamed /foz /baz
}
@test "rename two files to two, one slightly different" {
gendataA 10 foo
gendataA 10 foz
gendataA 20 bar
gendataB 20 baz
do_an_update
# we don't actually know how the client we do this rename, but don't care
checkrenamed /foo /bar
checkrenamed /foz /baz
}
@test "rename two files to two, each pair slightly different" {
gendataA 10 foo
gendataB 10 foz
gendataA 20 bar
gendataB 20 baz
do_an_update
checkrenamed /foo /bar
checkrenamed /foz /baz
}
@test "rename two files to two, one very different" {
gendataA 10 foo
gendataA 10 foz
gendataA 20 bar
gendataC 20 baz
do_an_update
# A renamed file comprises a new file and a deleted file
[[ 1 -eq $(grep '^F\.\.r.*/ba[rz]$' $DIR/www/20/Manifest.test-bundle | wc -l) ]]
[[ 1 -eq $(grep '^\.d\.r.*/fo[oz]$' $DIR/www/20/Manifest.test-bundle | wc -l) ]]
}
@test "rename two files to two, one small" {
gendataA 10 foo
gendataA 10 foz
gendataA 20 bar
gendataCs 20 baz
do_an_update
run checkrenamed /foo /bar
if [ $status -eq 1 ] ; then
checkrenamed /foz /bar
fi
}
@test "directory name changes" {
gendataA 10 dir1/foo
gendataA 20 dir2/foo
do_an_update
checkrenamed /dir1/foo /dir2/foo
}
@test "directory name changes small files" {
gendataAs 10 dir1/foo
gendataAs 20 dir2/foo
do_an_update
if ! checkrenamed /dir1/foo /dir2/foo ; then false ; fi
}
@test "directory name and small data changes" {
gendataA 10 dir1/foo
gendataB 20 dir2/foz
do_an_update
checkrenamed /dir1/foo /dir2/foz
}
@test "directory name and small data changes, choose same name" {
gendataA 10 dir1/foo
gendataA 10 dir1/foz
gendataB 20 dir2/foz
do_an_update
checkrenamed /dir1/foz /dir2/foz
}
@test "same basename test" {
gendataA 10 dir1/foo.so.1
gendataA 10 dir1/foz.so.1
gendataB 20 dir2/foo.so.2
do_an_update
checkrenamed /dir1/foo.so.1 /dir2/foo.so.2
}
@test "rename file to dir/file" {
gendataA 10 foo
gendataA 20 foo/bar
do_an_update
checkrenamed /foo /foo/bar
}
# @test "rename foo/foo to foo" {
# gendataA 10 foo/foo
# gendataA 20 foo
# do_an_update
# checkrenamed /foo/foo /foo
# }
# Emacs and vi support
# Local variables:
# sh-indentation: 2
# End:
# vi: ft=sh ts=8 sw=2 sts=2 et tw=80
+13
View File
@@ -0,0 +1,13 @@
#!/bin/bash
export BATS_TEST_DIRNAME=$PWD
export TIMEFORMAT
TIMEFORMAT="time %R %P%% (%U+%S)"
source ../swupdlib.bash
exec 3< rundata
while read -r -u 3
do
read -i "$REPLY" -e CMD
eval "set -x ; time ( $CMD ) ; set +x "
done
+22
View File
@@ -0,0 +1,22 @@
sudo rm -r logs/ web-dir/
tar xf ~/data/test.tar
init_test_dir
init_server_ini
set_latest_ver 0
init_groups_ini os-core test-bundle
set_os_release 10 os-core
track_bundle 10 os-core
track_bundle 10 test-bundle
set_os_release 20 os-core
track_bundle 20 os-core
track_bundle 20 test-bundle
sudo $CREATE_UPDATE --osversion 10 --statedir $DIR --format 3
sudo $MAKE_FULLFILES --statedir $DIR 10
sudo $MAKE_PACK --statedir $DIR 0 10 os-core
sudo $MAKE_PACK --statedir $DIR 0 10 test-bundle
time sudo $MAKE_PACK --statedir $DIR 0 10 test-bundle
set_latest_ver 10
sudo $CREATE_UPDATE --osversion 20 --statedir $DIR --format 3
sudo $MAKE_FULLFILES --statedir $DIR 20
sudo $MAKE_PACK --statedir $DIR 10 20 os-core
sudo $MAKE_PACK --statedir $DIR 10 20 test-bundle
+144
View File
@@ -0,0 +1,144 @@
#!/usr/bin/env bats
# common functions
load "../swupdlib"
setup() {
clean_test_dir
init_test_dir
tar x -C $DIR/.. -f ~/data/test.tar
mv $DIR/image/20/test-bundle/usr/share/bash-completion/completions{,.old}
init_server_ini
set_latest_ver 0
init_groups_ini os-core test-bundle
set_os_release 10 os-core
track_bundle 10 os-core
track_bundle 10 test-bundle
set_os_release 20 os-core
track_bundle 20 os-core
track_bundle 20 test-bundle
# Files have different names ("foo" vs "bar"), but have the same content
gen_file_plain_with_content 10 test-bundle "foo" "data"
gen_file_plain_with_content 20 test-bundle "bar" "data"
}
@test "rename detection support" {
sudo $CREATE_UPDATE --osversion 10 --statedir $DIR --format 3
sudo $MAKE_FULLFILES --statedir $DIR 10
sudo $MAKE_PACK --statedir $DIR 0 10 os-core
sudo $MAKE_PACK --statedir $DIR 0 10 test-bundle
set_latest_ver 10
sudo $CREATE_UPDATE --osversion 20 --statedir $DIR --format 3
sudo $MAKE_FULLFILES --statedir $DIR 20
sudo $MAKE_PACK --statedir $DIR 0 20 os-core
sudo $MAKE_PACK --statedir $DIR 0 20 test-bundle
sudo $MAKE_PACK --statedir $DIR 10 20 os-core
sudo $MAKE_PACK --statedir $DIR 10 20 test-bundle
# A renamed file comprises a new file and a deleted file
[[ 1 -eq $(grep '^F\.\.r.*/bar$' $DIR/www/20/Manifest.test-bundle | wc -l) ]]
[[ 1 -eq $(grep '^\.d\.r.*/foo$' $DIR/www/20/Manifest.test-bundle | wc -l) ]]
}
# @test "rename detection support 1 to 2" {
# sudo $CREATE_UPDATE --osversion 10 --statedir $DIR --format 3
# sudo $MAKE_FULLFILES --statedir $DIR 10
# sudo $MAKE_PACK --statedir $DIR 0 10 os-core
# sudo $MAKE_PACK --statedir $DIR 0 10 test-bundle
# set_latest_ver 10
# gen_file_plain_with_content 20 test-bundle "baz" "data"
# sudo $CREATE_UPDATE --osversion 20 --statedir $DIR --format 3
# sudo $MAKE_FULLFILES --statedir $DIR 20
# sudo $MAKE_PACK --statedir $DIR 0 20 os-core
# sudo $MAKE_PACK --statedir $DIR 0 20 test-bundle
# sudo $MAKE_PACK --statedir $DIR 10 20 os-core
# sudo $MAKE_PACK --statedir $DIR 10 20 test-bundle
# # A renamed file comprises a new file and a deleted file.
# # Should be one file that is not renamed
# # Need to figure out what happens to hard links
# [[ 1 -eq $(grep -c '^F\.\.r.*/ba[rz]$' $DIR/www/20/Manifest.test-bundle) ]]
# [[ 1 -eq $(grep -c '^F\.\.\..*/ba[rz]$' $DIR/www/20/Manifest.test-bundle) ]]
# [[ 1 -eq $(grep -c '^\.d\.r.*/foo$' $DIR/www/20/Manifest.test-bundle) ]]
# }
# @test "rename detection support 2 to 2" {
# gen_file_plain_with_content 10 test-bundle "fee" "data"
# sudo $CREATE_UPDATE --osversion 10 --statedir $DIR --format 3
# sudo $MAKE_FULLFILES --statedir $DIR 10
# sudo $MAKE_PACK --statedir $DIR 0 10 os-core
# sudo $MAKE_PACK --statedir $DIR 0 10 test-bundle
# set_latest_ver 10
# gen_file_plain_with_content 20 test-bundle "foo~" "data"
# sudo $CREATE_UPDATE --osversion 20 --statedir $DIR --format 3
# sudo $MAKE_FULLFILES --statedir $DIR 20
# sudo $MAKE_PACK --statedir $DIR 0 20 os-core
# sudo $MAKE_PACK --statedir $DIR 0 20 test-bundle
# sudo $MAKE_PACK --statedir $DIR 10 20 os-core
# sudo $MAKE_PACK --statedir $DIR 10 20 test-bundle
# # A renamed file comprises a new file and a deleted file.
# # Should be one file that is not renamed
# # Need to figure out what happens to hard links
# [[ 1 -eq $(grep -c '^F\.\.r.*/bar$' $DIR/www/20/Manifest.test-bundle) ]]
# [[ 1 -eq $(grep -c '^F\.\.r.*/foo~$' $DIR/www/20/Manifest.test-bundle) ]]
# [[ 2 -eq $(grep -c '^\.d\.r.*/f..$' $DIR/www/20/Manifest.test-bundle) ]]
# }
# @test "rename detection support binary files" {
# sudo mkdir -p $DIR/image/10/test-bundle/bin/
# sudo cp /bin/gcc-ar $DIR/image/10/test-bundle/bin/gcc-ar.1
# sudo cp /bin/gcc-ar $DIR/image/20/test-bundle/gcc-ar.1
# strip $DIR/image/20/test-bundle/gcc-ar.1
# sudo $CREATE_UPDATE --osversion 10 --statedir $DIR --format 3
# sudo $MAKE_FULLFILES --statedir $DIR 10
# sudo $MAKE_PACK --statedir $DIR 0 10 os-core
# sudo $MAKE_PACK --statedir $DIR 0 10 test-bundle
# set_latest_ver 10
# gen_file_plain_with_content 20 test-bundle "foo~" "And now not the same"
# sudo $CREATE_UPDATE --osversion 20 --statedir $DIR --format 3
# sudo $MAKE_FULLFILES --statedir $DIR 20
# sudo $MAKE_PACK --statedir $DIR 0 20 os-core
# sudo $MAKE_PACK --statedir $DIR 0 20 test-bundle
# sudo $MAKE_PACK --statedir $DIR 10 20 os-core
# sudo $MAKE_PACK --statedir $DIR 10 20 test-bundle
# # A renamed file comprises a new file and a deleted file.
# # Should be one file that is not renamed
# # Need to figure out what happens to hard links
# }
# @test "rename detection support binary files 2" {
# sudo $CREATE_UPDATE --osversion 10 --statedir $DIR --format 3
# sudo $MAKE_FULLFILES --statedir $DIR 10
# sudo $MAKE_PACK --statedir $DIR 0 10 os-core
# sudo $MAKE_PACK --statedir $DIR 0 10 test-bundle
# set_latest_ver 10
# gen_file_plain_with_content 20 test-bundle "foo" "And now not the same"
# gen_file_plain_with_content 20 test-bundle "foo.1" "data"
# sudo $CREATE_UPDATE --osversion 20 --statedir $DIR --format 3
# sudo $MAKE_FULLFILES --statedir $DIR 20
# sudo $MAKE_PACK --statedir $DIR 0 20 os-core
# sudo $MAKE_PACK --statedir $DIR 0 20 test-bundle
# sudo $MAKE_PACK --statedir $DIR 10 20 os-core
# sudo $MAKE_PACK --statedir $DIR 10 20 test-bundle
# # A renamed file comprises a new file and a deleted file.
# # Should be one file that is not renamed
# # Need to figure out what happens to hard links
# }
# # vi: ft=sh ts=8 sw=2 sts=2 et tw=80
+25 -2
View File
@@ -40,6 +40,11 @@ EOF
done done
} }
# If the variable RUN_JUST_ONE is set then only run that test
maybeskip() {
[ -z "$RUN_JUST_ONE" ] || [ "$RUN_JUST_ONE" -eq "$BATS_TEST_NUMBER" ] || skip
}
set_os_release() { set_os_release() {
local ver=$1 local ver=$1
local bundle=$2 local bundle=$2
@@ -92,7 +97,10 @@ gen_file_plain() {
local name="$3" local name="$3"
# Add plain text file into a bundle # Add plain text file into a bundle
mkdir -p $DIR/image/$ver/$bundle/$(dirname "$name") case "$name" in
(*"/"*) mkdir -p "$DIR/image/$ver/$bundle/${name%/*}" ;;
(*) mkdir -p $DIR/image/$ver/$bundle ;;
esac
echo "$name" > $DIR/image/$ver/$bundle/"$name" echo "$name" > $DIR/image/$ver/$bundle/"$name"
} }
@@ -102,7 +110,10 @@ gen_file_plain_change() {
local name="$3" local name="$3"
# Add plain text file into a bundle # Add plain text file into a bundle
mkdir -p $DIR/image/$ver/$bundle/$(dirname "$name") case "$name" in
(*"/"*) mkdir -p "$DIR/image/$ver/$bundle/${name%/*}" ;;
(*) mkdir -p $DIR/image/$ver/$bundle ;;
esac
echo "$ver $name" > $DIR/image/$ver/$bundle/"$name" echo "$ver $name" > $DIR/image/$ver/$bundle/"$name"
} }
@@ -137,4 +148,16 @@ hash_for() {
awk -F'\t' -v NAME="$name" 'NF == 4 && $4 == NAME { print $2 }' $DIR/www/$ver/Manifest.$bundle awk -F'\t' -v NAME="$name" 'NF == 4 && $4 == NAME { print $2 }' $DIR/www/$ver/Manifest.$bundle
} }
gen_file_plain_with_content() {
local ver=$1
local bundle=$2
local name="$3"
local content="$4"
case "$name" in
(*"/"*) mkdir -p "$DIR/image/$ver/$bundle/${name%/*}" ;;
(*) mkdir -p $DIR/image/$ver/$bundle ;;
esac
echo "$content" > $DIR/image/$ver/$bundle/"$name"
}
# vi: ft=sh ts=8 sw=2 sts=2 et tw=80 # vi: ft=sh ts=8 sw=2 sts=2 et tw=80