~ubuntu-branches/debian/experimental/backintime/experimental

« back to all changes in this revision

Viewing changes to common/tools.py

  • Committer: Bazaar Package Importer
  • Author(s): Jonathan Wiltshire
  • Date: 2010-12-03 21:56:53 UTC
  • mfrom: (1.2.1 upstream) (3.1.8 sid)
  • Revision ID: james.westby@ubuntu.com-20101203215653-scx9roloq4m0dqns
Tags: 1.0.4-1
* New upstream release
    Closes: #555293
    LP: #409130 #507246 #528518 #534829 #522618
* Update debian/copyright
* The following patches are either integrated or fixed properly
  upstream: no-chmod-777.patch allow-root-backup.patch
* Refactor remaining patches and make the headers DEP-3 compliant
* Convert to source format 3.0 (quilt) and drop quilt dependency and
  logic
* Don't depend on a specific version of Python; let dh_python choose
  the best option
* Remove the "earlier-than" restriction for the Conflicts on
  backintime-kde4
* Standards version 3.9.1 (no changes required)
* Update my email address

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
#    Back In Time
 
2
#    Copyright (C) 2008-2009 Oprea Dan, Bart de Koning, Richard Bailey
 
3
#
 
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.
 
8
#
 
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.
 
13
#
 
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.
 
17
 
 
18
 
 
19
import os.path
 
20
import os
 
21
import sys
 
22
import subprocess
 
23
import hashlib
 
24
import commands
 
25
 
 
26
 
 
27
ON_AC = 0
 
28
ON_BATTERY = 1
 
29
POWER_ERROR = 255
 
30
 
 
31
 
 
32
def get_backintime_path( path ):
 
33
        return os.path.join( os.path.dirname( os.path.abspath( os.path.dirname( __file__ ) ) ), path )
 
34
 
 
35
 
 
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
 
40
 
 
41
 
 
42
def read_file( path, default_value = None ):
 
43
        ret_val = default_value 
 
44
 
 
45
        try:
 
46
                file = open( path )
 
47
                ret_val = file.read()
 
48
                file.close()
 
49
        except:
 
50
                pass
 
51
 
 
52
        return ret_val
 
53
 
 
54
 
 
55
def read_file_lines( path, default_value = None ):
 
56
        ret_val = default_value 
 
57
 
 
58
        try:
 
59
                file = open( path )
 
60
                ret_val = file.readlines()
 
61
                file.close()
 
62
        except:
 
63
                pass
 
64
 
 
65
        return ret_val
 
66
 
 
67
 
 
68
def read_command_output( cmd ):
 
69
        ret_val = ''
 
70
 
 
71
        try:
 
72
                pipe = os.popen( cmd )
 
73
                ret_val = pipe.read().strip()
 
74
                pipe.close() 
 
75
        except:
 
76
                return ''
 
77
 
 
78
        return ret_val
 
79
 
 
80
 
 
81
def check_command( cmd ):
 
82
        cmd = cmd.strip()
 
83
 
 
84
        if len( cmd ) < 1:
 
85
                return False
 
86
 
 
87
        if os.path.isfile( cmd ):
 
88
                return True
 
89
 
 
90
        cmd = read_command_output( "which \"%s\"" % cmd )
 
91
 
 
92
        if len( cmd ) < 1:
 
93
                return False
 
94
 
 
95
        if os.path.isfile( cmd ):
 
96
                return True
 
97
 
 
98
        return False
 
99
 
 
100
 
 
101
def make_dirs( path ):
 
102
        path = path.rstrip( os.sep )
 
103
        if len( path ) <= 0:
 
104
                return
 
105
 
 
106
        if not os.path.isdir( path ):
 
107
                try:
 
108
                        os.makedirs( path )
 
109
                except:
 
110
                        pass
 
111
 
 
112
 
 
113
def process_exists( name ):
 
114
        output = read_command_output( "ps -o pid= -C %s" % name )
 
115
        return len( output ) > 0
 
116
 
 
117
 
 
118
def check_x_server():
 
119
        return 0 == os.system( 'xdpyinfo >/dev/null 2>&1' )
 
120
 
 
121
 
 
122
def prepare_path( path ):
 
123
        path = path.strip( "/" )
 
124
        path = os.sep + path
 
125
        return path
 
