... | ... |
@@ -16,6 +16,12 @@ import base64 |
16 | 16 |
import errno |
17 | 17 |
import urllib |
18 | 18 |
|
19 |
+try: |
|
20 |
+ import xattr |
|
21 |
+ have_xattr = True |
|
22 |
+except: |
|
23 |
+ have_xattr = False |
|
24 |
+ |
|
19 | 25 |
from logging import debug, info, warning, error |
20 | 26 |
|
21 | 27 |
|
... | ... |
@@ -227,6 +233,18 @@ def mktmpfile(prefix = "/tmp/tmpfile-", randchars = 20): |
227 | 227 |
__all__.append("mktmpfile") |
228 | 228 |
|
229 | 229 |
def hash_file_md5(filename): |
230 |
+ md5_xattr = Config.Config().md5_xattr |
|
231 |
+ if have_xattr and md5_xattr is not None: |
|
232 |
+ try: |
|
233 |
+ md5sum = xattr.get(filename, md5_xattr, namespace=xattr.NS_USER) |
|
234 |
+ debug("xattr.get(%s, %s) returned %s" % (filename, md5_xattr, md5sum)) |
|
235 |
+ return md5sum |
|
236 |
+ except: |
|
237 |
+ pass |
|
238 |
+ return hash_file_md5_io(filename) |
|
239 |
+__all__.append("hash_file_md5") |
|
240 |
+ |
|
241 |
+def hash_file_md5_io(filename): |
|
230 | 242 |
h = md5() |
231 | 243 |
f = open(filename, "rb") |
232 | 244 |
while True: |
... | ... |
@@ -237,7 +255,6 @@ def hash_file_md5(filename): |
237 | 237 |
h.update(data) |
238 | 238 |
f.close() |
239 | 239 |
return h.hexdigest() |
240 |
-__all__.append("hash_file_md5") |
|
241 | 240 |
|
242 | 241 |
def mkdir_with_parents(dir_name): |
243 | 242 |
""" |
... | ... |
@@ -12,6 +12,7 @@ import re |
12 | 12 |
from subprocess import Popen, PIPE, STDOUT |
13 | 13 |
import locale |
14 | 14 |
import pwd |
15 |
+import xattr |
|
15 | 16 |
|
16 | 17 |
count_pass = 0 |
17 | 18 |
count_fail = 0 |
... | ... |
@@ -25,8 +26,10 @@ verbose = False |
25 | 25 |
|
26 | 26 |
if os.name == "posix": |
27 | 27 |
have_wget = True |
28 |
+ have_md5sum = True |
|
28 | 29 |
elif os.name == "nt": |
29 | 30 |
have_wget = False |
31 |
+ have_md5sum = False |
|
30 | 32 |
else: |
31 | 33 |
print "Unknown platform: %s" % os.name |
32 | 34 |
sys.exit(1) |
... | ... |
@@ -404,6 +407,15 @@ test_s3cmd("Don't check MD5", ['sync', 'testsuite/', 's3://%s/xyz/' % bucket(1), |
404 | 404 |
test_s3cmd("Check MD5", ['sync', 'testsuite/', 's3://%s/xyz/' % bucket(1), '--no-encrypt', '--check-md5'], |
405 | 405 |
must_find = [ "cksum1.txt" ]) |
406 | 406 |
|
407 |
+## ====== Check MD5 sum on Sync with Extended Attributes |
|
408 |
+if 'xattr' in sys.modules.keys() and have_md5sum: |
|
409 |
+ os.system("echo 1234566789012 > testsuite/checksum/cksum4.txt") |
|
410 |
+ test_s3cmd("Overwrite cksum1.txt", ['put', 'testsuite/checksum/cksum4.txt', 's3://%s/xyz/checksum/cksum1.txt' % bucket(1), '--no-encrypt' ]) |
|
411 |
+ os.system("rm testsuite/checksum/cksum4.txt") |
|
412 |
+ os.system("attr -s md5sum -V $(md5sum testsuite/checksum/cksum1.txt | awk '{print $1}') testsuite/checksum/cksum1.txt > /dev/null") |
|
413 |
+ test_s3cmd("Check MD5 with xattr", ['sync', 'testsuite/', 's3://%s/xyz/' % bucket(1), '--no-encrypt', '--check-md5', '--xattr=md5sum'], |
|
414 |
+ must_find = [ "cksum1.txt" ]) |
|
415 |
+ |
|
407 | 416 |
|
408 | 417 |
## ====== Rename within S3 |
409 | 418 |
test_s3cmd("Rename within S3", ['mv', '%s/xyz/etc/logo.png' % pbucket(1), '%s/xyz/etc2/Logo.PNG' % pbucket(1)], |
... | ... |
@@ -26,6 +26,8 @@ import socket |
26 | 26 |
import shutil |
27 | 27 |
import tempfile |
28 | 28 |
import S3.Exceptions |
29 |
+try: import xattr |
|
30 |
+except: pass |
|
29 | 31 |
|
30 | 32 |
from copy import copy |
31 | 33 |
from optparse import OptionParser, Option, OptionValueError, IndentedHelpFormatter |
... | ... |
@@ -1806,6 +1808,8 @@ def main(): |
1806 | 1806 |
optparser.add_option( "--skip-existing", dest="skip_existing", action="store_true", help="Skip over files that exist at the destination (only for [get] and [sync] commands).") |
1807 | 1807 |
optparser.add_option("-r", "--recursive", dest="recursive", action="store_true", help="Recursive upload, download or removal.") |
1808 | 1808 |
optparser.add_option( "--check-md5", dest="check_md5", action="store_true", help="Check MD5 sums when comparing files for [sync]. (default)") |
1809 |
+ if 'xattr' in sys.modules.keys(): |
|
1810 |
+ optparser.add_option ("--xattr", dest="md5_xattr", metavar="ATTR", action="store", default=None, help="If possible, use extended file attribute ATTR from the user namespace containing the md5sum for the file, instead of calculating it [sync].") |
|
1809 | 1811 |
optparser.add_option( "--no-check-md5", dest="check_md5", action="store_false", help="Do not check MD5 sums when comparing files for [sync]. Only size will be compared. May significantly speed up transfer but may also miss some changed files.") |
1810 | 1812 |
optparser.add_option("-P", "--acl-public", dest="acl_public", action="store_true", help="Store objects with ACL allowing read for anyone.") |
1811 | 1813 |
optparser.add_option( "--acl-private", dest="acl_public", action="store_false", help="Store objects with default ACL allowing access for you only.") |
... | ... |
@@ -1988,6 +1992,7 @@ def main(): |
1988 | 1988 |
## Special handling for tri-state options (True, False, None) |
1989 | 1989 |
cfg.update_option("enable", options.enable) |
1990 | 1990 |
cfg.update_option("acl_public", options.acl_public) |
1991 |
+ cfg.update_option("md5_xattr", options.md5_xattr) |
|
1991 | 1992 |
|
1992 | 1993 |
## Check multipart chunk constraints |
1993 | 1994 |
if cfg.multipart_chunk_size_mb < MultiPartUpload.MIN_CHUNK_SIZE_MB: |
... | ... |
@@ -165,6 +165,9 @@ Check MD5 sums when comparing files for [sync]. (default) |
165 | 165 |
\fB\-\-no\-check\-md5\fR |
166 | 166 |
Do not check MD5 sums when comparing files for [sync]. Only size will be compared. May significantly speed up transfer but may also miss some changed files. |
167 | 167 |
.TP |
168 |
+\fB-\-xattr\fR=ATTR |
|
169 |
+If possible, use extended file attribute ATTR from the user namespace containing the md5sum for the file, instead of calculating it [sync]. |
|
170 |
+.TP |
|
168 | 171 |
\fB\-P\fR, \fB\-\-acl\-public\fR |
169 | 172 |
Store objects with ACL allowing read for anyone. |
170 | 173 |
.TP |