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# Baseclass for Bareos python plugins 25# Functions taken and adapted from bareos-fd.py 26 27import bareosfd 28from bareosfd import * 29import os 30import stat 31import time 32 33 34class BareosFdPluginBaseclass(object): 35 36 """ Bareos python plugin base class """ 37 38 def __init__(self, plugindef, mandatory_options=None): 39 bareosfd.DebugMessage( 40 100, 41 "Constructor called in module %s with plugindef=%s\n" 42 % (__name__, plugindef), 43 ) 44 events = [] 45 events.append(bEventJobEnd) 46 events.append(bEventEndBackupJob) 47 events.append(bEventEndFileSet) 48 events.append(bEventHandleBackupFile) 49 events.append(bEventStartBackupJob) 50 events.append(bEventStartRestoreJob) 51 bareosfd.RegisterEvents(events) 52 # get some static Bareos values 53 self.fdname = bareosfd.GetValue(bVarFDName) 54 self.jobId = bareosfd.GetValue(bVarJobId) 55 self.client = bareosfd.GetValue(bVarClient) 56 self.since = bareosfd.GetValue(bVarSinceTime) 57 self.level = bareosfd.GetValue(bVarLevel) 58 self.jobName = bareosfd.GetValue(bVarJobName) 59 # jobName is of format myName.2020-05-12_11.35.27_05 60 # short Name is everything left of the third point seen from the right 61 self.shortName = self.jobName.rsplit(".", 3)[0] 62 self.workingdir = bareosfd.GetValue(bVarWorkingDir) 63 self.startTime = int(time.time()) 64 self.FNAME = "undef" 65 self.filetype = "undef" 66 self.file = None 67 bareosfd.DebugMessage( 68 100, "FDName = %s - BareosFdPluginBaseclass\n" % (self.fdname) 69 ) 70 bareosfd.DebugMessage( 71 100, "WorkingDir: %s JobId: %s\n" % (self.workingdir, self.jobId) 72 ) 73 self.mandatory_options = mandatory_options 74 75 def __str__(self): 76 return "<%s:fdname=%s jobId=%s client=%s since=%d level=%c jobName=%s workingDir=%s>" % ( 77 self.__class__, 78 self.fdname, 79 self.jobId, 80 self.client, 81 self.since, 82 self.level, 83 self.jobName, 84 self.workingdir, 85 ) 86 87 def parse_plugin_definition(self, plugindef): 88 bareosfd.DebugMessage(100, 'plugin def parser called with "%s"\n' % (plugindef)) 89 # Parse plugin options into a dict 90 if not hasattr(self, "options"): 91 self.options = dict() 92 plugin_options = plugindef.split(":") 93 while plugin_options: 94 current_option = plugin_options.pop(0) 95 key, sep, val = current_option.partition("=") 96 # See if the last character is a escape of the value string 97 while val[-1:] == "\\": 98 val = val[:-1] + ":" + plugin_options.pop(0) 99 # Replace all '\\' 100 val = val.replace("\\", "") 101 bareosfd.DebugMessage(100, 'key:val = "%s:%s"\n' % (key, val)) 102 if val == "": 103 continue 104 else: 105 if key not in self.options: 106 self.options[key] = val 107 # after we've parsed all arguments, we check the options for mandatory settings 108 return self.check_options(self.mandatory_options) 109 110 def check_options(self, mandatory_options=None): 111 """ 112 Check Plugin options 113 Here we just verify that eventual mandatory options are set. 114 If you have more to veriy, just overwrite ths method in your class 115 """ 116 if mandatory_options is None: 117 return bRC_OK 118 for option in mandatory_options: 119 if option not in self.options: 120 bareosfd.DebugMessage( 121 100, "Mandatory option '%s' not defined.\n" % option 122 ) 123 bareosfd.JobMessage( 124 M_FATAL, 125 "Mandatory option '%s' not defined.\n" % (option), 126 ) 127 return bRC_Error 128 bareosfd.DebugMessage( 129 100, "Using Option %s=%s\n" % (option, self.options[option]) 130 ) 131 return bRC_OK 132 133 def plugin_io_open(self, IOP): 134 self.FNAME = IOP.fname 135 bareosfd.DebugMessage(250, "io_open: self.FNAME is set to %s\n" % (self.FNAME)) 136 if os.path.isdir(self.FNAME): 137 bareosfd.DebugMessage(100, "%s is a directory\n" % (self.FNAME)) 138 self.fileType = "FT_DIR" 139 bareosfd.DebugMessage( 140 100, 141 "Did not open file %s of type %s\n" % (self.FNAME, self.fileType), 142 ) 143 return bRC_OK 144 elif os.path.islink(self.FNAME): 145 self.fileType = "FT_LNK" 146 bareosfd.DebugMessage( 147 100, 148 "Did not open file %s of type %s\n" % (self.FNAME, self.fileType), 149 ) 150 return bRC_OK 151 elif os.path.exists(self.FNAME) and stat.S_ISFIFO(os.stat(self.FNAME).st_mode): 152 self.fileType = "FT_FIFO" 153 bareosfd.DebugMessage( 154 100, 155 "Did not open file %s of type %s\n" % (self.FNAME, self.fileType), 156 ) 157 return bRC_OK 158 else: 159 self.fileType = "FT_REG" 160 bareosfd.DebugMessage( 161 150, 162 "file %s has type %s - trying to open it\n" 163 % (self.FNAME, self.fileType), 164 ) 165 try: 166 if IOP.flags & (os.O_CREAT | os.O_WRONLY): 167 bareosfd.DebugMessage( 168 100, 169 "Open file %s for writing with %s\n" % (self.FNAME, IOP), 170 ) 171 dirname = os.path.dirname(self.FNAME) 172 if not os.path.exists(dirname): 173 bareosfd.DebugMessage( 174 100, 175 "Directory %s does not exist, creating it now\n" % (dirname), 176 ) 177 os.makedirs(dirname) 178 self.file = open(self.FNAME, "wb") 179 else: 180 bareosfd.DebugMessage( 181 100, 182 "Open file %s for reading with %s\n" % (self.FNAME, IOP), 183 ) 184 self.file = open(self.FNAME, "rb") 185 except: 186 IOP.status = -1 187 return bRC_Error 188 return bRC_OK 189 190 def plugin_io_close(self, IOP): 191 bareosfd.DebugMessage(100, "Closing file " + "\n") 192 if self.fileType == "FT_REG": 193 self.file.close() 194 return bRC_OK 195 196 def plugin_io_seek(self, IOP): 197 return bRC_OK 198 199 def plugin_io_read(self, IOP): 200 if self.fileType == "FT_REG": 201 bareosfd.DebugMessage( 202 200, "Reading %d from file %s\n" % (IOP.count, self.FNAME) 203 ) 204 IOP.buf = bytearray(IOP.count) 205 try: 206 IOP.status = self.file.readinto(IOP.buf) 207 IOP.io_errno = 0 208 except Exception as e: 209 bareosfd.JobMessage( 210 M_ERROR, 211 'Could net read %d bytes from file %s. "%s"' 212 % (IOP.count, file_to_backup, e.message), 213 ) 214 IOP.io_errno = e.errno 215 return bRC_Error 216 else: 217 bareosfd.DebugMessage( 218 100, 219 "Did not read from file %s of type %s\n" % (self.FNAME, self.fileType), 220 ) 221 IOP.buf = bytearray() 222 IOP.status = 0 223 IOP.io_errno = 0 224 return bRC_OK 225 226 def plugin_io_write(self, IOP): 227 bareosfd.DebugMessage(200, "Writing buffer to file %s\n" % (self.FNAME)) 228 try: 229 self.file.write(IOP.buf) 230 except Exception as e: 231 bareosfd.JobMessage( 232 M_ERROR, 233 'Could net write to file %s. "%s"' % (file_to_backup, e.message), 234 ) 235 IOP.io_errno = e.errno 236 IOP.status = 0 237 return bRC_Error 238 IOP.status = IOP.count 239 IOP.io_errno = 0 240 return bRC_OK 241 242 def plugin_io(self, IOP): 243 """ 244 Basic IO operations. Some tweaks here: IOP.fname is only set on file-open 245 We need to capture it on open and keep it for the remaining procedures 246 Now (since 2020) separated into sub-methods to ease overloading in derived classes 247 """ 248 bareosfd.DebugMessage( 249 250, 250 "plugin_io called with function %s filename %s\n" % (IOP.func, IOP.fname), 251 ) 252 bareosfd.DebugMessage(250, "self.FNAME is set to %s\n" % (self.FNAME)) 253 if IOP.func == IO_OPEN: 254 return self.plugin_io_open(IOP) 255 elif IOP.func == IO_CLOSE: 256 return self.plugin_io_close(IOP) 257 elif IOP.func == IO_SEEK: 258 return self.plugin_io_seek(IOP) 259 elif IOP.func == IO_READ: 260 return self.plugin_io_read(IOP) 261 elif IOP.func == IO_WRITE: 262 return self.plugin_io_write(IOP) 263 264 def handle_plugin_event(self, event): 265 if event == bEventJobEnd: 266 bareosfd.DebugMessage(100, "handle_plugin_event called with bEventJobEnd\n") 267 return self.end_job() 268 269 elif event == bEventEndBackupJob: 270 bareosfd.DebugMessage( 271 100, "handle_plugin_event called with bEventEndBackupJob\n" 272 ) 273 return self.end_backup_job() 274 275 elif event == bEventEndFileSet: 276 bareosfd.DebugMessage( 277 100, "handle_plugin_event called with bEventEndFileSet\n" 278 ) 279 return self.end_fileset() 280 281 elif event == bEventStartBackupJob: 282 bareosfd.DebugMessage( 283 100, "handle_plugin_event() called with bEventStartBackupJob\n" 284 ) 285 return self.start_backup_job() 286 287 elif event == bEventStartRestoreJob: 288 bareosfd.DebugMessage( 289 100, 290 "handle_plugin_event() called with bEventStartRestoreJob\n", 291 ) 292 return self.start_restore_job() 293 294 else: 295 bareosfd.DebugMessage( 296 100, "handle_plugin_event called with event %s\n" % (event) 297 ) 298 return bRC_OK 299 300 def start_backup_job(self): 301 """ 302 Start of Backup Job. Called just before backup job really start. 303 Overload this to arrange whatever you have to do at this time. 304 """ 305 return bRC_OK 306 307 def end_job(self): 308 """ 309 Called if job ends regularyly (not for cancelled jobs) 310 Overload this to arrange whatever you have to do at this time. 311 """ 312 return bRC_OK 313 314 def end_backup_job(self): 315 """ 316 Called if backup job ends, before ClientAfterJob 317 Overload this to arrange whatever you have to do at this time. 318 """ 319 return bRC_OK 320 321 def start_backup_file(self, savepkt): 322 """ 323 Base method, we do not add anything, overload this method with your 324 implementation to add files to backup fileset 325 """ 326 bareosfd.DebugMessage(100, "start_backup called\n") 327 return bRC_Skip 328 329 def end_backup_file(self): 330 bareosfd.DebugMessage(100, "end_backup_file() entry point in Python called\n") 331 return bRC_OK 332 333 def end_fileset(self): 334 bareosfd.DebugMessage(100, "end_fileset() entry point in Python called\n") 335 return bRC_OK 336 337 def start_restore_job(self): 338 """ 339 Start of Restore Job. Called , if you have Restore objects. 340 Overload this to handle restore objects, if applicable 341 """ 342 return bRC_OK 343 344 def start_restore_file(self, cmd): 345 bareosfd.DebugMessage( 346 100, 347 "start_restore_file() entry point in Python called with %s\n" % (cmd), 348 ) 349 return bRC_OK 350 351 def end_restore_file(self): 352 bareosfd.DebugMessage(100, "end_restore_file() entry point in Python called\n") 353 return bRC_OK 354 355 def restore_object_data(self, ROP): 356 bareosfd.DebugMessage(100, "restore_object_data called with " + str(ROP) + "\n") 357 return bRC_OK 358 359 def create_file(self, restorepkt): 360 """ 361 Creates the file to be restored and directory structure, if needed. 362 Adapt this in your derived class, if you need modifications for 363 virtual files or similar 364 """ 365 bareosfd.DebugMessage( 366 100, 367 "create_file() entry point in Python called with %s\n" % (restorepkt), 368 ) 369 # We leave file creation up to the core for the default case 370 restorepkt.create_status = CF_CORE 371 return bRC_OK 372 373 def set_file_attributes(self, restorepkt): 374 bareosfd.DebugMessage( 375 100, 376 "set_file_attributes() entry point in Python called with %s\n" 377 % (str(restorepkt)), 378 ) 379 return bRC_OK 380 381 def check_file(self, fname): 382 bareosfd.DebugMessage( 383 100, 384 "check_file() entry point in Python called with %s\n" % (fname), 385 ) 386 return bRC_OK 387 388 def get_acl(self, acl): 389 bareosfd.DebugMessage( 390 100, "get_acl() entry point in Python called with %s\n" % (acl) 391 ) 392 return bRC_OK 393 394 def set_acl(self, acl): 395 bareosfd.DebugMessage( 396 100, "set_acl() entry point in Python called with %s\n" % (acl) 397 ) 398 return bRC_OK 399 400 def get_xattr(self, xattr): 401 bareosfd.DebugMessage( 402 100, "get_xattr() entry point in Python called with %s\n" % (xattr) 403 ) 404 return bRC_OK 405 406 def set_xattr(self, xattr): 407 bareosfd.DebugMessage( 408 100, "set_xattr() entry point in Python called with %s\n" % (xattr) 409 ) 410 return bRC_OK 411 412 def handle_backup_file(self, savepkt): 413 bareosfd.DebugMessage( 414 100, 415 "handle_backup_file() entry point in Python called with %s\n" % (savepkt), 416 ) 417 return bRC_OK 418 419 420# vim: ts=4 tabstop=4 expandtab shiftwidth=4 softtabstop=4 421