2
# Copyright (C) 2008-2009 Oprea Dan, Bart de Koning, Richard Bailey
4
# This program is free software; you can redistribute it and/or modify
5
# it under the terms of the GNU General Public License as published by
6
# the Free Software Foundation; either version 2 of the License, or
7
# (at your option) any later version.
9
# This program is distributed in the hope that it will be useful,
10
# but WITHOUT ANY WARRANTY; without even the implied warranty of
11
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12
# GNU General Public License for more details.
14
# You should have received a copy of the GNU General Public License along
15
# with this program; if not, write to the Free Software Foundation, Inc.,
16
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
32
def get_backintime_path( path ):
33
return os.path.join( os.path.dirname( os.path.abspath( os.path.dirname( __file__ ) ) ), path )
36
def register_backintime_path( path ):
37
path = get_backintime_path( path )
38
if not path in sys.path:
39
sys.path = [path] + sys.path
42
def read_file( path, default_value = None ):
43
ret_val = default_value
55
def read_file_lines( path, default_value = None ):
56
ret_val = default_value
60
ret_val = file.readlines()
68
def read_command_output( cmd ):
72
pipe = os.popen( cmd )
73
ret_val = pipe.read().strip()
81
def check_command( cmd ):
87
if os.path.isfile( cmd ):
90
cmd = read_command_output( "which \"%s\"" % cmd )
95
if os.path.isfile( cmd ):
101
def make_dirs( path ):
102
path = path.rstrip( os.sep )
106
if not os.path.isdir( path ):
113
def process_exists( name ):
114
output = read_command_output( "ps -o pid= -C %s" % name )
115
return len( output ) > 0
118
def check_x_server():
119
return 0 == os.system( 'xdpyinfo >/dev/null 2>&1' )
122
def prepare_path( path ):
123
path = path.strip( "/" )
128
def power_status_available():
129
"""Uses the on_ac_power command to detect if the the system is able
130
to return the power status."""
132
rt = subprocess.call( 'on_ac_power' )
133
if rt == ON_AC or rt == ON_BATTERY:
141
"""Checks if the system is on battery power."""
142
if power_status_available ():
143
return subprocess.call ( 'on_ac_power' ) == ON_BATTERY
148
def get_snapshots_list_in_folder( folder, sort_reverse = True ):
153
biglist = os.listdir( folder )
161
#print item + ' ' + str(len( item ))
162
if len( item ) != 15 and len( item ) != 19:
164
if os.path.isdir( os.path.join( folder, item, 'backup' ) ):
168
list.sort( reverse = sort_reverse )
172
def get_nonsnapshots_list_in_folder( folder, sort_reverse = True ):
177
biglist = os.listdir( folder )
185
#print item + ' ' + str(len( item ))
186
if len( item ) != 15 and len( item ) != 19:
189
if os.path.isdir( os.path.join( folder, item, 'backup' ) ):
195
list.sort( reverse = sort_reverse )
199
def move_snapshots_folder( old_folder, new_folder ):
200
'''Moves all the snapshots from one folder to another'''
201
print "\nMove snapshots from %s to %s" %( old_folder, new_folder )
203
# Fetch a list with snapshots for verification
204
snapshots_to_move = get_snapshots_list_in_folder( old_folder )
205
snapshots_already_there = []
206
if os.path.exists( new_folder ) == True:
207
snapshots_already_there = get_snapshots_list_in_folder( new_folder )
209
tools.make_dirs( new_folder )
210
print "To move: %s" % snapshots_to_move
211
print "Already there: %s" % snapshots_already_there
212
snapshots_expected = snapshots_to_move + snapshots_already_there
213
print "Snapshots expected: %s" % snapshots_expected
215
# Check if both folders are within the same os
216
device_old = os.stat( old_folder ).st_dev
217
device_new = os.stat( new_folder ).st_dev
218
if device_old == device_new:
220
for snapshot in snapshots_to_move:
221
cmd = "mv -f \"%s/%s\" \"%s\"" %( old_folder, snapshot, new_folder )
226
if len( snapshots_already_there ) > 0:
227
first_snapshot_path = os.path.join( new_folder, snapshots_to_move[ len( snapshots_to_move ) - 1 ] )
228
snapshot_to_hardlink_path = os.path.join( new_folder, snapshots_already_there[0] )
229
cmd = "cp -al \"%s\" \"%s\"" % ( snapshot_to_hardlink_path, first_snapshot_path )
233
nonsnapshots = get_nonsnapshots_list_in_folder( old_folder )
234
print "Nonsnapshots: %s" % nonsnapshots
236
for nonsnapshot in nonsnapshots:
238
if nonsnapshot == item:
240
items.append( "--exclude=\"%s\"" % nonsnapshot )
241
rsync_exclude = ' '.join( items )
245
cmd = "rsync -aEAXHv --delete " + old_folder + " " + new_folder + " " + rsync_exclude
249
snapshots_not_moved = []
250
for snapshot in snapshots_to_move:
251
if os.path.exists( os.path.join( new_folder, snapshot, "backup" ) ):
252
if os.path.exists( os.path.join( old_folder, snapshot) ):
253
print "Remove: %s" %snapshot
254
path_to_remove = os.path.join( old_folder, snapshot )
255
cmd = "find \"%s\" -type d -exec chmod u+wx {} \\;" % path_to_remove #Debian patch
257
cmd = "rm -rfv \"%s\"" % path_to_remove
260
print "%s was already removed" %snapshot
262
snapshots_not_moved.append( snapshot )
264
# Check snapshot list
265
if len( snapshots_not_moved ) == 0:
269
print "Error! Not moved: %s\n" %snapshots_not_moved
273
def _execute( cmd, callback = None, user_data = None ):
277
ret_val = os.system( cmd )
279
pipe = os.popen( cmd, 'r' )
282
line = temp_failure_retry( pipe.readline )
285
callback( line.strip(), user_data )
287
ret_val = pipe.close()
292
print "Command \"%s\" returns %s" % ( cmd, ret_val )
294
print "Command \"%s\" returns %s" % ( cmd, ret_val )
299
def is_process_alive( pid ):
301
os.kill( pid, 0 ) #this will raise an exception if the pid is not valid
308
def get_rsync_caps():
309
data = read_command_output( 'rsync --version' )
310
si = data.find( 'Capabilities:' )
313
si = data.find( '\n', si )
316
ei = data.find( '\n\n', si )
320
data = data[ si + 1 : ei - 1 ]
321
data = data.split( '\n' )
328
if len( all_caps ) > 0:
329
all_caps = all_caps + ' '
330
all_caps = all_caps + line
332
caps = all_caps.split( ", " )
334
#print ( "ACLs" in get_rsync_caps() )
338
def use_rsync_fast( config ):
339
return not (config.preserve_acl() or config.preserve_xattr())
342
def get_rsync_prefix( config ):
343
caps = get_rsync_caps()
348
if config.use_checksum():
349
cmd = cmd + ' --checksum'
351
if config.copy_unsafe_links():
352
cmd = cmd + ' --copy-unsafe-links'
354
if config.copy_links():
355
cmd = cmd + ' --copy-links'
357
cmd = cmd + ' --links'
361
if config.preserve_acl() and "ACLs" in caps:
365
if config.preserve_xattr() and "xattrs" in caps:
370
cmd = cmd + ' --no-p --no-g --no-o'
377
def temp_failure_retry(func, *args, **kwargs):
380
return func(*args, **kwargs)
381
except (os.error, IOError), ex:
382
if ex.errno == errno.EINTR:
388
def _get_md5sum_from_path(path):
389
'''return md5sum of path, af available system command md5sum()'''
390
if check_command("md5sum"):
391
status,output = commands.getstatusoutput("md5sum '" + path + "'")
393
md5sum = output.split(" ")[0]
395
# md5sum unavailable or command failed; raise an exception ? a message ? use std lib ?
396
print "warning: md5sum() fail ! used (st_size, st_mttime) instead of md5sum."
398
unique_key = (obj.st_size, int(obj.st_mtime))
403
class UniquenessSet():
404
'''a class to check for uniqueness of snapshots of the same [item]'''
406
def __init__(self, dc = False, follow_symlink = False):
408
self.follow_sym = follow_symlink
409
self._uniq_dict = {} # if not self._uniq_dict[size] -> size already checked with md5sum
410
self._size_inode = set() # if (size,inode) in self._size_inode -> path is a hlink
412
def check_for(self, input_path, verb = False):
413
'''store a unique key for path, return True if path is unique'''
416
if self.follow_sym and os.path.islink(input_path):
417
path = os.readlink(input_path)
421
size,inode = dum.st_size, dum.st_ino
423
if (size, inode) in self._size_inode:
424
if verb: print "[deep test] : skip, it's a duplicate (size, inode)"
426
self._size_inode.add( (size,inode) )
427
if size not in self._uniq_dict.keys():
428
# first item of that size
430
if verb: print "[deep test] : store current size ?"
432
prev = self._uniq_dict[size]
434
# store md5sum instead of previously stored size
435
md5sum_prev = _get_md5sum_from_path(prev)
436
self._uniq_dict[size] = None
437
self._uniq_dict[md5sum_prev] = prev
439
print "[deep test] : size duplicate, remove the size, store prev md5sum"
440
unique_key = _get_md5sum_from_path(path)
441
if verb: print "[deep test] : store current md5sum ?"
443
# store a tuple of (size, modification time)
445
unique_key = (obj.st_size, int(obj.st_mtime))
446
# print "..", path, unique_key
447
# store if not already present, then return True
448
if unique_key not in self._uniq_dict.keys():
449
if verb: print " >> ok, store !"
450
self._uniq_dict[unique_key] = path
452
if verb: print " >> skip (it's a duplicate)"