1#!/usr/bin/env python 2# -*- coding: utf-8 -*- 3# BAREOS - Backup Archiving REcovery Open Sourced 4# 5# Copyright (C) 2014-2020 Bareos GmbH & Co. KG 6# 7# This program is Free Software; you can redistribute it and/or 8# modify it under the terms of version three of the GNU Affero General Public 9# License as published by the Free Software Foundation, which is 10# listed in the file LICENSE. 11# 12# This program is distributed in the hope that it will be useful, but 13# WITHOUT ANY WARRANTY; without even the implied warranty of 14# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 15# Affero General Public License for more details. 16# 17# You should have received a copy of the GNU Affero General Public License 18# along with this program; if not, write to the Free Software 19# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 20# 02110-1301, USA. 21# 22# Author: Maik Aussendorf 23# 24# Bareos python plugins class that adds files from a local list to 25# the backup fileset 26 27 28import bareosfd 29from bareosfd import * 30import os 31import re 32import hashlib 33import time 34import BareosFdPluginBaseclass 35 36 37class BareosFdPluginLocalFilesetWithRestoreObjects( 38 BareosFdPluginBaseclass.BareosFdPluginBaseclass 39): 40 """ 41 This Bareos-FD-Plugin-Class was created for automated test purposes only. 42 It is based on the BareosFdPluginLocalFileset class that parses a file 43 and backups all files listed there. 44 Filename is taken from plugin argument 'filename'. 45 In addition to the file backup and restore, this plugin also tests 46 restore objects of different sizes. As restore objects are compressed 47 automatically, when they are greater then 1000 bytes, both uncompressed 48 and compressed restore objects are tested. 49 """ 50 51 def __init__(self, plugindef): 52 bareosfd.DebugMessage( 53 100, 54 "Constructor called in module %s with plugindef=%s\n" 55 % (__name__, plugindef), 56 ) 57 # Last argument of super constructor is a list of mandatory arguments 58 super(BareosFdPluginLocalFilesetWithRestoreObjects, self).__init__( 59 plugindef, ["filename"] 60 ) 61 self.files_to_backup = [] 62 self.allow = None 63 self.deny = None 64 self.object_index_seq = int((time.time() - 1546297200) * 10) 65 self.sha256sums_by_filename = {} 66 # bareosfd.SetValue(bVarSinceTime,999) 67 # triple_nine = bareosfd.GetValue(bVarSinceTime) 68 # if triple_nine != 999: 69 # bareosfd.JobMessage( 70 # M_ERROR, 71 # "Wrong value for since time (should be 999 but is %d)\n" % triple_nine 72 # ) 73 74 def filename_is_allowed(self, filename, allowregex, denyregex): 75 """ 76 Check, if filename is allowed. 77 True, if matches allowreg and not denyregex. 78 If allowreg is None, filename always matches 79 If denyreg is None, it never matches 80 """ 81 if allowregex is None or allowregex.search(filename): 82 allowed = True 83 else: 84 allowed = False 85 if denyregex is None or not denyregex.search(filename): 86 denied = False 87 else: 88 denied = True 89 if not allowed or denied: 90 bareosfd.DebugMessage(100, "File %s denied by configuration\n" % (filename)) 91 bareosfd.JobMessage( 92 M_ERROR, 93 "File %s denied by configuration\n" % (filename), 94 ) 95 return False 96 else: 97 return True 98 99 def start_backup_job(self): 100 """ 101 At this point, plugin options were passed and checked already. 102 We try to read from filename and setup the list of file to backup 103 in self.files_to_backup 104 """ 105 106 bareosfd.DebugMessage( 107 100, 108 "Using %s to search for local files\n" % (self.options["filename"]), 109 ) 110 if os.path.exists(self.options["filename"]): 111 try: 112 config_file = open(self.options["filename"], "r") 113 # config_file = open(self.options["filename"], "rb") 114 except: 115 bareosfd.DebugMessage( 116 100, 117 "Could not open file %s\n" % (self.options["filename"]), 118 ) 119 return bRC_Error 120 else: 121 bareosfd.DebugMessage( 122 100, "File %s does not exist\n" % (self.options["filename"]) 123 ) 124 return bRC_Error 125 # Check, if we have allow or deny regular expressions defined 126 if "allow" in self.options: 127 self.allow = re.compile(self.options["allow"]) 128 if "deny" in self.options: 129 self.deny = re.compile(self.options["deny"]) 130 131 for listItem in config_file.read().splitlines(): 132 if os.path.isfile(listItem) and self.filename_is_allowed( 133 listItem, self.allow, self.deny 134 ): 135 self.files_to_backup.append(listItem) 136 if os.path.isdir(listItem): 137 for topdir, dirNames, fileNames in os.walk(listItem): 138 for fileName in fileNames: 139 if self.filename_is_allowed( 140 os.path.join(topdir, fileName), 141 self.allow, 142 self.deny, 143 ): 144 self.files_to_backup.append(os.path.join(topdir, fileName)) 145 if os.path.isfile(os.path.join(topdir, fileName)): 146 self.files_to_backup.append( 147 os.path.join(topdir, fileName) + ".sha256sum" 148 ) 149 self.files_to_backup.append( 150 os.path.join(topdir, fileName) + ".abspath" 151 ) 152 else: 153 if os.path.isfile(listItem): 154 self.files_to_backup.append(listItem + ".sha256sum") 155 self.files_to_backup.append(listItem + ".abspath") 156 157 for longrestoreobject_length in range(998, 1004): 158 self.files_to_backup.append( 159 "%s.longrestoreobject" % longrestoreobject_length 160 ) 161 162 if not self.files_to_backup: 163 bareosfd.JobMessage( 164 M_ERROR, 165 "No (allowed) files to backup found\n", 166 ) 167 return bRC_Error 168 else: 169 return bRC_Cancel 170 171 def start_backup_file(self, savepkt): 172 """ 173 Defines the file to backup and creates the savepkt. In this example 174 only files (no directories) are allowed 175 """ 176 bareosfd.DebugMessage(100, "start_backup_file() called\n") 177 if not self.files_to_backup: 178 bareosfd.DebugMessage(100, "No files to backup\n") 179 return bRC_Skip 180 181 file_to_backup = self.files_to_backup.pop() 182 183 statp = bareosfd.StatPacket() 184 savepkt.statp = statp 185 186 if file_to_backup.endswith(".sha256sum"): 187 checksum = self.get_sha256sum(os.path.splitext(file_to_backup)[0]) 188 savepkt.type = FT_RESTORE_FIRST 189 savepkt.fname = file_to_backup 190 savepkt.object_name = file_to_backup 191 savepkt.object = bytearray(checksum.encode("utf-8")) 192 savepkt.object_len = len(savepkt.object) 193 savepkt.object_index = self.object_index_seq 194 self.object_index_seq += 1 195 196 elif file_to_backup.endswith(".abspath"): 197 savepkt.type = FT_RESTORE_FIRST 198 savepkt.fname = file_to_backup 199 savepkt.object_name = file_to_backup 200 savepkt.object = bytearray( 201 os.path.splitext(file_to_backup)[0].encode("utf-8") 202 ) 203 204 savepkt.object_len = len(savepkt.object) 205 savepkt.object_index = self.object_index_seq 206 self.object_index_seq += 1 207 208 elif file_to_backup.endswith(".longrestoreobject"): 209 teststring_length = int(os.path.splitext(file_to_backup)[0]) 210 savepkt.type = FT_RESTORE_FIRST 211 savepkt.fname = file_to_backup 212 savepkt.object_name = file_to_backup 213 savepkt.object = bytearray(b"a" * teststring_length) 214 savepkt.object_len = len(savepkt.object) 215 savepkt.object_index = self.object_index_seq 216 self.object_index_seq += 1 217 218 else: 219 savepkt.fname = file_to_backup 220 savepkt.type = FT_REG 221 222 bareosfd.JobMessage( 223 M_INFO, 224 "Starting backup of {}\n".format(file_to_backup), 225 ) 226 return bRC_OK 227 228 def end_backup_file(self): 229 """ 230 Here we return 'bRC_More' as long as our list files_to_backup is not 231 empty and bRC_OK when we are done 232 """ 233 bareosfd.DebugMessage(100, "end_backup_file() entry point in Python called\n") 234 if self.files_to_backup: 235 return bRC_More 236 else: 237 return bRC_OK 238 239 def set_file_attributes(self, restorepkt): 240 bareosfd.DebugMessage( 241 100, 242 "set_file_attributes() entry point in Python called with %s\n" 243 % (str(restorepkt)), 244 ) 245 246 orig_fname = "/" + os.path.relpath(restorepkt.ofname, restorepkt.where) 247 bareosfd.DebugMessage( 248 100, "set_file_attributes() orig_fname: {} \n".format(orig_fname) 249 ) 250 restoreobject_sha256sum = self.sha256sums_by_filename[orig_fname] 251 252 file_sha256sum = self.get_sha256sum(orig_fname).encode("utf-8") 253 bareosfd.DebugMessage( 254 100, 255 "set_file_attributes() orig_fname: %s restoreobject_sha256sum: %s file_sha256sum: %s\n" 256 % (orig_fname, repr(restoreobject_sha256sum), repr(file_sha256sum)), 257 ) 258 if file_sha256sum != restoreobject_sha256sum: 259 bareosfd.JobMessage( 260 M_ERROR, 261 "bad restoreobject orig_fname: %s restoreobject_sha256sum: %s file_sha256sum: %s\n" 262 % (orig_fname, repr(restoreobject_sha256sum), repr(file_sha256sum)), 263 ) 264 265 return bRC_OK 266 267 def end_restore_file(self): 268 bareosfd.DebugMessage(100, "end_restore_file() self.FNAME: %s\n" % self.FNAME) 269 return bRC_OK 270 271 def restore_object_data(self, ROP): 272 """ 273 Note: 274 This is called in two cases: 275 - on diff/inc backup (should be called only once) 276 - on restore (for every job id being restored) 277 But at the point in time called, it is not possible 278 to distinguish which of them it is, because job type 279 is "I" until the bEventStartBackupJob event 280 """ 281 bareosfd.DebugMessage( 282 100, 283 "BareosFdPluginLocalFilesetWithRestoreObjects:restore_object_data() called with ROP:%s\n" 284 % (ROP), 285 ) 286 bareosfd.DebugMessage( 287 100, 288 "ROP.object_name(%s): %s\n" % (type(ROP.object_name), ROP.object_name), 289 ) 290 bareosfd.DebugMessage( 291 100, 292 "ROP.plugin_name(%s): %s\n" % (type(ROP.plugin_name), ROP.plugin_name), 293 ) 294 bareosfd.DebugMessage( 295 100, 296 "ROP.object_len(%s): %s\n" % (type(ROP.object_len), ROP.object_len), 297 ) 298 bareosfd.DebugMessage( 299 100, 300 "ROP.object_full_len(%s): %s\n" 301 % (type(ROP.object_full_len), ROP.object_full_len), 302 ) 303 bareosfd.DebugMessage( 304 100, "ROP.object(%s): %s\n" % (type(ROP.object), repr(ROP.object)) 305 ) 306 orig_filename = os.path.splitext(ROP.object_name)[0] 307 if ROP.object_name.endswith(".sha256sum"): 308 self.sha256sums_by_filename[orig_filename] = ROP.object 309 # self.sha256sums_by_filename[orig_filename] = str(ROP.object) 310 elif ROP.object_name.endswith(".abspath"): 311 if ROP.object.decode() != orig_filename: 312 bareosfd.JobMessage( 313 M_ERROR, 314 "bad restoreobject orig_fname: %s restoreobject_fname: %s\n" 315 % (orig_filename, ROP.object.decode()), 316 ) 317 elif ROP.object_name.endswith(".longrestoreobject"): 318 stored_length = int(os.path.splitext(ROP.object_name)[0]) 319 if ROP.object.decode() != "a" * stored_length: 320 bareosfd.JobMessage( 321 M_ERROR, 322 "bad long restoreobject {} does not match stored object: {}, {}\n".format( 323 ROP.object_name, ROP.object.decode(), "a" * stored_length 324 ), 325 ) 326 else: 327 bareosfd.DebugMessage( 328 100, 329 "not checking restoreobject: %s\n" % (type(ROP.object_name)), 330 ) 331 return bRC_OK 332 333 def get_sha256sum(self, filename): 334 f = open(filename, "rb") 335 m = hashlib.sha256() 336 while True: 337 d = f.read(8096) 338 if not d: 339 break 340 m.update(d) 341 f.close() 342 return m.hexdigest() 343 344 345# vim: ts=4 tabstop=4 expandtab shiftwidth=4 softtabstop=4 346