1#!/usr/bin/env python 2# -*- coding: utf-8 -*- 3# BAREOS - Backup Archiving REcovery Open Sourced 4# 5# Copyright (C) 2014-2019 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 27import bareosfd 28from bareos_fd_consts import bJobMessageType, bFileType, bRCs 29import os 30import re 31import hashlib 32import time 33import BareosFdPluginBaseclass 34 35 36class BareosFdPluginLocalFilesetWithRestoreObjects( 37 BareosFdPluginBaseclass.BareosFdPluginBaseclass 38): 39 """ 40 This Bareos-FD-Plugin-Class was created for automated test purposes only. 41 It is based on the BareosFdPluginLocalFileset class that parses a file 42 and backups all files listed there. 43 Filename is taken from plugin argument 'filename'. 44 In addition to the file backup and restore, this plugin also tests 45 restore objects of different sizes. As restore objects are compressed 46 automatically, when they are greater then 1000 bytes, both uncompressed 47 and compressed restore objects are tested. 48 """ 49 50 def __init__(self, context, plugindef): 51 bareosfd.DebugMessage( 52 context, 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 context, 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 67 def filename_is_allowed(self, context, filename, allowregex, denyregex): 68 """ 69 Check, if filename is allowed. 70 True, if matches allowreg and not denyregex. 71 If allowreg is None, filename always matches 72 If denyreg is None, it never matches 73 """ 74 if allowregex is None or allowregex.search(filename): 75 allowed = True 76 else: 77 allowed = False 78 if denyregex is None or not denyregex.search(filename): 79 denied = False 80 else: 81 denied = True 82 if not allowed or denied: 83 bareosfd.DebugMessage( 84 context, 100, "File %s denied by configuration\n" % (filename) 85 ) 86 bareosfd.JobMessage( 87 context, 88 bJobMessageType["M_ERROR"], 89 "File %s denied by configuration\n" % (filename), 90 ) 91 return False 92 else: 93 return True 94 95 def start_backup_job(self, context): 96 """ 97 At this point, plugin options were passed and checked already. 98 We try to read from filename and setup the list of file to backup 99 in self.files_to_backup 100 """ 101 102 bareosfd.DebugMessage( 103 context, 104 100, 105 "Using %s to search for local files\n" % (self.options["filename"]), 106 ) 107 if os.path.exists(self.options["filename"]): 108 try: 109 config_file = open(self.options["filename"], "rb") 110 except: 111 bareosfd.DebugMessage( 112 context, 113 100, 114 "Could not open file %s\n" % (self.options["filename"]), 115 ) 116 return bRCs["bRC_Error"] 117 else: 118 bareosfd.DebugMessage( 119 context, 100, "File %s does not exist\n" % (self.options["filename"]) 120 ) 121 return bRCs["bRC_Error"] 122 # Check, if we have allow or deny regular expressions defined 123 if "allow" in self.options: 124 self.allow = re.compile(self.options["allow"]) 125 if "deny" in self.options: 126 self.deny = re.compile(self.options["deny"]) 127 128 for listItem in config_file.read().splitlines(): 129 if os.path.isfile(listItem) and self.filename_is_allowed( 130 context, listItem, self.allow, self.deny 131 ): 132 self.files_to_backup.append(listItem) 133 if os.path.isdir(listItem): 134 for topdir, dirNames, fileNames in os.walk(listItem): 135 for fileName in fileNames: 136 if self.filename_is_allowed( 137 context, 138 os.path.join(topdir, fileName), 139 self.allow, 140 self.deny, 141 ): 142 self.files_to_backup.append(os.path.join(topdir, fileName)) 143 if os.path.isfile(os.path.join(topdir, fileName)): 144 self.files_to_backup.append( 145 os.path.join(topdir, fileName) + ".sha256sum" 146 ) 147 self.files_to_backup.append( 148 os.path.join(topdir, fileName) + ".abspath" 149 ) 150 else: 151 if os.path.isfile(listItem): 152 self.files_to_backup.append(listItem + ".sha256sum") 153 self.files_to_backup.append(listItem + ".abspath") 154 155 for longrestoreobject_length in range(998, 1004): 156 self.files_to_backup.append( 157 "%s.longrestoreobject" % longrestoreobject_length 158 ) 159 160 if not self.files_to_backup: 161 bareosfd.JobMessage( 162 context, 163 bJobMessageType["M_ERROR"], 164 "No (allowed) files to backup found\n", 165 ) 166 return bRCs["bRC_Error"] 167 else: 168 return bRCs["bRC_Cancel"] 169 170 def start_backup_file(self, context, savepkt): 171 """ 172 Defines the file to backup and creates the savepkt. In this example 173 only files (no directories) are allowed 174 """ 175 bareosfd.DebugMessage(context, 100, "start_backup_file() called\n") 176 if not self.files_to_backup: 177 bareosfd.DebugMessage(context, 100, "No files to backup\n") 178 return bRCs["bRC_Skip"] 179 180 file_to_backup = self.files_to_backup.pop() 181 bareosfd.DebugMessage(context, 100, "file: " + file_to_backup + "\n") 182 183 statp = bareosfd.StatPacket() 184 savepkt.statp = statp 185 186 if file_to_backup.endswith(".sha256sum"): 187 checksum = self.get_sha256sum(context, os.path.splitext(file_to_backup)[0]) 188 savepkt.type = bFileType["FT_RESTORE_FIRST"] 189 savepkt.fname = file_to_backup 190 savepkt.object_name = file_to_backup 191 savepkt.object = bytearray(checksum) 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 = bFileType["FT_RESTORE_FIRST"] 198 savepkt.fname = file_to_backup 199 savepkt.object_name = file_to_backup 200 savepkt.object = bytearray(os.path.splitext(file_to_backup)[0]) 201 savepkt.object_len = len(savepkt.object) 202 savepkt.object_index = self.object_index_seq 203 self.object_index_seq += 1 204 205 elif file_to_backup.endswith(".longrestoreobject"): 206 teststring_length = int(os.path.splitext(file_to_backup)[0]) 207 savepkt.type = bFileType["FT_RESTORE_FIRST"] 208 savepkt.fname = file_to_backup 209 savepkt.object_name = file_to_backup 210 savepkt.object = bytearray("a" * teststring_length) 211 savepkt.object_len = len(savepkt.object) 212 savepkt.object_index = self.object_index_seq 213 self.object_index_seq += 1 214 215 else: 216 savepkt.fname = file_to_backup 217 savepkt.type = bFileType["FT_REG"] 218 219 bareosfd.JobMessage( 220 context, 221 bJobMessageType["M_INFO"], 222 "Starting backup of %s\n" % (file_to_backup), 223 ) 224 return bRCs["bRC_OK"] 225 226 def end_backup_file(self, context): 227 """ 228 Here we return 'bRC_More' as long as our list files_to_backup is not 229 empty and bRC_OK when we are done 230 """ 231 bareosfd.DebugMessage( 232 context, 100, "end_backup_file() entry point in Python called\n" 233 ) 234 if self.files_to_backup: 235 return bRCs["bRC_More"] 236 else: 237 return bRCs["bRC_OK"] 238 239 def set_file_attributes(self, context, restorepkt): 240 bareosfd.DebugMessage( 241 context, 242 100, 243 "set_file_attributes() entry point in Python called with %s\n" 244 % (str(restorepkt)), 245 ) 246 247 orig_fname = "/" + os.path.relpath(restorepkt.ofname, restorepkt.where) 248 restoreobject_sha256sum = self.sha256sums_by_filename[orig_fname] 249 250 file_sha256sum = self.get_sha256sum(context, orig_fname) 251 bareosfd.DebugMessage( 252 context, 253 100, 254 "set_file_attributes() orig_fname: %s restoreobject_sha256sum: %s file_sha256sum: %s\n" 255 % (orig_fname, repr(restoreobject_sha256sum), repr(file_sha256sum)), 256 ) 257 if file_sha256sum != restoreobject_sha256sum: 258 bareosfd.JobMessage( 259 context, 260 bJobMessageType["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 bRCs["bRC_OK"] 266 267 def end_restore_file(self, context): 268 bareosfd.DebugMessage( 269 context, 100, "end_restore_file() self.FNAME: %s\n" % self.FNAME 270 ) 271 return bRCs["bRC_OK"] 272 273 def restore_object_data(self, context, ROP): 274 """ 275 Note: 276 This is called in two cases: 277 - on diff/inc backup (should be called only once) 278 - on restore (for every job id being restored) 279 But at the point in time called, it is not possible 280 to distinguish which of them it is, because job type 281 is "I" until the bEventStartBackupJob event 282 """ 283 bareosfd.DebugMessage( 284 context, 285 100, 286 "BareosFdPluginLocalFilesetWithRestoreObjects:restore_object_data() called with ROP:%s\n" 287 % (ROP), 288 ) 289 bareosfd.DebugMessage( 290 context, 291 100, 292 "ROP.object_name(%s): %s\n" % (type(ROP.object_name), ROP.object_name), 293 ) 294 bareosfd.DebugMessage( 295 context, 296 100, 297 "ROP.plugin_name(%s): %s\n" % (type(ROP.plugin_name), ROP.plugin_name), 298 ) 299 bareosfd.DebugMessage( 300 context, 301 100, 302 "ROP.object_len(%s): %s\n" % (type(ROP.object_len), ROP.object_len), 303 ) 304 bareosfd.DebugMessage( 305 context, 306 100, 307 "ROP.object_full_len(%s): %s\n" 308 % (type(ROP.object_full_len), ROP.object_full_len), 309 ) 310 bareosfd.DebugMessage( 311 context, 100, "ROP.object(%s): %s\n" % (type(ROP.object), repr(ROP.object)) 312 ) 313 orig_filename = os.path.splitext(ROP.object_name)[0] 314 if ROP.object_name.endswith(".sha256sum"): 315 self.sha256sums_by_filename[orig_filename] = str(ROP.object) 316 elif ROP.object_name.endswith(".abspath"): 317 if str(ROP.object) != orig_filename: 318 bareosfd.JobMessage( 319 context, 320 bJobMessageType["M_ERROR"], 321 "bad restoreobject orig_fname: %s restoreobject_fname: %s\n" 322 % (orig_filename, repr(str(ROP.object))), 323 ) 324 elif ROP.object_name.endswith(".longrestoreobject"): 325 stored_length = int(os.path.splitext(ROP.object_name)[0]) 326 if str(ROP.object) != "a" * stored_length: 327 bareosfd.JobMessage( 328 context, 329 bJobMessageType["M_ERROR"], 330 "bad long restoreobject %s does not match stored object\n" 331 % (ROP.object_name), 332 ) 333 else: 334 bareosfd.DebugMessage( 335 context, 336 100, 337 "not checking restoreobject: %s\n" % (type(ROP.object_name)), 338 ) 339 return bRCs["bRC_OK"] 340 341 def get_sha256sum(self, context, filename): 342 f = open(filename, "rb") 343 m = hashlib.sha256() 344 while True: 345 d = f.read(8096) 346 if not d: 347 break 348 m.update(d) 349 f.close() 350 return m.hexdigest() 351 352 353# vim: ts=4 tabstop=4 expandtab shiftwidth=4 softtabstop=4 354