mirror of
https://github.com/borgbackup/borg.git
synced 2025-01-03 05:35:58 +00:00
Added symlink support
This commit is contained in:
parent
54b0c9cb0a
commit
be649e8e52
2 changed files with 49 additions and 17 deletions
|
@ -4,6 +4,7 @@
|
||||||
import zlib
|
import zlib
|
||||||
import argparse
|
import argparse
|
||||||
import sys
|
import sys
|
||||||
|
import stat
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
|
|
||||||
import msgpack
|
import msgpack
|
||||||
|
@ -98,11 +99,17 @@ def extract(self, dest=None):
|
||||||
for item in self.items:
|
for item in self.items:
|
||||||
assert item['path'][0] not in ('/', '\\', ':')
|
assert item['path'][0] not in ('/', '\\', ':')
|
||||||
path = os.path.join(dest, item['path'])
|
path = os.path.join(dest, item['path'])
|
||||||
logging.info(path)
|
|
||||||
if item['type'] == 'DIRECTORY':
|
if item['type'] == 'DIRECTORY':
|
||||||
|
logging.info(path)
|
||||||
if not os.path.exists(path):
|
if not os.path.exists(path):
|
||||||
os.makedirs(path)
|
os.makedirs(path)
|
||||||
if item['type'] == 'FILE':
|
elif item['type'] == 'SYMLINK':
|
||||||
|
logging.info('%s => %s', path, item['source'])
|
||||||
|
if not os.path.exists(os.path.dirname(path)):
|
||||||
|
os.makedirs(os.path.dirname(path))
|
||||||
|
os.symlink(item['source'], path)
|
||||||
|
elif item['type'] == 'FILE':
|
||||||
|
logging.info(path)
|
||||||
if not os.path.exists(os.path.dirname(path)):
|
if not os.path.exists(os.path.dirname(path)):
|
||||||
os.makedirs(os.path.dirname(path))
|
os.makedirs(os.path.dirname(path))
|
||||||
with open(path, 'wb') as fd:
|
with open(path, 'wb') as fd:
|
||||||
|
@ -144,34 +151,48 @@ def delete(self, cache):
|
||||||
del cache.archives[self.name]
|
del cache.archives[self.name]
|
||||||
cache.save()
|
cache.save()
|
||||||
|
|
||||||
|
def walk(self, path):
|
||||||
|
st = os.lstat(path)
|
||||||
|
if stat.S_ISDIR(st.st_mode):
|
||||||
|
for f in os.listdir(path):
|
||||||
|
for x in self.walk(os.path.join(path, f)):
|
||||||
|
yield x
|
||||||
|
else:
|
||||||
|
yield path, st
|
||||||
|
|
||||||
def create(self, name, paths, cache):
|
def create(self, name, paths, cache):
|
||||||
if name in cache.archives:
|
if name in cache.archives:
|
||||||
raise NameError('Archive already exists')
|
raise NameError('Archive already exists')
|
||||||
for path in paths:
|
for path in paths:
|
||||||
for root, dirs, files in os.walk(unicode(path)):
|
for path, st in self.walk(unicode(path)):
|
||||||
for d in dirs:
|
if stat.S_ISDIR(st.st_mode):
|
||||||
p = os.path.join(root, d)
|
self.process_dir(path, st)
|
||||||
self.items.append(self.process_dir(p, cache))
|
elif stat.S_ISLNK(st.st_mode):
|
||||||
for f in files:
|
self.process_link(path, st)
|
||||||
p = os.path.join(root, f)
|
elif stat.S_ISREG(st.st_mode):
|
||||||
entry = self.process_file(p, cache)
|
self.process_file(path, st)
|
||||||
if entry:
|
else:
|
||||||
self.items.append(entry)
|
logging.error('Unknown file type: %s', path)
|
||||||
self.save(name)
|
self.save(name)
|
||||||
cache.archives[name] = self.id
|
cache.archives[name] = self.id
|
||||||
cache.save()
|
cache.save()
|
||||||
|
|
||||||
def process_dir(self, path, cache):
|
def process_dir(self, path, st):
|
||||||
path = path.lstrip('/\\:')
|
path = path.lstrip('/\\:')
|
||||||
logging.info(path)
|
logging.info(path)
|
||||||
return {'type': 'DIRECTORY', 'path': path}
|
self.items.append({'type': 'DIRECTORY', 'path': path})
|
||||||
|
|
||||||
def process_file(self, path, cache):
|
def process_link(self, path, st):
|
||||||
|
source = os.readlink(path)
|
||||||
|
path = path.lstrip('/\\:')
|
||||||
|
logging.info('%s => %s', path, source)
|
||||||
|
self.items.append({'type': 'SYMLINK', 'path': path, 'source': source})
|
||||||
|
|
||||||
|
def process_file(self, path, st):
|
||||||
try:
|
try:
|
||||||
fd = open(path, 'rb')
|
fd = open(path, 'rb')
|
||||||
except IOError, e:
|
except IOError, e:
|
||||||
logging.error(e)
|
logging.error(e)
|
||||||
return
|
|
||||||
with fd:
|
with fd:
|
||||||
path = path.lstrip('/\\:')
|
path = path.lstrip('/\\:')
|
||||||
logging.info(path)
|
logging.info(path)
|
||||||
|
@ -179,8 +200,8 @@ def process_file(self, path, cache):
|
||||||
size = 0
|
size = 0
|
||||||
for chunk in chunkify(fd, CHUNK_SIZE, 30):
|
for chunk in chunkify(fd, CHUNK_SIZE, 30):
|
||||||
size += len(chunk)
|
size += len(chunk)
|
||||||
chunks.append(self.add_chunk(*cache.add_chunk(chunk)))
|
chunks.append(self.add_chunk(*self.cache.add_chunk(chunk)))
|
||||||
return {'type': 'FILE', 'path': path, 'chunks': chunks, 'size': size}
|
self.items.append({'type': 'FILE', 'path': path, 'chunks': chunks, 'size': size})
|
||||||
|
|
||||||
|
|
||||||
class Archiver(object):
|
class Archiver(object):
|
||||||
|
|
|
@ -42,6 +42,17 @@ def test_corrupted_store(self):
|
||||||
fd.close()
|
fd.close()
|
||||||
self.dedupestore('verify', self.store_path + '::test', exit_code=1)
|
self.dedupestore('verify', self.store_path + '::test', exit_code=1)
|
||||||
|
|
||||||
|
def test_symlinks(self):
|
||||||
|
testdir = os.path.join(self.tmpdir, 'linktest')
|
||||||
|
os.mkdir(testdir)
|
||||||
|
os.symlink('/tmp/somewhere', os.path.join(testdir, 'link'))
|
||||||
|
self.dedupestore('create', self.store_path + '::symlinktest', testdir)
|
||||||
|
dest_dir = os.path.join(self.tmpdir, 'dest')
|
||||||
|
self.dedupestore('extract', self.store_path + '::symlinktest', dest_dir)
|
||||||
|
dest = os.path.join(dest_dir, testdir[1:])
|
||||||
|
self.assertEqual(os.path.islink(os.path.join(dest, 'link')), True)
|
||||||
|
self.assertEqual(os.readlink(os.path.join(dest, 'link')), '/tmp/somewhere')
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
unittest.main()
|
unittest.main()
|
||||||
|
|
Loading…
Reference in a new issue