Revision 7e8045d8
Added by David Sorber almost 3 years ago
| software/fss-parallel-tools/LICENSE | ||
|---|---|---|
|
Copyright (c) 2016, Oracle and/or its affiliates. All rights reserved.
|
||
|
|
||
|
The Universal Permissive License (UPL), Version 1.0
|
||
|
|
||
|
Subject to the condition set forth below, permission is hereby granted to any
|
||
|
person obtaining a copy of this software, associated documentation and/or data
|
||
|
(collectively the "Software"), free of charge and under any and all copyright
|
||
|
rights in the Software, and any and all patent rights owned or freely licensable
|
||
|
by each licensor hereunder covering either (i) the unmodified Software as
|
||
|
contributed to or provided by such licensor, or (ii) the Larger Works (as
|
||
|
defined below), to deal in both
|
||
|
|
||
|
(a) the Software, and
|
||
|
(b) any piece of software and/or hardware listed in the lrgrwrks.txt file if one
|
||
|
is included with the Software (each a “Larger Work” to which the Software is
|
||
|
contributed by such licensors),
|
||
|
|
||
|
without restriction, including without limitation the rights to copy, create
|
||
|
derivative works of, display, perform, and distribute the Software and make,
|
||
|
use, sell, offer for sale, import, export, have made, and have sold the Software
|
||
|
and the Larger Work(s), and to sublicense the foregoing rights on either these
|
||
|
or other terms.
|
||
|
|
||
|
This license is subject to the following condition:
|
||
|
|
||
|
The above copyright notice and either this complete permission notice or at a
|
||
|
minimum a reference to the UPL must be included in all copies or substantial
|
||
|
portions of the Software.
|
||
|
|
||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||
|
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||
|
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
||
|
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||
|
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||
|
|
||
| software/fss-parallel-tools/Makefile | ||
|---|---|---|
|
##
|
||
|
# Copyright © 2019, Oracle and/or its affiliates. All rights reserved.
|
||
|
#
|
||
|
# Licensed under the Universal Permissive License v 1.0 as shown at
|
||
|
# http://oss.oracle.com/licenses/upl.
|
||
|
#
|
||
|
# Update this (and the VERSION_HISTORY in version.h) when the version changes.
|
||
|
VERSION = 1.58
|
||
|
|
||
|
#SAN = -fsanitize=thread
|
||
|
#SAN = -fsanitize=address
|
||
|
#GCOV_FLAGS = -fprofile-arcs -ftest-coverage
|
||
|
CFLAGS=-Wall -Wextra -Wshadow -Wconversion -Wpointer-arith -Wmissing-prototypes -Wwrite-strings -Wswitch-enum -Werror -g -O1 -pthread -std=gnu99 $(SAN) $(GCOV_FLAGS)
|
||
|
CPPFLAGS=-DVERSION=\"$(VERSION)\"
|
||
|
#CPPFLAGS+=-DSPEW_ENABLED
|
||
|
|
||
|
# It turns out that we need -lrt (probably because of clock_gettime()
|
||
|
# or else we'll end up with a dependency on glibc 2.17, which often is
|
||
|
# too advanced (e.g., on Oracle Linux 6.9)
|
||
|
LDLIBS=-lrt
|
||
|
LDFLAGS=-pthread $(SAN) $(GCOV_FLAGS)
|
||
|
|
||
|
VALGRIND = valgrind --leak-check=full --error-exitcode=3 -q
|
||
|
|
||
|
PARTAR = partar
|
||
|
|
||
|
default: $(PARTAR) parcp parrm $(PARTAR).1.gz parcp.1.gz parrm.1.gz
|
||
|
|
||
|
# Don't delete intermediate files (specify .SECONDARY with no dependencies)
|
||
|
.SECONDARY:
|
||
|
|
||
|
rpms: ../fss-parallel-tools-$(VERSION).tar.gz fss-parallel-tools.spec
|
||
|
mkdir -p ~/rpmbuild/SOURCES
|
||
|
mkdir -p ~/rpmbuild/SPECS
|
||
|
cp ../fss-parallel-tools-$(VERSION).tar.gz ~/rpmbuild/SOURCES
|
||
|
cp fss-parallel-tools.spec ~/rpmbuild/SPECS
|
||
|
cd ~/rpmbuild/SPECS;rpmbuild -ba fss-parallel-tools.spec
|
||
|
fss-parallel-tools.spec: fss-parallel-tools.spec.in Makefile
|
||
|
sed s/VERSION_DEFINED_IN_MAKEFILE/$(VERSION)/ < $< > $@
|
||
|
mkfile_path := $(abspath $(lastword $(MAKEFILE_LIST)))
|
||
|
current_dir := $(notdir $(patsubst %/,%,$(dir $(mkfile_path))))
|
||
|
SOURCES = gitrev.c $(wildcard *.c *.h common/*.c common/*.h) Makefile LICENSE
|
||
|
|
||
|
../fss-parallel-tools-$(VERSION).tar.gz: $(SOURCES)
|
||
|
tar czf $@ --transform 's,^,fss-parallel-tools/,' $(SOURCES)
|
||
|
tarball: ../fss-parallel-tools-$(VERSION).tar.gz
|
||
|
|
||
|
todo.o: todo.h
|
||
|
dircache.o: dircache.h malloc.h
|
||
|
htable.o: htable.h malloc.h
|
||
|
fwg.o: malloc.h fwg.h hashfun.h
|
||
|
stringset.o: stringset.h hashfun.h malloc.h
|
||
|
|
||
|
####################
|
||
|
# COMMON
|
||
|
####################
|
||
|
# sources
|
||
|
common/assert.o: common/assert.h common/backtrace.h common/spew.h version.h
|
||
|
COMMON_O += common/assert.o
|
||
|
COMMON_O += common/backtrace.o
|
||
|
common/backtrace.o: common/spew.h common/simplest-defs.h common/sparse.h common/backtrace.h
|
||
|
COMMON_O += common/debug-tag.o
|
||
|
common/debug-tag.o: common/debug-tag.h common/simplest-defs.h common/sparse.h
|
||
|
COMMON_O += common/error.o
|
||
|
common/error.o: common/error.h common/simplest-defs.h common/sparse.h
|
||
|
COMMON_O += common/spew.o
|
||
|
common/spew.o: common/spew.h common/backtrace.h common/simplest-defs.h common/tsan.h version.h common/debug-tag.h common/sparse.h common/error.h
|
||
|
|
||
|
fdleak.o: fdleak.h
|
||
|
putils.o: putils.h common/spew.h
|
||
|
parcp.o: putils.h malloc.h version.h sched.h vector-of-strings.h common/spew.h
|
||
|
$(PARTAR).o: putils.h malloc.h version.h sched.h vector-of-strings.h inodemap.h stringset.h vec.h fdleak.h fwg.h common/spew.h
|
||
|
parrm.o: putils.h malloc.h version.h sched.h vector-of-strings.h common/spew.h
|
||
|
vector-of-strings.o: vector-of-strings.h malloc.h
|
||
|
$(PARTAR): $(PARTAR).o putils.o sched.o vector-of-strings.o gitrev.o fwg.o stringset.o vec.o fdleak.o inodemap.o $(COMMON_O)
|
||
|
inodemap.o: inodemap.h malloc.h
|
||
|
parcp: parcp.o putils.o sched.o vector-of-strings.o gitrev.o inodemap.o todo.o $(COMMON_O)
|
||
|
parrm: parrm.o putils.o gitrev.o sched.o vector-of-strings.o $(COMMON_O)
|
||
|
|
||
|
.DELETE_ON_ERROR:
|
||
|
|
||
|
.SUFFIX: .1.gz
|
||
|
%.1.gz: %
|
||
|
PATH=$$PATH:. help2man -N --name="parallel implementation of $(subst par,,$<)" $< | gzip - > $@
|
||
|
|
||
|
gitrev.o: gitrev.c
|
||
|
# Don't create a new gitrev.c file if nothing changed. We don't want
|
||
|
# to rerun all the tests just because I modified the Makefile.
|
||
|
gitrev.c: $(filter-out gitrev.c,$(wildcard *.c *.h common/*.c common/*.h Makefile))
|
||
|
echo \#include \"version.h\" > gitrev_new.c
|
||
|
echo const char gitrev[] = \"$$(git diff-index --quiet HEAD -- || echo "++")$$(git rev-parse HEAD)\"\; >> gitrev_new.c
|
||
|
if ! diff gitrev_new.c gitrev.c >> /dev/null ; then mv gitrev_new.c gitrev.c; fi
|
||
|
setup: $(PARTAR)
|
||
|
rm -rf tar-1.30
|
||
|
|
||
|
# Tests
|
||
|
|
||
|
.PHONY: check
|
||
|
TESTS = fifo tar-1.30 linux-4.15 longfilename3
|
||
|
TESTS_T = $(patsubst %, %.test, $(TESTS))
|
||
|
check: parrm_eaccess_test.test
|
||
|
check: parrm_eaccess_test2.test
|
||
|
check: parrm_eaccess_test3.test
|
||
|
check: parrm_eaccess_test4.test
|
||
|
check: excld.test
|
||
|
check: excld-equal.test
|
||
|
check: error.test
|
||
|
check: linklink.test
|
||
|
check: permissions.test
|
||
|
check: $(TESTS_T) test_fib.run
|
||
|
check: decompression-errors.test
|
||
|
check: parcp0.test parcp1.test parcp2.test parcp3.test parcp4.test parcp5.test parcp6.test parcp7.test
|
||
|
.SUFFIX: .test
|
||
|
%.test: %.partar_test %.parcp_test %.parrm_test %.tar_test
|
||
|
touch $@
|
||
|
|
||
|
.SUFFIX: .partar_test
|
||
|
%.partar_test: %.partar1_diff %.partar2_diff %.partar3_diff %.partar4_diff partar1.test
|
||
|
touch $@
|
||
|
|
||
|
test_fib.run: test_fib
|
||
|
./test_fib
|
||
|
touch $@
|
||
|
|
||
|
$(info VAR="$(COMMON_O)")
|
||
|
|
||
|
test_fib: test_fib.o sched.o $(COMMON_O) gitrev.o
|
||
|
test_fib.o: malloc.h sched.h common/spew.h
|
||
|
sched.o: malloc.h sched.h common/spew.h
|
||
|
|
||
|
#
|
||
|
|
||
|
# These tests run partar in various ways and compare the output to what happened with tar.
|
||
|
# partar_diff1_test runs "partar pxf %.tar"
|
||
|
# partar_diff2_test runs "partar pxf - < %.tar"
|
||
|
# partar_diff3_test runs "partar pxzf %.tar.gz"
|
||
|
# partar_diff4_test runs "partar pxzf - < %.tar.gz"
|
||
|
|
||
|
# partar_test has things that are fifos in it, and we want to ignore
|
||
|
# diff error messages about those. That's what the grep is about in
|
||
|
# the diff line.
|
||
|
|
||
|
output-with-tar output-with-partar1 output-with-partar2 output-with-partar3 output-with-partar4 uncompressed-tars gzipped-tars:
|
||
|
mkdir -p $@
|
||
|
|
||
|
uncompressed-tars/%.tar: test-tars/%.tar.xz | uncompressed-tars
|
||
|
xzcat $< > $@
|
||
|
|
||
|
gzipped-tars/%.tar.gz: uncompressed-tars/%.tar | gzipped-tars
|
||
|
gzip < $< > $@
|
||
|
|
||
|
%.tar_done: uncompressed-tars/%.tar | output-with-tar
|
||
|
rm -rf $(patsubst uncompressed-tars/%.tar,output-with-tar/%, $<)
|
||
|
tar xf $< -C output-with-tar
|
||
|
touch $@
|
||
|
|
||
|
# partar1 tests makes sure that partar works with a named file
|
||
|
%.partar1_done: uncompressed-tars/%.tar $(PARTAR) | output-with-partar1
|
||
|
rm -rf $(patsubst uncompressed-tars/%.tar,output-with-partar1/%, $<)
|
||
|
$(VALGRIND) ./$(PARTAR) xf $< -C output-with-partar1
|
||
|
touch $@
|
||
|
|
||
|
%.partar1_diff: %.tar_done %.partar1_done
|
||
|
diff --no-dereference -r $(patsubst %.tar_done,output-with-tar/%,$<) $(patsubst %.tar_done,output-with-partar1/%,$<) | (! grep -v "is a fifo.*is a fifo")
|
||
|
touch $@
|
||
|
|
||
|
# partar2 tests make sure that partar works from stdin
|
||
|
%.partar2_done: uncompressed-tars/%.tar $(PARTAR) | output-with-partar2
|
||
|
rm -rf $(patsubst uncompressed-tars/%.tar,output-with-partar2/%, $<)
|
||
|
$(VALGRIND) ./$(PARTAR) xf - -C output-with-partar2 < $<
|
||
|
touch $@
|
||
|
|
||
|
%.partar2_diff: %.tar_done %.partar2_done
|
||
|
diff --no-dereference -r $(patsubst %.tar_done,output-with-tar/%,$<) $(patsubst %.tar_done,output-with-partar2/%,$<) | (! grep -v "is a fifo.*is a fifo")
|
||
|
touch $@
|
||
|
|
||
|
# partar3 tests make sure that partar works with a gzipped named file.
|
||
|
.SUFFIX: .partar3_done
|
||
|
%.partar3_done: gzipped-tars/%.tar.gz $(PARTAR) | output-with-partar3
|
||
|
rm -rf $(patsubst gzipped-tars/%.tar.gz,output-with-partar3/%, $<)
|
||
|
$(VALGRIND) ./$(PARTAR) xzf $< -C output-with-partar3
|
||
|
touch $@
|
||
|
|
||
|
.SUFFIX: .partar3_diff
|
||
|
%.partar3_diff: %.tar_done %.partar3_done
|
||
|
diff --no-dereference -r $(patsubst %.tar_done,output-with-tar/%,$<) $(patsubst %.tar_done,output-with-partar3/%,$<) | (! grep -v "is a fifo.*is a fifo")
|
||
|
touch $@
|
||
|
|
||
|
# partar4 tests make sure that partar works with a gzipped stdin
|
||
|
%.partar4_done: gzipped-tars/%.tar.gz $(PARTAR) | output-with-partar4
|
||
|
rm -rf $(patsubst gzipped-tars/%.tar.gz,output-with-partar4/%, $<)
|
||
|
$(VALGRIND) ./$(PARTAR) xzf - -C output-with-partar4 < $<
|
||
|
touch $@
|
||
|
|
||
|
%.partar4_diff: %.tar_done %.partar4_done
|
||
|
diff --no-dereference -r $(patsubst %.tar_done,output-with-tar/%,$<) $(patsubst %.tar_done,output-with-partar4/%,$<) | (! grep -v "is a fifo.*is a fifo")
|
||
|
touch $@
|
||
|
|
||
|
# Test untarring onto a read-only file
|
||
|
partar1.test: $(PARTAR) | partar1.test.dir
|
||
|
rm -rf $@.dir/src $@.dir/dst
|
||
|
mkdir -p $@.dir/src $@.dir/dst
|
||
|
touch $@.dir/dst/readonly
|
||
|
chmod guo-w $@.dir/dst/readonly
|
||
|
echo contents > $@.dir/src/readonly
|
||
|
chmod guo-w $@.dir/src/readonly
|
||
|
set -e; set -o pipefail; $(VALGRIND) ./$(PARTAR) c -C $@.dir/src readonly|$(VALGRIND) ./$(PARTAR) x -C $@.dir/dst
|
||
|
diff -r $@.dir/{src,dst}
|
||
|
touch $@
|
||
|
|
||
|
output-with-parcp output-to-rm output-of-partarred:
|
||
|
mkdir -p $@
|
||
|
|
||
|
.SUFFIX: .parcp_test
|
||
|
# Turn off valgrind for the big linux parcp
|
||
|
linux-4.15.parcp_test: VALGRIND=
|
||
|
%.parcp_test: parcp %.tar_done | output-with-parcp
|
||
|
rm -rf $(patsubst %.parcp_test,output-with-parcp/%, $@)
|
||
|
$(VALGRIND) ./parcp -P 3 $(patsubst %.parcp_test,output-with-tar/%, $@) $(patsubst %.parcp_test,output-with-parcp/%, $@)
|
||
|
diff --no-dereference -r $(patsubst %.parcp_test,output-with-tar/%, $@) $(patsubst %.parcp_test,output-with-parcp/%, $@) | (! grep -v "is a fifo.*is a fifo")
|
||
|
touch $@
|
||
|
|
||
|
.SUFFIX: .parrm_test
|
||
|
%.parrm_test: uncompressed-tars/%.tar parrm $(PARTAR) | output-to-rm
|
||
|
$(VALGRIND) ./$(PARTAR) xf - -C output-to-rm < $<
|
||
|
test -d $(patsubst %.parrm_test,output-to-rm/%,$@)
|
||
|
$(VALGRIND) ./parrm -P 3 $(patsubst %.parrm_test,output-to-rm/%,$@)
|
||
|
test ! -d $(patsubst %.parrm_test,output-to-rm/%,$@)
|
||
|
touch $@
|
||
|
|
||
|
# This test only works on a few cases, since the order of the files isn't unique
|
||
|
.SUFFIX: .tardiff_test
|
||
|
%.tardiff_test: %.tar_out %.partar_out
|
||
|
diff $^
|
||
|
touch $@
|
||
|
|
||
|
.SUFFIX: .tar_test
|
||
|
%.tar_test: %.partar_out %.tar_done | output-of-partarred
|
||
|
rm -rf $(patsubst %.partar_out,output-of-partarred/%,$<)
|
||
|
$(VALGRIND) ./$(PARTAR) xf $< -C output-of-partarred
|
||
|
diff --no-dereference -r $(patsubst %.tar_test,output-with-tar/%,$@) $(patsubst %.tar_test,output-of-partarred/%,$@) | (! grep -v "is a fifo.*is a fifo")
|
||
|
$(EXTRA_TEST)
|
||
|
|
||
|
# These two tests check to see what happens if parrm runs into a
|
||
|
# directory that it hasn't got permission to access. The whole
|
||
|
# directory, or a subdirectory, respectively.
|
||
|
|
||
|
ED = parrm_eaccess_test_dir
|
||
|
parrm_eaccess_test.test: parrm
|
||
|
-chmod u+rwx $(ED) > /dev/null 2>&1 ;true
|
||
|
rm -rf $(ED)
|
||
|
mkdir $(ED)
|
||
|
touch $(ED)/foo
|
||
|
chmod ugo-rwx $(ED)
|
||
|
rm -rf $(ED) > parrm_eaccess_test.out 2>&1 ; test $$? = 1
|
||
|
fgrep Permission parrm_eaccess_test.out > /dev/null
|
||
|
./parrm $(ED) > parrm_eaccess_test.out 2>&1 ; test $$? = 1
|
||
|
fgrep Permission parrm_eaccess_test.out > /dev/null
|
||
|
touch $@
|
||
|
|
||
|
ED2 = parrm_eaccess_test2_dir
|
||
|
parrm_eaccess_test2.test: parrm
|
||
|
chmod -R u+rwx $(ED2) > /dev/null 2>&1 ;true
|
||
|
rm -rf $(ED2)
|
||
|
mkdir -p $(ED2)/$(ED2)
|
||
|
touch $(ED2)/$(ED2)/foo
|
||
|
touch $(ED2)/foo
|
||
|
chmod ugo-rwx $(ED2)/$(ED2)
|
||
|
./parrm $(ED2) > $(ED2).out 2>&1 ; test $$? = 1
|
||
|
chmod -R u+rwx $(ED2) > /dev/null 2>&1 ;true
|
||
|
test ! -f $(ED2)/foo
|
||
|
test -f $(ED2)/$(ED2)/foo
|
||
|
touch $@
|
||
|
|
||
|
# And these are the same tests where we can access the dir but we cannot unlink it.
|
||
|
ED3 = parrm_eaccess_test3_dir
|
||
|
parrm_eaccess_test3.test: parrm
|
||
|
-chmod u+rwx $(ED3) > /dev/null 2>&1 ;true
|
||
|
rm -rf $(ED3)
|
||
|
mkdir $(ED3)
|
||
|
touch $(ED3)/foo
|
||
|
chmod ugo-w $(ED3)
|
||
|
ls -la $(ED3)
|
||
|
rm -rf $(ED3) > $(ED3).rm.out 2>&1 ; test $$? = 1
|
||
|
fgrep Permission $(ED3).rm.out > /dev/null
|
||
|
./parrm $(ED3) > $(ED3).parrm.out 2>&1 ; test $$? = 1
|
||
|
fgrep Permission $(ED3).parrm.out > /dev/null
|
||
|
touch $@
|
||
|
|
||
|
ED4 = parrm_eaccess_test4_dir
|
||
|
parrm_eaccess_test4.test: parrm
|
||
|
chmod -R u+rwx $(ED4) > /dev/null 2>&1 ;true
|
||
|
rm -rf $(ED4)
|
||
|
mkdir -p $(ED4)/$(ED4)
|
||
|
touch $(ED4)/$(ED4)/foo
|
||
|
touch $(ED4)/foo
|
||
|
chmod ugo-rx $(ED4)/$(ED4)
|
||
|
./parrm $(ED4) > $(ED4).out 2>&1 ; test $$? = 1
|
||
|
chmod -R u+rwx $(ED4) > /dev/null 2>&1 ;true
|
||
|
test ! -f $(ED4)/foo
|
||
|
test -f $(ED4)/$(ED4)/foo
|
||
|
touch $@
|
||
|
|
||
|
# Some tarballs have extra requirements that are hard to capture with diff. We'd really like to verify that the topology of two directories is the same, but I don't know how to get diff to really do it. So we'll have an ad hoc test that these two files really are the same.
|
||
|
longfilename3.tar_test: EXTRA_TEST = test output-of-partarred/longfilename3/aaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbccccccccccccccccdddddddddddddddeeeeeeeeeeeeeeffffffffffffffg -ef output-of-partarred/longfilename3/bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
|
||
|
|
||
|
.SUFFIX: .tar_out .partar_out
|
||
|
%.tar_out: %.tar_done
|
||
|
tar cf $@ $(patsubst %.tar_done,output-with-tar/%,$<)
|
||
|
%.partar_out: %.tar_done $(PARTAR)
|
||
|
(cd output-with-tar;../$(PARTAR) cf - $(patsubst %.tar_done,%,$<)) > $@
|
||
|
|
||
|
error.test: $(PARTAR)
|
||
|
! ./$(PARTAR) xf - < /dev/null 2> error.test.out
|
||
|
fgrep -q ": This does not look like a tar archive" error.test.out
|
||
|
fgrep -q ": Exiting with failure status due to previous errors" error.test.out
|
||
|
! ./$(PARTAR) xzf - < /dev/null 2> error.test.out
|
||
|
fgrep -q "gzip: stdin: unexpected end of file" error.test.out
|
||
|
fgrep -q ": Child returned status 1" error.test.out
|
||
|
fgrep -q ": Exiting with failure status due to previous errors" error.test.out
|
||
|
! ./$(PARTAR) cf error.tar output-with-tar/fifo no-such-file output-with-tar/fifo 2> error.test.out
|
||
|
fgrep -q ": no-such-file: Cannot stat: No such file or directory" error.test.out
|
||
|
fgrep -q ": Exiting with failure status due to previous errors" error.test.out
|
||
|
touch $@
|
||
|
|
||
|
# Test excluding files both in "c" mode and in "x" mode.
|
||
|
excld.test: tar-1.30.tar_done $(PARTAR)
|
||
|
rm -rf excld.dir.tar excld.dir.partar excld.odir.tar excld.odir.partar
|
||
|
mkdir excld.dir.tar excld.dir.partar excld.odir.tar excld.odir.partar
|
||
|
tar cf - -C output-with-tar/ --exclude d\?c tar-1.30 | tar xf - -C excld.dir.tar
|
||
|
$(VALGRIND) ./$(PARTAR) cf - -C output-with-tar/ --exclude d\?c tar-1.30 | tar xf - -C excld.dir.partar
|
||
|
diff -r excld.dir.tar excld.dir.partar
|
||
|
tar c -C output-with-tar/ tar-1.30 | tar x -C excld.odir.tar --exclude d\?c
|
||
|
diff -r excld.dir.tar excld.odir.tar
|
||
|
tar c -C output-with-tar/ tar-1.30 | $(VALGRIND) ./$(PARTAR) x -C excld.odir.partar --exclude d\?c
|
||
|
diff -r excld.dir.tar excld.odir.partar
|
||
|
touch $@
|
||
|
|
||
|
# Test excluding files both in "c" mode and in "x" mode same as above but uses --exclude=PATTERN.
|
||
|
excld-equal.test: tar-1.30.tar_done $(PARTAR)
|
||
|
rm -rf excld-equal.dir.tar excld-equal.dir.partar excld-equal.odir.tar excld-equal.odir.partar
|
||
|
mkdir excld-equal.dir.tar excld-equal.dir.partar excld-equal.odir.tar excld-equal.odir.partar
|
||
|
tar cf - -C output-with-tar/ --exclude=d\?c tar-1.30 | tar xf - -C excld-equal.dir.tar
|
||
|
$(VALGRIND) ./$(PARTAR) cf - -C output-with-tar/ --exclude=d\?c tar-1.30 | tar xf - -C excld-equal.dir.partar
|
||
|
diff -r excld-equal.dir.tar excld-equal.dir.partar
|
||
|
tar c -C output-with-tar/ tar-1.30 | tar x -C excld-equal.odir.tar --exclude d\?c
|
||
|
diff -r excld-equal.dir.tar excld-equal.odir.tar
|
||
|
tar c -C output-with-tar/ tar-1.30 | $(VALGRIND) ./$(PARTAR) x -C excld-equal.odir.partar --exclude=d\?c
|
||
|
diff -r excld-equal.dir.tar excld-equal.odir.partar
|
||
|
touch $@
|
||
|
|
||
|
# Test the same file appearing twice on the command line. tar makes a hardlink from the file to itself.
|
||
|
linklink.test: $(PARTAR)
|
||
|
echo a > linklink.txt
|
||
|
tar cf linklink.tar linklink.txt linklink.txt
|
||
|
$(VALGRIND) ./$(PARTAR) cf linklink.partar linklink.txt linklink.txt
|
||
|
# Don't run the diff unless we have a recent version of tar
|
||
|
if (tar --version|head -1|fgrep 1.3 > /dev/null); then diff linklink.tar linklink.partar; fi
|
||
|
mkdir -p linklink.tdir
|
||
|
tar xf linklink.tar -C linklink.tdir
|
||
|
mkdir -p linklink.pdir
|
||
|
tar xf linklink.partar -C linklink.pdir
|
||
|
diff -r linklink.tdir linklink.pdir
|
||
|
touch $@
|
||
|
|
||
|
# This isn't part of the standard tests, since it requires sudo to run properly.
|
||
|
permissions.sudo.test: $(PARTAR)
|
||
|
sudo rm -rf permissions.sudopartar.testdir permissions.sudountar.testdir
|
||
|
mkdir -p permissions.sudopartar.testdir
|
||
|
mkdir -p permissions.sudountar.testdir
|
||
|
sudo tar pxf test-tars/permissions.tar -C permissions.sudountar.testdir
|
||
|
sudo ./$(PARTAR) pxf test-tars/permissions.tar -C permissions.sudopartar.testdir
|
||
|
# for some reason tar doesn't set the mtime on partar/src/bin in those directories, so let's ignore bin.
|
||
|
(cd permissions.sudopartar.testdir; ls -lR)|sort | egrep -v "bin$$" > permissions.sudopartar.out
|
||
|
(cd permissions.sudountar.testdir; ls -lR)|sort | egrep -v "bin$$" > permissions.sudountar.out
|
||
|
diff permissions.sudopartar.out permissions.sudountar.out
|
||
|
|
||
|
# permissions test for not sudo
|
||
|
permissions.test: $(PARTAR)
|
||
|
rm -rf permissions.in.dir permissions.tar.out.dir permissions.partar.out.dir permissions.tar.ls permissions.partar.ls
|
||
|
mkdir -p permissions.in.dir/s
|
||
|
mkdir -p permissions.in.dir/s/d1
|
||
|
mkdir -p permissions.in.dir/s/d2
|
||
|
ls -l permissions.in.dir/s
|
||
|
chmod 770 permissions.in.dir/s/d1
|
||
|
chmod 750 permissions.in.dir/s/d2
|
||
|
ls -l permissions.in.dir/s
|
||
|
mkdir -p permissions.tar.out.dir
|
||
|
tar cf - -C permissions.in.dir s | tar xf - -C permissions.tar.out.dir
|
||
|
ls -l permissions.tar.out.dir/s | sort > permissions.tar.ls
|
||
|
mkdir -p permissions.partar.out.dir
|
||
|
tar cf - -C permissions.in.dir s | $(VALGRIND) ./$(PARTAR) xf - -C permissions.partar.out.dir
|
||
|
ls -l permissions.partar.out.dir/s | sort > permissions.partar.ls
|
||
|
diff permissions.tar.ls permissions.partar.ls
|
||
|
touch $@
|
||
|
|
||
|
# Test for detecting .gz and .xz inputs.
|
||
|
decompression-errors.test: ./partar
|
||
|
! ./partar xf test-tars/fifo.tar.xz 2> decompression-errors.out
|
||
|
fgrep "Input does not appear to be a tar file." decompression-errors.out > /dev/null
|
||
|
fgrep "Input appears to be xz compressed" decompression-errors.out > /dev/null
|
||
|
!(xzcat test-tars/fifo.tar.xz |gzip|./partar xf - 2> decompression-errors.out)
|
||
|
fgrep "Input does not appear to be a tar file." decompression-errors.out > /dev/null
|
||
|
fgrep "appears to be gzipped" decompression-errors.out > /dev/null
|
||
|
touch $@
|
||
|
|
||
|
%.dir:
|
||
|
mkdir $@
|
||
|
|
||
|
# Some specific tests for parcp
|
||
|
parcp0.test: parcp tar-1.30.tar_done | parcp0.test.dir
|
||
|
rm -rf $@.dir/*
|
||
|
./parcp --restore -- output-with-tar/tar-1.30 $@.dir
|
||
|
touch $@
|
||
|
|
||
|
parcp1.test: parcp
|
||
|
./parcp --version |fgrep $(VERSION) > /dev/null
|
||
|
./parcp --help |fgrep OPTION > /dev/null
|
||
|
touch $@
|
||
|
|
||
|
# test that if we copy into an existing file that is longer, that it's properly trunated
|
||
|
parcp2.test: parcp | parcp2.test.dir
|
||
|
rm -rf $@.dir/*
|
||
|
mkdir -p $@.dir/src $@.dir/dst/src
|
||
|
echo "hello" > $@.dir/src/hello
|
||
|
echo "hello there" > $@.dir/dst/src/hello
|
||
|
ls -i $@.dir/dst/src/hello > $@.dir/original.inode
|
||
|
./parcp -P 3 $@.dir/src $@.dir/dst
|
||
|
ls -i $@.dir/dst/src/hello > $@.dir/new.inode
|
||
|
diff $@.dir/src/hello $@.dir/dst/src/hello
|
||
|
diff $@.dir/new.inode $@.dir/original.inode
|
||
|
touch $@
|
||
|
|
||
|
parcp3.test: parcp | parcp3.dir
|
||
|
rm -rf $@.dir/*
|
||
|
mkdir -p $@.dir/src $@.dir/dst
|
||
|
# source and dest are the same symlink, so expect no change
|
||
|
ln -s unchanged $@.dir/src/uc
|
||
|
ln -s unchanged $@.dir/dst/uc
|
||
|
# source and dest are slightly different symlinks, the same length.
|
||
|
ln -s changed0 $@.dir/src/ch
|
||
|
ln -s changed1 $@.dir/dst/ch
|
||
|
# source and dest are different symlinks of different length.
|
||
|
ln -s shortch $@.dir/src/chlen
|
||
|
ln -s longerch $@.dir/dst/chlen
|
||
|
# source is symlink, dest is not there
|
||
|
ln -s notthere $@.dir/src/nt
|
||
|
# source is a symlink, dest is a dir
|
||
|
ln -s dest-was-dir $@.dir/src/wasdir
|
||
|
mkdir $@.dir/dst/wasdir
|
||
|
# source is a symlink, dest is a regular file.
|
||
|
ln -s dest-was-reg $@.dir/src/wasreg
|
||
|
touch $@.dir/dst/wasreg
|
||
|
#ls -l $@.dir/dst
|
||
|
ls -i parcp3.test.dir/dst/uc > $@.dir/original.uc.symlink
|
||
|
# source and dest are fifos. Make sure the inode is reused
|
||
|
mknod $@.dir/src/src_fifo_dst_fifo p
|
||
|
mknod $@.dir/dst/src_fifo_dst_fifo p
|
||
|
ls -i $@.dir/dst/src_fifo_dst_fifo > $@.dir/src_fifo_dst_fifo.original
|
||
|
# source is fifo and dest is regular. Make sure dest becomes fifo
|
||
|
mknod $@.dir/src/src_fifo_dst_reg p
|
||
|
touch $@.dir/dst/src_fifo_dst_reg
|
||
|
# source is regular and dest is fifo. Make sure dest becomes reg
|
||
|
touch $@.dir/src/src_reg_dst_fifo
|
||
|
mknod $@.dir/dst/src_reg_dst_fifo p
|
||
|
# source is file, dest is dir
|
||
|
touch $@.dir/src/src_reg_dst_dir
|
||
|
mkdir $@.dir/dst/src_reg_dst_dir
|
||
|
# source is dir, dest is file
|
||
|
mkdir $@.dir/src/src_dir_dst_reg
|
||
|
touch $@.dir/dst/src_dir_dst_reg
|
||
|
# two files that are the same inode in src, nothing in dst
|
||
|
touch $@.dir/src/src_two_inodes_dst_null.1
|
||
|
ln $@.dir/src/src_two_inodes_dst_null.1 $@.dir/src/src_two_inodes_dst_null.2
|
||
|
# two files the same inode in src, a file in dst
|
||
|
touch $@.dir/src/src_two_inodes_dst_reg.1
|
||
|
ln $@.dir/src/src_two_inodes_dst_reg.1 $@.dir/src/src_two_inodes_dst_reg.2
|
||
|
touch $@.dir/dst/src_two_inodes_dst_reg.1
|
||
|
# two files the same inode in src, two dirs in dst
|
||
|
touch $@.dir/src/src_two_inodes_dst_two_dirs.1
|
||
|
ln $@.dir/src/src_two_inodes_dst_two_dirs.1 $@.dir/src/src_two_inodes_dst_two_dirs.2
|
||
|
mkdir $@.dir/dst/src_two_inodes_dst_two_dirs.1
|
||
|
mkdir $@.dir/dst/src_two_inodes_dst_two_dirs.2
|
||
|
# three files the same inode in src, three files in dst, two of which are the same inode.
|
||
|
touch $@.dir/src/src_3_inodes_dst_3_files.1
|
||
|
ln $@.dir/src/src_3_inodes_dst_3_files.1 $@.dir/src/src_3_inodes_dst_3_files.2
|
||
|
ln $@.dir/src/src_3_inodes_dst_3_files.1 $@.dir/src/src_3_inodes_dst_3_files.3
|
||
|
touch $@.dir/dst/src_3_inodes_dst_3_files.1
|
||
|
ln $@.dir/dst/src_3_inodes_dst_3_files.1 $@.dir/dst/src_3_inodes_dst_3_files.2
|
||
|
touch $@.dir/dst/src_3_inodes_dst_3_files.3
|
||
|
# do the copy
|
||
|
$(VALGRIND) ./parcp $@.dir/src $@.dir/dst
|
||
|
ls -l $@.dir/dst
|
||
|
test -h $@.dir/dst/ch
|
||
|
ls -l $@.dir/dst/ch | fgrep "ch -> changed0"
|
||
|
ls -l $@.dir/dst/nt | fgrep "nt -> notthere"
|
||
|
ls -l $@.dir/dst/wasdir | fgrep "wasdir -> dest-was-dir"
|
||
|
ls -l $@.dir/dst/chlen | fgrep "chlen -> shortch"
|
||
|
test -h $@.dir/dst/wasreg
|
||
|
ls -l $@.dir/dst/wasreg| fgrep "wasreg -> dest-was-reg"
|
||
|
# test follows symlinks so we must do htis more carefully
|
||
|
ls -i parcp3.test.dir/dst/uc > $@.dir/new.uc.symlink
|
||
|
diff $@.dir/original.uc.symlink $@.dir/new.uc.symlink
|
||
|
ls -i $@.dir/dst/src_fifo_dst_fifo > $@.dir/src_fifo_dst_fifo.new
|
||
|
diff $@.dir/src_fifo_dst_fifo.new $@.dir/src_fifo_dst_fifo.original
|
||
|
test -p $@.dir/dst/src_fifo_dst_reg
|
||
|
test -f $@.dir/dst/src_reg_dst_fifo
|
||
|
test -f $@.dir/dst/src_reg_dst_dir
|
||
|
test -d $@.dir/dst/src_dir_dst_reg
|
||
|
test parcp3.test.dir/dst/src_two_inodes_dst_null.1 -ef parcp3.test.dir/dst/src_two_inodes_dst_null.2
|
||
|
test parcp3.test.dir/dst/src_two_inodes_dst_reg.1 -ef parcp3.test.dir/dst/src_two_inodes_dst_reg.2
|
||
|
test parcp3.test.dir/dst/src_3_inodes_dst_3_files.1 -ef parcp3.test.dir/dst/src_3_inodes_dst_3_files.2
|
||
|
test parcp3.test.dir/dst/src_3_inodes_dst_3_files.1 -ef parcp3.test.dir/dst/src_3_inodes_dst_3_files.3
|
||
|
touch $@
|
||
|
|
||
|
# Check --delete
|
||
|
parcp4.test: parcp | parcp4.test.dir
|
||
|
rm -rf $@.dir/*
|
||
|
mkdir -p $@.dir/src $@.dir/dst
|
||
|
mkdir -p $@.dir/dst/a/b/c/d/e
|
||
|
touch $@.dir/dst/a/b/c/d/e/f
|
||
|
mkdir -p $@.dir/dst/a/b/c/d/e2
|
||
|
touch $@.dir/dst/a/b/c/d/e2/f
|
||
|
mkdir -p $@.dir/src/x/y/z
|
||
|
mkdir -p $@.dir/src/a/w
|
||
|
touch $@.dir/src/a/w/foo
|
||
|
$(VALGRIND) ./parcp --delete $@.dir/src $@.dir/dst
|
||
|
diff -r $@.dir/src $@.dir/dst
|
||
|
touch $@
|
||
|
|
||
|
# Check --exclude-from
|
||
|
parcp5.test: parcp | parcp5.test.dir
|
||
|
rm -rf $@.dir/*
|
||
|
mkdir -p $@.dir/src $@.dir/dst
|
||
|
mkdir $@.dir/src/wanted
|
||
|
mkdir $@.dir/src/unwanted
|
||
|
touch $@.dir/src/wanted/file1.keep
|
||
|
touch $@.dir/src/wanted/file2.nokeep
|
||
|
touch $@.dir/src/wanted/file3.nokeep
|
||
|
echo unwanted > $@.dir/excludelist
|
||
|
echo wanted/file2.nokeep >> $@.dir/excludelist
|
||
|
echo file3.nokeep >> $@.dir/excludelist
|
||
|
$(VALGRIND) ./parcp --exclude-from=$@.dir/excludelist $@.dir/src $@.dir/dst
|
||
|
test -d $@.dir/dst/wanted
|
||
|
test -f $@.dir/dst/wanted/file1.keep
|
||
|
ls $@.dir/dst/wanted|fgrep -v file2.nokeep > /dev/null
|
||
|
ls $@.dir/dst/wanted|fgrep -v file3.nokeep > /dev/null
|
||
|
test ! -d $@.dir/dst/unwanted
|
||
|
touch $@
|
||
|
|
||
|
# Check --exclude-from with --delete
|
||
|
parcp6.test: parcp Makefile | parcp6.test.dir
|
||
|
rm -rf $@.dir/*
|
||
|
mkdir -p $@.dir/src $@.dir/dst
|
||
|
mkdir -p $@.dir/src/src_wanted
|
||
|
mkdir -p $@.dir/dst/dst_wanted
|
||
|
mkdir -p $@.dir/dst/dst_extra
|
||
|
mkdir -p $@.dir/dst/dst_extra2
|
||
|
mkdir -p $@.dir/dst/dst_kill
|
||
|
touch $@.dir/dst/dst_wanted/keepme
|
||
|
touch $@.dir/dst/dst_extra/dst_wanted_file
|
||
|
touch $@.dir/dst/dst_extra2/keep2
|
||
|
touch $@.dir/dst/dst_extra2/killme
|
||
|
touch $@.dir/dst/dst_kill/killme2
|
||
|
echo dst_wanted > $@.dir/excludelist
|
||
|
echo dst_wanted_file >> $@.dir/excludelist
|
||
|
echo dst_extra2/keep2 >> $@.dir/excludelist
|
||
|
$(VALGRIND) ./parcp --exclude-from=$@.dir/excludelist --delete $@.dir/src $@.dir/dst
|
||
|
test -f $@.dir/dst/dst_wanted/keepme
|
||
|
test -f $@.dir/dst/dst_extra/dst_wanted_file
|
||
|
test -f $@.dir/dst/dst_extra2/keep2
|
||
|
test ! -f $@.dir/dst/dst_extra2/killme
|
||
|
test ! -f $@.dir/dst/dst_kill/killme2
|
||
|
test ! -d $@.dir/dst/dst_kill
|
||
|
touch $@
|
||
|
|
||
|
# Check target contains a read-only file
|
||
|
# Three cases: 1) No such file in src
|
||
|
# 2) Exactly the same file in src
|
||
|
# 3) A different file in src
|
||
|
parcp7.test: parcp | parcp7.test.dir
|
||
|
rm -rf $@.dir/*
|
||
|
mkdir -p $@.dir/src $@.dir/dst
|
||
|
touch $@.dir/dst/no-such-file-in-src
|
||
|
chmod ugo-w $@.dir/dst/no-such-file-in-src
|
||
|
echo same in src > $@.dir/src/same-file-in-src
|
||
|
chmod ugo-w $@.dir/src/same-file-in-src
|
||
|
cp -a $@.dir/src/same-file-in-src $@.dir/dst/same-file-in-src
|
||
|
echo diffrent in src > $@.dir/src/diff-file-in-src
|
||
|
chmod ugo-w $@.dir/src/diff-file-in-src
|
||
|
echo diffrent in dst dst > $@.dir/dst/diff-file-in-src
|
||
|
chmod ugo-w $@.dir/dst/diff-file-in-src
|
||
|
ls -l $@.dir/{src,dst}
|
||
|
$(VALGRIND) ./parcp --delete $@.dir/src $@.dir/dst
|
||
|
ls -l $@.dir/{src,dst}
|
||
|
test ! -f $@.dir/dst/no-such-file-in-src
|
||
|
test -f $@.dir/dst/diff-file-in-src
|
||
|
test -f $@.dir/dst/same-file-in-src
|
||
|
diff -r $@.dir/{src,dst}
|
||
|
touch $@
|
||
|
|
||
|
parcp8.test: parcp | parcp7.test.dir
|
||
|
rm -rf $@.dir/*
|
||
|
mkdir -p $@.dir/src $@.dir/dst
|
||
|
touch $@.dir/src/rws-file
|
||
|
chmod +sx $@.dir/src/rws-file
|
||
|
./parcp $@.dir/src $@.dir/dst
|
||
|
test -x parcp8.test.dir/dst/rws-file
|
||
|
test -u parcp8.test.dir/dst/rws-file
|
||
|
echo touch $@
|
||
|
|
||
|
.PHONY: clean
|
||
|
clean:
|
||
|
rm -rf parrm parcp $(PARTAR) parrm.1.gz parcp.1.gz $(PARTAR).1.gz *.o output-with-tar output-to-rm output-with-parcp *.tar_done *.parrm_test *.parcp_test *.partar_test
|
||
|
rm -rf gitrev.c
|
||
|
rm -rf output-with-partar{1,2,3,4} output-of-partarred
|
||
|
rm -rf uncompressed-tars gzipped-tars
|
||
|
rm -rf *.partar{1,2,3,4}_diff
|
||
|
rm -rf *.partar{1,2,3,4}_done
|
||
|
rm -rf excld.dir.*tar excld.test
|
||
|
rm -rf excld-equal.dir.*tar excld-equal.test
|
||
|
if test -n "$$(find . -maxdepth 1 -name 'parrm_eaccess_test*_dir' -print -quit)"; then chmod -R u+rwx parrm_eaccess_test*_dir; fi
|
||
|
rm -rf parrm_eaccess_test*{.out,_dir}
|
||
|
rm -rf error.test.out error.tar linklink.tar linklink.partar linklink.tdir linklink.pdir linklink.txt excld.odir.partar excld.odir.tar
|
||
|
rm -rf permissions.in.dir permissions.tar.out.dir permissions.partar.out.dir permissions.tar.ls permissions.partar.ls
|
||
|
rm -rf *.ls *.test *.tar_test
|
||
|
rm -rf *.partar_out *.tar_out test1.out *.out
|
||
|
rm -rf test_fin
|
||
|
rm -f *.gcda *.gcno common/*.gcda common/*.gcno
|
||
|
rm -rf *.dir/
|
||
|
rm -rf *.1.gz
|
||
|
rm -f $(COMMON_O)
|
||
|
rm -rf partar_log
|
||
|
|
||
|
.PHONY: realclean
|
||
|
realclean: clean
|
||
| software/fss-parallel-tools/atdiff.c | ||
|---|---|---|
|
/**
|
||
|
* Copyright © 2019, Oracle and/or its affiliates. All rights reserved.
|
||
|
*
|
||
|
* Licensed under the Universal Permissive License v 1.0 as shown at
|
||
|
* http://oss.oracle.com/licenses/upl
|
||
|
*
|
||
|
*/
|
||
|
#include <assert.h>
|
||
|
#include <sys/stat.h>
|
||
|
#include <pthread.h>
|
||
|
#include <dirent.h>
|
||
|
#include <stdio.h>
|
||
|
#include <stdlib.h>
|
||
|
#include <string.h>
|
||
|
#include <unistd.h>
|
||
|
#include "putils.h"
|
||
|
#include "malloc.h"
|
||
|
/* parallel diff of a directory heirarchy that also checks ownership and attributes */
|
||
|
|
||
|
struct diff_task {
|
||
|
char *relative_path;
|
||
|
struct diff_task *next;
|
||
|
};
|
||
|
|
||
|
// Worker state
|
||
|
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER; ;
|
||
|
pthread_cond_t cond = PTHREAD_COND_INITIALIZER;
|
||
|
struct diff_task *tasks = NULL;
|
||
|
enum { n_workers = 32};
|
||
|
size_t n_working = 0; // When tasks==NULL and n_working==0, we are done
|
||
|
pthread_t workers[n_workers];
|
||
|
|
||
|
#define PMAX 4096
|
||
|
const char *src_dir;
|
||
|
const char *dst_dir;
|
||
|
|
||
|
int check_ownership = 1;
|
||
|
|
||
|
// From the qsort man page:
|
||
|
static int cmpstringp(const void *p1, const void *p2) {
|
||
|
// The actual arguments to this function are "pointers to pointers
|
||
|
// to char", but strcmp(3) arguments are "pointers to char", hence
|
||
|
// the following cast plus dereference.
|
||
|
return strcmp(* (char * const *) p1, * (char * const *) p2);
|
||
|
}
|
||
|
|
||
|
static void collect_and_sort_pathnames(const char *abs_path,
|
||
|
// output:
|
||
|
size_t *np,
|
||
|
char ***paths_p)
|
||
|
// Collect all the pathnames in a directory, except for . and .., and
|
||
|
// return them in sorted order.
|
||
|
{
|
||
|
size_t n = 0;
|
||
|
size_t limit = 2;
|
||
|
char **MALLOC_N(paths, limit);
|
||
|
DIR *dir = opendir(abs_path);
|
||
|
assert(dir);
|
||
|
struct dirent *dentry;
|
||
|
while ((dentry = readdir(dir))) {
|
||
|
if (strcmp(dentry->d_name, ".") == 0) continue;
|
||
|
if (strcmp(dentry->d_name, "..") == 0) continue;
|
||
|
if (n >= limit) {
|
||
|
limit *= 2;
|
||
|
REALLOC(paths, limit);
|
||
|
}
|
||
|
assert(n < limit);
|
||
|
paths[n++] = strdup(dentry->d_name);
|
||
|
}
|
||
|
closedir(dir);
|
||
|
qsort(paths, n, sizeof(char*), cmpstringp);
|
||
|
*np = n;
|
||
|
*paths_p = paths;
|
||
|
}
|
||
|
|
||
|
static void diff_r(const char *relative_path) {
|
||
|
char src_path[PMAX];
|
||
|
{
|
||
|
int n = snprintf(src_path, PMAX, "%s/%s", src_dir, relative_path);
|
||
|
assert(n < PMAX);
|
||
|
}
|
||
|
char dst_path[PMAX];
|
||
|
{
|
||
|
int n = snprintf(dst_path, PMAX, "%s/%s", dst_dir, relative_path);
|
||
|
assert(n < PMAX);
|
||
|
}
|
||
|
struct stat src_stat, dst_stat;
|
||
|
{
|
||
|
int src_r = lstat(src_path, &src_stat);
|
||
|
assert (src_r == 0);
|
||
|
}
|
||
|
{
|
||
|
int dst_r = lstat(dst_path, &dst_stat);
|
||
|
assert(dst_r == 0);
|
||
|
}
|
||
|
assert((src_stat.st_mode & S_IFMT) == (dst_stat.st_mode & S_IFMT));
|
||
|
{
|
||
|
mode_t mask = 0777;
|
||
|
if ((src_stat.st_mode & mask) != (dst_stat.st_mode & mask)) {
|
||
|
fprintf(stderr, "masks differ %s and %s\n", src_path, dst_path);
|
||
|
assert(0);
|
||
|
}
|
||
|
}
|
||
|
if (check_ownership) {
|
||
|
if (src_stat.st_uid != dst_stat.st_uid) {
|
||
|
fprintf(stderr, "uid doesn't match for %s and %s\n", src_path, dst_path);
|
||
|
}
|
||
|
assert(src_stat.st_uid == dst_stat.st_uid);
|
||
|
assert(src_stat.st_gid == dst_stat.st_gid);
|
||
|
}
|
||
|
if (S_ISREG(dst_stat.st_mode)) {
|
||
|
assert(src_stat.st_size == dst_stat.st_size);
|
||
|
FILE *src_f = fopen(src_path, "r");
|
||
|
FILE *dst_f = fopen(dst_path, "r");
|
||
|
if (src_f == NULL || dst_f == NULL) {
|
||
|
fprintf(stderr, "Failed to open one or more files\n");
|
||
|
return;
|
||
|
}
|
||
|
for (size_t i = 0 ; i < (size_t)(src_stat.st_size); i++) {
|
||
|
int src_c = getc(src_f); assert(src_c != EOF);
|
||
|
int dst_c = getc(dst_f); assert(dst_c != EOF);
|
||
|
assert(src_c == dst_c);
|
||
|
}
|
||
|
fclose(src_f);
|
||
|
fclose(dst_f);
|
||
|
} else if (S_ISDIR(src_stat.st_mode)) {
|
||
|
size_t sn, dn;
|
||
|
char **sps, **dps;
|
||
|
collect_and_sort_pathnames(src_path, &sn, &sps);
|
||
|
collect_and_sort_pathnames(dst_path, &dn, &dps);
|
||
|
assert(sn == dn);
|
||
|
for (size_t i = 0; i < sn; i++) {
|
||
|
assert(strcmp(sps[i], dps[i]) == 0);
|
||
|
struct diff_task *MALLOC(task);
|
||
|
char sub_rel_path[PMAX];
|
||
|
{
|
||
|
int n = snprintf(sub_rel_path, PMAX, "%s/%s", relative_path, sps[i]);
|
||
|
assert(n < PMAX);
|
||
|
}
|
||
|
char *srp = strdup(sub_rel_path);
|
||
|
pthread_mutex_lock(&mutex);
|
||
|
*task = (struct diff_task){.relative_path = srp,
|
||
|
.next = tasks};
|
||
|
tasks = task;
|
||
|
pthread_cond_signal(&cond);
|
||
|
pthread_mutex_unlock(&mutex);
|
||
|
}
|
||
|
} else if (S_ISLNK(src_stat.st_mode)) {
|
||
|
char slname[PMAX];
|
||
|
ssize_t slen = readlink(src_path, slname, PMAX);
|
||
|
assert(slen >= 0 && slen < PMAX);
|
||
|
char dlname[PMAX];
|
||
|
ssize_t dlen = readlink(dst_path, dlname, PMAX);
|
||
|
assert(dlen >= 0 && dlen < PMAX);
|
||
|
assert(slen == dlen);
|
||
|
slname[slen] = 0;
|
||
|
dlname[dlen] = 0;
|
||
|
if (0 != strcmp(slname, dlname)) {
|
||
|
fprintf(stderr, "%s -> %s is not the same as %s -> %s\n", src_path, slname, dst_path, dlname);
|
||
|
assert(0);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
static void* run_worker(void *d) {
|
||
|
pthread_mutex_lock(&mutex);
|
||
|
while (1) {
|
||
|
// Lock is held here.
|
||
|
while (!tasks && n_working) {
|
||
|
pthread_cond_wait(&cond, &mutex);
|
||
|
}
|
||
|
// Either there is a task or n_working==0
|
||
|
if (tasks) {
|
||
|
struct diff_task *task = tasks;
|
||
|
tasks = task->next;
|
||
|
n_working++;
|
||
|
pthread_mutex_unlock(&mutex);
|
||
|
diff_r(task->relative_path);
|
||
|
free(task->relative_path);
|
||
|
free(task);
|
||
|
pthread_mutex_lock(&mutex);
|
||
|
n_working--;
|
||
|
if (tasks == NULL && n_working == 0) {
|
||
|
pthread_cond_broadcast(&cond);
|
||
|
}
|
||
|
} else if (n_working == 0) {
|
||
|
// No tasks and nothing to do.
|
||
|
pthread_mutex_unlock(&mutex);
|
||
|
return d;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
static void run_workers(void) {
|
||
|
MALLOC(tasks);
|
||
|
*tasks = (struct diff_task){.relative_path = strdup(""),
|
||
|
.next = NULL};
|
||
|
n_working = 0;
|
||
|
for (size_t i = 0; i < n_workers; i++) {
|
||
|
pthread_create(&workers[i], NULL, run_worker, &workers[i]);
|
||
|
}
|
||
|
for (size_t i = 0; i < n_workers; i++) {
|
||
|
void *result;
|
||
|
pthread_join(workers[i], &result);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
int main (int argc, const char *argv[]) {
|
||
|
assert(argc == 3);
|
||
|
src_dir = argv[1];
|
||
|
dst_dir = argv[2];
|
||
|
run_workers();
|
||
|
return 0;
|
||
|
}
|
||
| software/fss-parallel-tools/common/assert.c | ||
|---|---|---|
|
#include "assert.h"
|
||
|
#include <inttypes.h> // for PRIx64
|
||
|
#include <stddef.h> // for size_t
|
||
|
#include <stdint.h> // for uint64_t
|
||
|
#include <stdlib.h> // for abort
|
||
|
#include <stdarg.h> // for va_end
|
||
|
#include "backtrace.h" // for spew_backtrace
|
||
|
#include "spew.h" // for log0, ::SPEW_FATAL, the_spew_log
|
||
|
#include "../version.h"
|
||
|
|
||
|
void assert_fail(const char *assertion,
|
||
|
const char *file, size_t line, const char *function)
|
||
|
{
|
||
|
log0(&the_spew_log, /*honor_spewage_suppression=*/0, /*maybe_roll=*/1,
|
||
|
/*is_stack_trace=*/0,
|
||
|
SPEW_FATAL, file, line, function,
|
||
|
"ASSERTION FAILURE: %s", assertion);
|
||
|
log0(&the_spew_log, /*honor_spewage_suppression=*/0, /*maybe_roll=*/1,
|
||
|
/*is_stack_trace=*/0,
|
||
|
SPEW_FATAL, file, line, function, "GITREV %s", gitrev);
|
||
|
spew_backtrace(SPEW_FATAL);
|
||
|
abort();
|
||
|
}
|
||
|
|
||
|
|
||
|
void assert_fail_int(const char *assertion, const char *str_var, int var,
|
||
|
const char *file, size_t line, const char *function)
|
||
|
{
|
||
|
log0(&the_spew_log, /*honor_spewage_suppression=*/0, /*maybe_roll=*/1,
|
||
|
/*is_stack_trace=*/0,
|
||
|
SPEW_FATAL, file, line, function, "ASSERTION FAILURE: %s, %s=%d",
|
||
|
assertion, str_var, var);
|
||
|
spew_backtrace(SPEW_FATAL);
|
||
|
abort();
|
||
|
}
|
||
|
|
||
|
|
||
|
void assert_fail_v(const char *assertion,
|
||
|
const char *file,
|
||
|
size_t line,
|
||
|
const char *function,
|
||
|
const char *fmt_string,
|
||
|
...) {
|
||
|
va_list args;
|
||
|
#ifndef __CHECKER__
|
||
|
// va_start() contains a cast to void * (from char const **, in
|
||
|
// this case), which runs afoul of sparse's check for qualifier
|
||
|
// drops.
|
||
|
//
|
||
|
va_start(args, fmt_string);
|
||
|
#endif // !__CHECKER__
|
||
|
log0(&the_spew_log, /*honor_spewage_suppression=*/0, /*maybe_roll=*/1,
|
||
|
/*is_stack_trace=*/0,
|
||
|
SPEW_FATAL, file, line, function,
|
||
|
"ASSERTION FAILURE: %s", assertion);
|
||
|
log0(&the_spew_log, /*honor_spewage_suppression=*/0, /*maybe_roll=*/1,
|
||
|
/*is_stack_trace=*/0,
|
||
|
SPEW_FATAL, file, line, function, "GITREV %s", gitrev);
|
||
|
logv(&the_spew_log, /*honor_spewage_suppression=*/0, /*maybe_roll=*/1,
|
||
|
/*is_stack_trace=*/0,
|
||
|
SPEW_FATAL, file, line, function, fmt_string, args);
|
||
|
spew_backtrace(SPEW_FATAL);
|
||
|
abort();
|
||
|
va_end(args);
|
||
|
}
|
||
|
|
||
|
void check_eq_u(uintmax_t a, uintmax_t b,
|
||
|
const char *file, size_t line, const char *function,
|
||
|
const char *assertion)
|
||
|
{
|
||
|
if (a != b) {
|
||
|
log0(&the_spew_log, /*honor_spewage_suppression=*/0, /*maybe_roll=*/1,
|
||
|
/*is_stack_trace=*/0,
|
||
|
SPEW_FATAL, file, line, function,
|
||
|
"ASSERTION FAILURE: [%s]: 0x%"PRIxMAX" != 0x%"PRIxMAX,
|
||
|
assertion, a, b);
|
||
|
log0(&the_spew_log, /*honor_spewage_suppression=*/0, /*maybe_roll=*/1,
|
||
|
/*is_stack_trace=*/0,
|
||
|
SPEW_FATAL, file, line, function, "GITREV %s", gitrev);
|
||
|
spew_backtrace(SPEW_FATAL);
|
||
|
abort();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
void check_eq_i(intmax_t a, intmax_t b,
|
||
|
const char *file, size_t line, const char *function,
|
||
|
const char *assertion)
|
||
|
{
|
||
|
if (a != b) {
|
||
|
log0(&the_spew_log, /*honor_spewage_suppression=*/0, /*maybe_roll=*/1,
|
||
|
/*is_stack_trace=*/0,
|
||
|
SPEW_FATAL, file, line, function,
|
||
|
"ASSERTION FAILURE [%s]: %"PRIiMAX" != %"PRIiMAX,
|
||
|
assertion, a, b);
|
||
|
log0(&the_spew_log, /*honor_spewage_suppression=*/0, /*maybe_roll=*/1,
|
||
|
/*is_stack_trace=*/0,
|
||
|
SPEW_FATAL, file, line, function, "GITREV %s", gitrev);
|
||
|
spew_backtrace(SPEW_FATAL);
|
||
|
abort();
|
||
|
}
|
||
|
}
|
||
| software/fss-parallel-tools/common/assert.h | ||
|---|---|---|
|
#ifndef __COMMON_ASSERT_H__
|
||
|
#define __COMMON_ASSERT_H__
|
||
|
|
||
|
#include <stddef.h> // for size_t
|
||
|
#include <stdint.h> // for uint64_t
|
||
|
#include "simplest-defs.h" // for STRINGIFY
|
||
|
|
||
|
// Use CHECK for checking something that you really want checked.
|
||
|
//
|
||
|
// Use ASSERT for checking something that you want checked, but the
|
||
|
// functionality of the program won't be compromised if the predicate isn't
|
||
|
// checked, or even run. It should be correct to define ASSERT as "#define
|
||
|
// ASSERT(x) ((void)0)". This means that you should not put side effects into
|
||
|
// ASSERT predicates.
|
||
|
//
|
||
|
// Use ASSERT_WITH_SIDE_EFFECTS for checking something you want checked, but
|
||
|
// there may be side effects in the predicate, so we need need to evaluate the
|
||
|
// predicate even if NOASSERT is set.
|
||
|
//
|
||
|
// See wisdom.tex for more discussion.
|
||
|
|
||
|
#ifndef CHECK_ASSERT_SIDE_EFFECTS
|
||
|
#define CHECK_ASSERT_SIDE_EFFECTS 0
|
||
|
#endif
|
||
|
|
||
|
#ifdef NOASSERT
|
||
|
#define ASSERT(x) ((0) ? ((void)(x), (void)(0)) : (void)(0))
|
||
|
#define ASSERT0 ASSERT
|
||
|
#define ASSERTv(x, typeof_val, val) ((0) ? ((void)(x), (void)(val), (void)(0)) : (void)(0))
|
||
|
#define ASSERT_EQ_u(a, b) ((0) ? ((void)(a), (void)(b), (void)(0)) \
|
||
|
: (void)(0))
|
||
|
#define ASSERT_EQ_i(a, b) ((0) ? ((void)(a), (void)(b), (void)(0)) \
|
||
|
: (void)(0))
|
||
|
#define ASSERT_WITH_SIDE_EFFECTS(expr) ((void)expr)
|
||
|
#elif !CHECK_ASSERT_SIDE_EFFECTS // && !NOASSERT
|
||
|
#define ASSERT CHECK
|
||
|
#define ASSERT0 CHECK0
|
||
|
#define ASSERTv CHECKv
|
||
|
#define ASSERT_EQ_u CHECK_EQ_u
|
||
|
#define ASSERT_EQ_i CHECK_EQ_i
|
||
|
#define ASSERT_WITH_SIDE_EFFECTS ASSERT
|
||
|
#else // CHECK_ASSERT_SIDE_EFFECTS && !NOASSERT
|
||
|
// See https://stackoverflow.com/questions/10593492/catching-assert-with-side-effects
|
||
|
// for this trick for noticing that you have an ASSERT with side effects.
|
||
|
extern int not_needed_if_expr_has_no_side_effects;
|
||
|
#define ASSERT(expr) ((void)(not_needed_if_expr_has_no_side_effects || (expr)))
|
||
|
#define ASSERT0(expr) ASSERT((expr) == 0)
|
||
|
#define ASSERTv(x, typeof_val, val) ASSERT(x)
|
||
|
#define ASSERT_EQ_u(a, b) ASSERT((a) == (b))
|
||
|
#define ASSERT_EQ_i(a, b) ASSERT((a) == (b))
|
||
|
#define ASSERT_WITH_SIDE_EFFECTS(expr) ((void)(expr))
|
||
|
#endif // CHECK_ASSERT_SIDE_EFFECTS && !NOASSERT
|
||
|
|
||
|
#define CHECK(x) ((x) ? (void) (0) : assert_fail (STRINGIFY(x), __FILE__, __LINE__, __PRETTY_FUNCTION__))
|
||
|
#define CHECK0(x) CHECK((x) == 0)
|
||
|
/* Check and print variable of specified type. */
|
||
|
#define CHECKv(x, typeof_val, val) ((x) ? (void) (0) : assert_fail_ ## typeof_val \
|
||
|
(STRINGIFY(x), #val, val, __FILE__, __LINE__, __PRETTY_FUNCTION__))
|
||
|
#define CHECKvv(x, fmt_args...) ((x) ? (void)(0) : assert_fail_v(STRINGIFY(x), __FILE__, __LINE__, __PRETTY_FUNCTION__, fmt_args) )
|
||
|
#define UNREACHABLE() CHECK("UNREACHABLE" && 0)
|
||
|
|
||
|
void assert_fail(const char *assertion,
|
||
|
const char *file, size_t line, const char *function)
|
||
|
__attribute__ ((__noreturn__));
|
||
|
void assert_fail_int(const char *assertion, const char *str_var, int var,
|
||
|
const char *file, size_t line, const char *function)
|
||
|
__attribute__ ((__noreturn__));
|
||
|
void assert_fail_v(const char *assertion,
|
||
|
const char *file,
|
||
|
size_t line,
|
||
|
const char *function,
|
||
|
const char *fmt_string,
|
||
|
...)
|
||
|
__attribute__ ((__noreturn__))
|
||
|
__attribute__ ((format (printf, 5, 6)));
|
||
|
void
|
||
|
check_eq_u(uintmax_t a, uintmax_t b,
|
||
|
const char *file, size_t line, const char *function,
|
||
|
const char *assertion);
|
||
|
|
||
|
#define CHECK_EQ_u(a, b) ( \
|
||
|
check_eq_u((uintmax_t)(a), (uintmax_t)(b), \
|
||
|
__FILE__, __LINE__, __PRETTY_FUNCTION__, \
|
||
|
STRINGIFY(a) " == " STRINGIFY(b)))
|
||
|
|
||
|
|
||
|
void
|
||
|
check_eq_i(intmax_t a, intmax_t b,
|
||
|
const char *file, size_t line, const char *function,
|
||
|
const char *assertion);
|
||
|
|
||
|
#define CHECK_EQ_i(a, b) ( \
|
||
|
check_eq_i((intmax_t)(a), (intmax_t)(b), \
|
||
|
__FILE__, __LINE__, __PRETTY_FUNCTION__, \
|
||
|
STRINGIFY(a) " == " STRINGIFY(b)))
|
||
|
|
||
|
|
||
|
#endif
|
||
| software/fss-parallel-tools/common/backtrace.c | ||
|---|---|---|
|
#define _GNU_SOURCE
|
||
|
#include "backtrace.h"
|
||
|
#include <execinfo.h> // for backtrace, backtrace_symbols
|
||
|
#include <fcntl.h> // for open, O_CLOEXEC, O_RDONLY
|
||
|
#include <pthread.h> // for pthread_mutex_lock, pthread_mute...
|
||
|
#include <spawn.h> // for posix_spawn_file_actions_t, posi...
|
||
|
#include <stddef.h> // for ptrdiff_t
|
||
|
#include <stdio.h> // for snprintf, sscanf
|
||
|
#include <stdlib.h> // for size_t, NULL, abort, free, strtoull
|
||
|
#include <string.h> // for memset, strrchr
|
||
|
#include <sys/wait.h> // for waitpid
|
||
|
#include <unistd.h> // for close, read, pipe2, ssize_t, rea...
|
||
|
#include "simplest-defs.h" // for NELEM, UNUSED
|
||
|
#include "sparse.h" // for __force
|
||
|
#include "spew.h" // for spew, SPEW_DEBUG, SPEW_INFO, SPE...
|
||
|
|
||
|
/* DO NOT USE ASSERT() IN THIS FILE, TO AVOID INFINITE RECURSION */
|
||
|
#ifdef ASSERT
|
||
|
#error CANNOT_USE_ASSERT_IN_THIS_FILE
|
||
|
#endif
|
||
|
|
||
|
// I'm using pthread_mutex_t rather than xthread_mutex_t because I cannot
|
||
|
// think of a better way to do this than with a static variable. And with a
|
||
|
// static variable, I want to use PTHREAD_MUTEX_INITIALIZER, which there
|
||
|
// doesn't seem to have a xthread_mutex analogue.
|
||
|
|
||
|
static pthread_mutex_t backtrace_mutex = PTHREAD_MUTEX_INITIALIZER;
|
||
|
|
||
|
// Backtracer wants to fork an "addr2line" process to be able to translate
|
||
|
// code offsets into human-readable strings. Back in the dark ages, we would
|
||
|
// do the fork whenever we wanted a backtrace. That turned out to be too
|
||
|
// expensive, since fork() would copy huge page table (covering many gigabytes
|
||
|
// of memory), and it was silly because the exec() would just throw it away.
|
||
|
//
|
||
|
// Our next solution used a constructor/destructor. At the beginning of the
|
||
|
// program we would start up the backtracer. At that point in time the page
|
||
|
// table was small, and we didn't really care if it was expensive since
|
||
|
// nothing was really happening yet. However we don't really like the
|
||
|
// constructor/destructor stuff, since it's fragile. (And Matteo doesn't like
|
||
|
// it because it's a desugared version of C++ :-)
|
||
|
//
|
||
|
// This solution uses posix_spawnp() to do a fork/exec operation.
|
||
|
// posix_spawnp() lets you do some housekeeping (with dup2 and close
|
||
|
// operations) between the fork and the spawn. Because the kernel gets all
|
||
|
// the information about the fork/dup/close/spawn sequence at once, it can
|
||
|
// avoid copying the page tables. The performance of this sequence is about
|
||
|
// 200us to 500us.
|
||
|
|
||
|
struct backtracer {
|
||
|
int broken; // true if something went wrong and the backtracer
|
||
|
// is failing. Fall back to not printing
|
||
|
// backtraces, and try to keep running.
|
||
|
pid_t child; // The addr2line process
|
||
|
int pipe_to_child; // The write end of a pipe that goes the
|
||
|
// addr2line's stdin.
|
||
|
int pipe_from_child; // The read end of a pipe that comes from
|
||
|
// addr2line's stdout.
|
||
|
size_t va_base; // The offset to be subtracted from addresses to
|
||
|
// to ASLR.
|
||
|
};
|
||
|
|
||
|
static int __attribute__((warn_unused_result)) // NEGATIVE_ON_ERROR
|
||
|
sinit(posix_spawn_file_actions_t *file_actions)
|
||
|
// Effect: Initialize the file_actions. If anything goes wrong return -1.
|
||
|
{
|
||
|
int r = posix_spawn_file_actions_init(file_actions);
|
||
|
if (r != 0) {
|
||
|
spew(SPEW_DEBUG, "Cannot spawn_file_actions_init: r=%d", r);
|
||
|
return -1;
|
||
|
} else {
|
||
|
return 0;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
static int __attribute__((warn_unused_result)) // NEGATIVE_ON_ERROR
|
||
|
sdup2(posix_spawn_file_actions_t *file_actions, int from, int to)
|
||
|
// Effect: Record a dup2() operation to be done when posix_spawn runs. If
|
||
|
// anything goes wrong, spew a diagnostic and return -1;
|
||
|
{
|
||
|
int r = posix_spawn_file_actions_adddup2(file_actions, from, to);
|
||
|
if (r != 0) {
|
||
|
spew(SPEW_DEBUG, "Cannot spawn_fileactions_adddup2: r=%d", r);
|
||
|
return -1;
|
||
|
} else {
|
||
|
return 0;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
static int __attribute__((warn_unused_result)) // NEGATIVE_ON_ERROR
|
||
|
sclose(posix_spawn_file_actions_t *file_actions, int fd)
|
||
|
// Effect: Record a close() operation to be cone when posix_spawn runs. If
|
||
|
// anything goes wrong, spew a diagnostic and return -1;
|
||
|
{
|
||
|
int r = posix_spawn_file_actions_addclose(file_actions, fd);
|
||
|
if (r != 0) {
|
||
|
spew(SPEW_DEBUG, "Cannot spawn_fileactions_addclose: r=%d", r);
|
||
|
return -1;
|
||
|
} else {
|
||
|
return 0;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
static const struct backtracer errresult = {
|
||
|
.pipe_to_child=-1, .pipe_from_child=-1, .child=0, .broken=1, .va_base = 0};
|
||
|
|
||
|
static struct backtracer
|
||
|
mk_backtracer(void) {
|
||
|
// Only one backtracer may exist at a time (to prevent forkbombing if all
|
||
|
// threads start backtracing.) This mutex is unlocked when you destroy
|
||
|
// the backtracer.
|
||
|
pthread_mutex_lock(&backtrace_mutex);
|
||
|
|
||
|
// Plumbing
|
||
|
int tochildpipe[2];
|
||
|
int fromchildpipe[2];
|
||
|
{
|
||
|
int r = pipe2(tochildpipe, O_CLOEXEC);
|
||
|
if (r) {
|
||
|
spew(SPEW_ERROR, "pipe2 failed");
|
||
|
}
|
||
|
}
|
||
|
{
|
||
|
int r = pipe2(fromchildpipe, O_CLOEXEC);
|
||
|
if (r) {
|
||
|
spew(SPEW_ERROR, "pipe2 failed");
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Find the path to the current executable. It's a symlink in
|
||
|
// /proc/self/exe.
|
||
|
|
||
|
char exec_path[204];
|
||
|
// readlink doesn't null terminate. Fill it with zeros and tell it to
|
||
|
// only use sizeof(exec_path)-1 to make sure there is a null at the
|
||
|
// end.
|
||
|
memset(exec_path, 0, sizeof(exec_path));
|
||
|
ssize_t rc = readlink("/proc/self/exe", exec_path,
|
||
|
sizeof(exec_path)-1);
|
||
|
UNUSED(rc);
|
||
|
spew(SPEW_INFO, "exec path is %s", exec_path);
|
||
|
|
||
|
// Set up to do the plumbing after the spawn.
|
||
|
posix_spawn_file_actions_t file_actions;
|
||
|
if (0
|
||
|
|| sinit(&file_actions)
|
||
|
|| sdup2(&file_actions, tochildpipe[0], 0)
|
||
|
|| sdup2(&file_actions, fromchildpipe[1], 1)
|
||
|
|| sclose(&file_actions, tochildpipe[0])
|
||
|
|| sclose(&file_actions, tochildpipe[1])
|
||
|
|| sclose(&file_actions, fromchildpipe[0])
|
||
|
|| sclose(&file_actions, fromchildpipe[1]))
|
||
|
{
|
||
|
return errresult;
|
||
|
}
|
||
|
pid_t child;
|
||
|
{
|
||
|
// Hack around because posix_spawnp doesn't have const's in quite the right place.
|
||
|
char addr2line_s[] = "addr2line";
|
||
|
char _e_s[] = "-e";
|
||
|
char _f_s[] = "-f";
|
||
|
char * const argv[] = {addr2line_s, _e_s, exec_path, _f_s, NULL};
|
||
|
int r = posix_spawnp(&child, "addr2line",
|
||
|
&file_actions, NULL,
|
||
|
argv, environ);
|
||
|
if (r != 0) {
|
||
|
spew(SPEW_DEBUG, "Could not posix_spawnp r=%d", r);
|
||
|
return errresult;
|
||
|
}
|
||
|
}
|
||
|
{
|
||
|
int r = posix_spawn_file_actions_destroy(&file_actions);
|
||
|
if (r) spew(SPEW_FATAL, "Cannot destroy fail_actions r=%d", r);
|
||
|
}
|
||
|
|
||
|
// Plumbing back here the main program.
|
||
|
struct backtracer result = {.broken = 0,
|
||
|
.child = child,
|
||
|
.pipe_to_child = tochildpipe[1],
|
||
|
.pipe_from_child = fromchildpipe[0],
|
||
|
.va_base = 0};
|
||
|
close(tochildpipe[0]);
|
||
|
close(fromchildpipe[1]);
|
||
|
|
||
|
// Find the va_base
|
||
|
{
|
||
|
int fd = open("/proc/self/maps", O_RDONLY);
|
||
|
if (fd >= 0) {
|
||
|
char addr_buffer[256];
|
||
|
// HACK: we're going to assume that the first line in the
|
||
|
// file contains the mapping for our executable. Dynamic
|
||
|
// libraries will be mapped in different locations, but we
|
||
|
// don't really use any of them so our traceback should be
|
||
|
// mostly correct.
|
||
|
ssize_t bytes_read = read(fd, &addr_buffer, NELEM(addr_buffer)-1);
|
||
|
if (bytes_read > 0) {
|
||
|
addr_buffer[bytes_read] = 0;
|
||
|
int rval = sscanf(addr_buffer, "%zx-", &result.va_base);
|
||
|
if (rval != 1) {
|
||
|
result.va_base = 0;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
close(fd);
|
||
|
}
|
||
|
spew(SPEW_INFO, "va_base is %zx", result.va_base);
|
||
|
if (result.va_base < 0x800000) {
|
||
|
// If ASLR is off, subtracting va_base makes offsets that addr2line
|
||
|
// doesn't understand. The best heuristic I've (jmp) found is that if
|
||
|
// va_base is "low", then ASLR is off. I haven't been able to figure
|
||
|
// out if there's a "low" address under which ASLR is disabled, but
|
||
|
// I've seen 4 MiB in many systems, so I'm going to claim any va_base
|
||
|
// under 8 MiB is in a non-ASLR system.
|
||
|
result.va_base = 0;
|
||
|
}
|
||
|
|
||
|
return result;
|
||
|
}
|
||
|
|
||
|
static void
|
||
|
backtracer_destroy(struct backtracer *bt) {
|
||
|
close(bt->pipe_to_child);
|
||
|
close(bt->pipe_from_child);
|
||
|
spew(SPEW_INFO, "waitpid(%d)", bt->child);
|
||
|
int wstatus;
|
||
|
waitpid(bt->child, &wstatus, 0);
|
||
|
spew(SPEW_INFO, "waitpid(%d) done wstatus=%d", bt->child, wstatus);
|
||
|
*bt = errresult;
|
||
|
pthread_mutex_unlock(&backtrace_mutex);
|
||
|
}
|
||
|
|
||
|
static void read_tail_of_line(int fd, char *line, size_t linelen)
|
||
|
// Effect: Read a line of data from fd into line. If the line is too long to
|
||
|
// fit, then put the last characters in so that if we read
|
||
|
// "longfilename/fun.c:25" and linelen is 11 we end up with "e/fun.c:25"
|
||
|
// rather than "longfilena".
|
||
|
// line will always end NUL-terminated.
|
||
|
// Requires: linelen>0. This function will fail unpredictably if linelen==0.
|
||
|
{
|
||
|
for (size_t i = 0; i+1 < linelen; i++) {
|
||
|
char c;
|
||
|
ssize_t rr = read(fd, &c, 1);
|
||
|
if (0 == rr || '\n' == c) {
|
||
|
line[i] = 0;
|
||
|
return;
|
||
|
} else {
|
||
|
line[i] = c;
|
||
|
}
|
||
|
}
|
||
|
while (1) {
|
||
|
char c;
|
||
|
ssize_t rr = read(fd, &c, 1);
|
||
|
if (0 == rr || '\n' == c) {
|
||
|
line[linelen-1] = 0;
|
||
|
return;
|
||
|
} else {
|
||
|
line[linelen-1] = c;
|
||
|
// Shift the string left by 1.o
|
||
|
for (size_t j = 0; j+1 < linelen; j++) {
|
||
|
line[j] = line[j+1];
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
static void spew_backtrace_symbol(struct backtracer *bt,
|
||
|
const void *addr,
|
||
|
const char *from_backtrace_symbol,
|
||
|
size_t spew_level)
|
||
|
{
|
||
|
void *addr_from_base = (void*)((__force size_t)addr
|
||
|
- bt->va_base);
|
||
|
char str_of_ptr[20];
|
||
|
char fun_line[300] = "";
|
||
|
char file_line[300] = "";
|
||
|
if (!bt->broken) {
|
||
|
int len = snprintf(str_of_ptr, sizeof(str_of_ptr),
|
||
|
"%p\n", addr_from_base);
|
||
|
ssize_t wr = write(bt->pipe_to_child, str_of_ptr,
|
||
|
(__force size_t)(ptrdiff_t)len);
|
||
|
int did_write = wr == len;
|
||
|
if (!did_write) {
|
||
|
bt->broken = 1;
|
||
|
} else {
|
||
|
read_tail_of_line(bt->pipe_from_child, fun_line, sizeof(fun_line));
|
||
|
read_tail_of_line(bt->pipe_from_child, file_line, sizeof(file_line));
|
||
|
}
|
||
|
}
|
||
|
if (!from_backtrace_symbol) {
|
||
|
from_backtrace_symbol = "(unknown offset)";
|
||
|
}
|
||
|
const char* function = &fun_line[0];
|
||
|
if (!fun_line[0]) {
|
||
|
function = "(unknown function)";
|
||
|
}
|
||
|
size_t line_number = 0;
|
||
|
const char* filename = &file_line[0];
|
||
|
if (*filename) {
|
||
|
// Find line number by looking for last : in file_line.
|
||
|
char* line_number_start = strrchr(file_line, ':');
|
||
|
if (line_number_start != NULL) {
|
||
|
// Parse the line number
|
||
|
line_number = strtoull(line_number_start + 1, NULL, 10);
|
||
|
if (line_number > 0) {
|
||
|
// Parsed, remove the line number and : from file_line.
|
||
|
*line_number_start = 0;
|
||
|
}
|
||
|
}
|
||
|
} else {
|
||
|
// No filename, substitute backtrace symbol for filename.
|
||
|
filename = from_backtrace_symbol;
|
||
|
from_backtrace_symbol = "";
|
||
|
}
|
||
|
spew_for_backtrace(
|
||
|
spew_level, filename, line_number, function, from_backtrace_symbol);
|
||
|
}
|
||
|
|
||
|
void spew_stored_backtrace(void *const*ptrs, int nptrs, size_t spew_level)
|
||
|
{
|
||
|
char **strings = backtrace_symbols(ptrs, nptrs);
|
||
|
|
||
|
struct backtracer bt = mk_backtracer();
|
||
|
|
||
|
for (int j = 0; j < nptrs; j++) {
|
||
|
spew_backtrace_symbol(&bt,
|
||
|
ptrs[(__force ptrdiff_t)j],
|
||
|
strings ? strings[(__force ptrdiff_t)j] : NULL,
|
||
|
spew_level);
|
||
|
}
|
||
|
|
||
|
backtracer_destroy(&bt);
|
||
|
|
||
|
free(strings);
|
||
|
}
|
||
|
|
||
|
void spew_backtrace(size_t spew_level)
|
||
|
{
|
||
|
if (spew_level < spew_get_level()) return;
|
||
|
void *buffer[100];
|
||
|
int nptrs = backtrace(buffer, (__force int)NELEM(buffer));
|
||
|
|
||
|
/* cannot assert in this file */
|
||
|
if (nptrs >= 0 && nptrs <= (__force int)NELEM(buffer)) {
|
||
|
spew_stored_backtrace(buffer, nptrs, spew_level);
|
||
|
} else {
|
||
|
abort(); // can't happen
|
||
|
}
|
||
|
}
|
||
| software/fss-parallel-tools/common/backtrace.h | ||
|---|---|---|
|
#ifndef __BACKTRACE_H__
|
||
|
#define __BACKTRACE_H__
|
||
|
|
||
|
#include <stdlib.h> // For size_t
|
||
|
|
||
|
/* Fancier backtrace facility.
|
||
|
*/
|
||
|
void spew_backtrace(size_t spew_level);
|
||
|
void spew_stored_backtrace(void *const*ptrs,
|
||
|
// This is an int, rather than a size_t because
|
||
|
// I cannot ASSERT that nelem fits in an int
|
||
|
// inside the spew code.
|
||
|
int nelem,
|
||
|
size_t spew_level);
|
||
|
|
||
|
#endif // !__SPEW_H__
|
||
Adding Orcale's fss-parallel-tools taken from
fss-parallel-tools-1.58-1.el8.src.rpm.