126
 
 
127
 
 
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."""
 
131
        try:
 
132
                rt = subprocess.call( 'on_ac_power' )
 
133
                if rt == ON_AC or rt == ON_BATTERY:
 
134
                        return True
 
135
        except:
 
136
                pass
 
137
        return False
 
138
 
 
139
 
 
140
def 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
 
144
        else:
 
145
                return False
 
146
 
 
147
 
 
148
def get_snapshots_list_in_folder( folder, sort_reverse = True ):
 
149
        biglist = []
 
150
        #print folder
 
151
 
 
152
        try:
 
153
                biglist = os.listdir( folder )
 
154
                #print biglist
 
155
        except:
 
156
                pass
 
157
 
 
158
        list = []
 
159
 
 
160
        for item in biglist:
 
161
                #print item + ' ' + str(len( item ))
 
162
                if len( item ) != 15 and len( item ) != 19:
 
163
                        continue
 
164
                if os.path.isdir( os.path.join( folder, item, 'backup' ) ):
 
165
                        #print item
 
166
                        list.append( item )
 
167
 
 
168
        list.sort( reverse = sort_reverse )
 
169
        return list
 
170
 
 
171
 
 
172
def get_nonsnapshots_list_in_folder( folder, sort_reverse = True ):
 
173
        biglist = []
 
174
        #print folder
 
175
 
 
176
        try:
 
177
                biglist = os.listdir( folder )
 
178
                #print biglist
 
179
        except:
 
180
                pass
 
181
 
 
182
        list = []
 
183
 
 
184
        for item in biglist:
 
185
                #print item + ' ' + str(len( item ))
 
186
                if len( item ) != 15 and len( item ) != 19:
 
187
                        list.append( item )
 
188
                else: 
 
189
                        if os.path.isdir( os.path.join( folder, item, 'backup' ) ):
 
190
                                #print item
 
191
                                continue
 
192
                        else:
 
193
                                list.append( item )
 
194
 
 
195
        list.sort( reverse = sort_reverse )
 
196
        return list
 
197
 
 
198
 
 
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 )      
 
202
 
 
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 )
 
208
        else:
 
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
 
214
        
 
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:
 
219
                # Use move
 
220
                for snapshot in snapshots_to_move:
 
221
                        cmd = "mv -f \"%s/%s\" \"%s\"" %( old_folder, snapshot, new_folder )
 
222
                        _execute( cmd )
 
223
        else:
 
224
                # Use rsync
 
225
                # Prepare hardlinks 
 
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 )
 
230
                        _execute( cmd )
 
231
        
 
232
                # Prepare excludes
 
233
                nonsnapshots = get_nonsnapshots_list_in_folder( old_folder )
 
234
                print "Nonsnapshots: %s" % nonsnapshots
 
235
                items = []
 
236
                for nonsnapshot in nonsnapshots:
 
237
                        for item in items:
 
238
                                if nonsnapshot == item:
 
239
                                        break
 
240
                        items.append( "--exclude=\"%s\"" % nonsnapshot )
 
241
                rsync_exclude = ' '.join( items )
 
242
                #print rsync_exclude
 
243
                
 
244
                # Move move move
 
245
                cmd = "rsync -aEAXHv --delete " + old_folder + " " + new_folder + " " + rsync_exclude
 
246
                _execute( cmd )
 
247
                
 
248
        # Remove old ones
 
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
 
256
                                _execute( cmd )
 
257
                                cmd = "rm -rfv \"%s\"" % path_to_remove
 
258
                                _execute( cmd )
 
259
                        else:
 
260
                                print "%s was already removed" %snapshot
 
261
                else: 
 
262
                        snapshots_not_moved.append( snapshot )
 
263
                                
 
264
        # Check snapshot list
 
265
        if len( snapshots_not_moved ) == 0:
 
266
                print "Succes!\n"
 
267
                return True
 
268
        else:
 
269
                print "Error! Not moved: %s\n" %snapshots_not_moved
 
270
                return False
 
271
 
 
272
 
 
273
def _execute( cmd, callback = None, user_data = None ):
 
274
        ret_val = 0
 
275
 
 
276
        if callback is None:
 
277
                ret_val = os.system( cmd )
 
278
        else:
 
279
                pipe = os.popen( cmd, 'r' )
 
280
 
 
281
                while True:
 
282
                        line = temp_failure_retry( pipe.readline )
 
283
                        if len( line ) == 0:
 
284
                                break
 
285
                        callback( line.strip(), user_data )
 
286
 
 
287
                ret_val = pipe.close()
 
288
                if ret_val is None:
 
289
                        ret_val = 0
 
290
 
 
291
        if ret_val != 0:
 
292
                print "Command \"%s\" returns %s" % ( cmd, ret_val ) 
 
293
        else:
 
294
                print "Command \"%s\" returns %s" % ( cmd, ret_val ) 
 
295
 
 
296
        return ret_val
 
297
 
 
298
 
 
299
def is_process_alive( pid ):
 
300
        try:
 
301
                os.kill( pid, 0 )       #this will raise an exception if the pid is not valid
 
302
        except:
 
303
                return False
 
304
 
 
305
        return True
 
306
 
 
307
 
 
308
def get_rsync_caps():
 
309
        data = read_command_output( 'rsync --version' )
 
310
        si = data.find( 'Capabilities:' )
 
311
        if si < 0:
 
312
                return []
 
313
        si = data.find( '\n', si )
 
314
        if si < 0:
 
315
                return []
 
316
        ei = data.find( '\n\n', si )
 
317
        if ei < 0:
 
318
                return []
 
319
 
 
320
        data = data[ si + 1 : ei - 1 ]
 
321
        data = data.split( '\n' )
 
322
        all_caps = ''
 
323
 
 
324
        for line in data:
 
325
                line = line.strip()
 
326
                if len( line ) <= 0:
 
327
                        continue
 
328
                if len( all_caps ) > 0:
 
329
                        all_caps = all_caps + ' '
 
330
                all_caps = all_caps + line
 
331
 
 
332
        caps = all_caps.split( ", " )
 
333
        #print caps
 
334
        #print ( "ACLs" in get_rsync_caps() )
 
335
        return caps
 
336
 
 
337
 
 
338
def use_rsync_fast( config ):
 
339
        return not (config.preserve_acl() or config.preserve_xattr())
 
340
 
 
341
 
 
342
def get_rsync_prefix( config ):
 
343
        caps = get_rsync_caps()
 
344
        #cmd = 'rsync -aEH'
 
345
        cmd = 'rsync'
 
346
        cmd = cmd + ' -rtDH'
 
347
 
 
348
        if config.use_checksum():
 
349
                cmd = cmd + ' --checksum'
 
350
 
 
351
        if config.copy_unsafe_links():
 
352
                cmd = cmd + ' --copy-unsafe-links'
 
353
 
 
354
        if config.copy_links():
 
355
                cmd = cmd + ' --copy-links'
 
356
        else:
 
357
                cmd = cmd + ' --links'
 
358
 
 
359
        no_perms = True
 
360
 
 
361
        if config.preserve_acl() and "ACLs" in caps:
 
362
                cmd = cmd + ' -A'
 
363
                no_perms = False
 
364
 
 
365
        if config.preserve_xattr() and "xattrs" in caps:
 
366
                cmd = cmd + ' -X'
 
367
                no_perms = False
 
368
 
 
369
        if no_perms:
 
370
                cmd = cmd + ' --no-p --no-g --no-o'
 
371
        else:
 
372
                cmd = cmd + ' -pEgo'
 
373
 
 
374
        return cmd + ' '
 
375
 
 
376
 
 
377
def temp_failure_retry(func, *args, **kwargs): 
 
378
        while True:
 
379
                try:
 
380
                        return func(*args, **kwargs)
 
381
                except (os.error, IOError), ex:
 
382
                        if ex.errno == errno.EINTR:
 
383
                                continue
 
384
                        else:
 
385
                                raise
 
386
 
 
387
 
 
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 + "'")
 
392
        if status == 0:
 
393
            md5sum = output.split(" ")[0]
 
394
            return md5sum
 
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."
 
397
    obj  = os.stat(path)
 
398
    unique_key = (obj.st_size, int(obj.st_mtime))    
 
399
    return unique_key
 
400
                                        
 
401
#
 
402
#
 
403
class UniquenessSet():
 
404
    '''a class to check for uniqueness of snapshots of the same [item]'''
 
405
    
 
406
    def __init__(self, dc = False, follow_symlink = False): 
 
407
        self.deep_check = dc
 
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 
 
411
        
 
412
    def check_for(self, input_path, verb = False):
 
413
        '''store a unique key for path, return True if path is unique'''
 
414
        # follow symlinks ?
 
415
        path = input_path
 
416
        if self.follow_sym and os.path.islink(input_path):
 
417
            path = os.readlink(input_path)
 
418
        # check
 
419
        if self.deep_check:
 
420
            dum = os.stat(path)
 
421
            size,inode  = dum.st_size, dum.st_ino
 
422
            # is it a hlink ?
 
423
            if (size, inode) in self._size_inode: 
 
424
                if verb: print "[deep test] : skip, it's a duplicate (size, inode)" 
 
425
                return False   
 
426
            self._size_inode.add( (size,inode) )
 
427
            if size not in self._uniq_dict.keys(): 
 
428
                # first item of that size
 
429
                unique_key = size
 
430
                if verb: print "[deep test] : store current size ?" 
 
431
            else: 
 
432
                prev = self._uniq_dict[size]
 
433
                if prev:
 
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      
 
438
                    if verb: 
 
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 ?" 
 
442
        else:
 
443
            # store a tuple of (size, modification time)
 
444
            obj  = os.stat(path)
 
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
 
451
            return True    
 
452
        if verb: print " >> skip (it's a duplicate)" 
 
453
        return False
 
454