1#################################################################################################################################### 2# C Storage Interface 3#################################################################################################################################### 4package pgBackRestTest::Common::StorageRepo; 5use parent 'pgBackRestTest::Common::StorageBase'; 6 7use strict; 8use warnings FATAL => qw(all); 9use Carp qw(confess); 10use English '-no_match_vars'; 11 12use Digest::SHA qw(sha1_hex); 13use Exporter qw(import); 14 our @EXPORT = qw(); 15use File::Basename qw(dirname); 16use Fcntl qw(:mode); 17use File::stat qw{lstat}; 18use JSON::PP; 19 20use pgBackRestDoc::Common::Exception; 21use pgBackRestDoc::Common::Log; 22use pgBackRestDoc::ProjectInfo; 23 24use pgBackRestTest::Common::Io::Handle; 25use pgBackRestTest::Common::Io::Process; 26use pgBackRestTest::Common::StorageBase; 27 28#################################################################################################################################### 29# Temp file extension 30#################################################################################################################################### 31use constant STORAGE_TEMP_EXT => PROJECT_EXE . '.tmp'; 32 push @EXPORT, qw(STORAGE_TEMP_EXT); 33 34#################################################################################################################################### 35# new 36#################################################################################################################################### 37sub new 38{ 39 my $class = shift; 40 41 # Create the class hash 42 my $self = {}; 43 bless $self, $class; 44 45 # Assign function parameters, defaults, and log debug info 46 ( 47 my $strOperation, 48 $self->{strCommand}, 49 $self->{strType}, 50 $self->{lBufferMax}, 51 $self->{iTimeoutIo}, 52 $self->{iRepo}, 53 $self->{strDefaultPathMode}, 54 $self->{strDefaultFileMode}, 55 ) = 56 logDebugParam 57 ( 58 __PACKAGE__ . '->new', \@_, 59 {name => 'strCommand'}, 60 {name => 'strType'}, 61 {name => 'lBufferMax'}, 62 {name => 'iTimeoutIo'}, 63 {name => 'iRepo'}, 64 {name => 'strDefaultPathMode', optional => true, default => '0750'}, 65 {name => 'strDefaultFileMode', optional => true, default => '0640'}, 66 ); 67 68 # Create JSON object 69 $self->{oJSON} = JSON::PP->new()->allow_nonref(); 70 71 # Return from function and log return values if any 72 return logDebugReturn 73 ( 74 $strOperation, 75 {name => 'self', value => $self} 76 ); 77} 78 79#################################################################################################################################### 80# Escape characteres that have special meaning on the command line 81#################################################################################################################################### 82sub escape 83{ 84 my $self = shift; 85 86 # Assign function parameters, defaults, and log debug info 87 my 88 ( 89 $strOperation, 90 $strValue, 91 ) = 92 logDebugParam 93 ( 94 __PACKAGE__ . '->escape', \@_, 95 {name => 'strValue', trace => true}, 96 ); 97 98 $strValue =~ s/\\/\\\\/g; 99 $strValue =~ s/\</\\\</g; 100 $strValue =~ s/\>/\\\>/g; 101 $strValue =~ s/\!/\\\!/g; 102 $strValue =~ s/\*/\\\*/g; 103 $strValue =~ s/\(/\\\(/g; 104 $strValue =~ s/\)/\\\)/g; 105 $strValue =~ s/\&/\\\&/g; 106 $strValue =~ s/\'/\\\'/g; 107 $strValue =~ s/\;/\\\;/g; 108 $strValue =~ s/\?/\\\?/g; 109 110 # Return from function and log return values if any 111 return logDebugReturn 112 ( 113 $strOperation, 114 {name => 'strValue', value => $strValue}, 115 ); 116} 117 118#################################################################################################################################### 119# Execute command and return the output 120#################################################################################################################################### 121sub exec 122{ 123 my $self = shift; 124 125 # Assign function parameters, defaults, and log debug info 126 my 127 ( 128 $strOperation, 129 $strCommand, 130 ) = 131 logDebugParam 132 ( 133 __PACKAGE__ . '->exec', \@_, 134 {name => 'strCommand'}, 135 ); 136 137 $strCommand = "$self->{strCommand} ${strCommand}"; 138 my $oBuffer = new pgBackRestTest::Common::Io::Buffered( 139 new pgBackRestTest::Common::Io::Handle($strCommand), $self->{iTimeoutIo}, $self->{lBufferMax}); 140 my $oProcess = new pgBackRestTest::Common::Io::Process($oBuffer, $strCommand); 141 142 my $tResult; 143 144 while (!$oBuffer->eof()) 145 { 146 $oBuffer->read(\$tResult, $self->{lBufferMax}, false); 147 } 148 149 $oProcess->close(); 150 151 # Return from function and log return values if any 152 return logDebugReturn 153 ( 154 $strOperation, 155 {name => 'tResult', value => $tResult}, 156 {name => 'iExitStatus', value => $oProcess->exitStatus()}, 157 ); 158} 159 160#################################################################################################################################### 161# Create storage 162#################################################################################################################################### 163sub create 164{ 165 my $self = shift; 166 167 # Assign function parameters, defaults, and log debug info 168 my ($strOperation) = logDebugParam(__PACKAGE__ . '->create'); 169 170 $self->exec("--repo=$self->{iRepo} repo-create"); 171 172 # Return from function and log return values if any 173 return logDebugReturn($strOperation); 174} 175 176#################################################################################################################################### 177# Check if file exists (not a path) 178#################################################################################################################################### 179sub exists 180{ 181 my $self = shift; 182 183 # Assign function parameters, defaults, and log debug info 184 my 185 ( 186 $strOperation, 187 $strFileExp, 188 ) = 189 logDebugParam 190 ( 191 __PACKAGE__ . '->exists', \@_, 192 {name => 'strFileExp'}, 193 ); 194 195 # Return from function and log return values if any 196 return logDebugReturn 197 ( 198 $strOperation, 199 {name => 'bExists', value => $self->info($strFileExp, {bIgnoreMissing => true})->{type} eq 'f'} 200 ); 201} 202 203#################################################################################################################################### 204# Read a buffer from storage all at once 205#################################################################################################################################### 206sub get 207{ 208 my $self = shift; 209 210 # Assign function parameters, defaults, and log debug info 211 my 212 ( 213 $strOperation, 214 $xFile, 215 $strCipherPass, 216 $bRaw, 217 ) = 218 logDebugParam 219 ( 220 __PACKAGE__ . '->get', \@_, 221 {name => 'xFile', required => false}, 222 {name => 'strCipherPass', optional => true, redact => true}, 223 {name => 'bRaw', optional => true, default => false}, 224 ); 225 226 # If openRead() was called first set values from that call 227 my $strFile = $xFile; 228 my $bIgnoreMissing = false; 229 230 if (ref($xFile)) 231 { 232 $strFile = $xFile->{strFile}; 233 $bIgnoreMissing = $xFile->{bIgnoreMissing}; 234 $strCipherPass = $xFile->{strCipherPass}; 235 } 236 237 # Check invalid params 238 if ($bRaw && defined($strCipherPass)) 239 { 240 confess &log(ERROR, 'bRaw and strCipherPass cannot both be set'); 241 } 242 243 # Get file 244 my ($tResult, $iExitStatus) = $self->exec( 245 (defined($strCipherPass) ? ' --cipher-pass=' . $self->escape($strCipherPass) : '') . ($bRaw ? ' --raw' : '') . 246 ($bIgnoreMissing ? ' --ignore-missing' : '') . " --repo=$self->{iRepo} repo-get " . $self->escape($strFile)); 247 248 # Error if missing an not ignored 249 if ($iExitStatus == 1 && !$bIgnoreMissing) 250 { 251 confess &log(ERROR, "unable to open '${strFile}'", ERROR_FILE_OPEN); 252 } 253 254 # Return from function and log return values if any 255 return logDebugReturn 256 ( 257 $strOperation, 258 {name => 'rtContent', value => $iExitStatus == 0 ? \$tResult : undef, trace => true}, 259 ); 260} 261 262#################################################################################################################################### 263# Get information for path/file 264#################################################################################################################################### 265sub info 266{ 267 my $self = shift; 268 269 # Assign function parameters, defaults, and log debug info 270 my 271 ( 272 $strOperation, 273 $strPathFileExp, 274 $bIgnoreMissing, 275 ) = 276 logDebugParam 277 ( 278 __PACKAGE__ . '->info', \@_, 279 {name => 'strPathFileExp'}, 280 {name => 'bIgnoreMissing', optional => true, default => false}, 281 ); 282 283 # Return from function and log return values if any 284 return logDebugReturn 285 ( 286 $strOperation, 287 {name => 'rhInfo', value => $self->manifest($strPathFileExp, {bRecurse => false})->{'.'}, trace => true} 288 ); 289} 290 291#################################################################################################################################### 292# List all files/paths in path 293#################################################################################################################################### 294sub list 295{ 296 my $self = shift; 297 298 # Assign function parameters, defaults, and log debug info 299 my 300 ( 301 $strOperation, 302 $strPathExp, 303 $strExpression, 304 $strSortOrder, 305 $bIgnoreMissing, 306 ) = 307 logDebugParam 308 ( 309 __PACKAGE__ . '->list', \@_, 310 {name => 'strPathExp', required => false}, 311 {name => 'strExpression', optional => true}, 312 {name => 'strSortOrder', optional => true, default => 'forward'}, 313 {name => 'bIgnoreMissing', optional => true, default => false}, 314 ); 315 316 # Get file list 317 my $rstryFileList = []; 318 my $rhManifest = $self->manifest($strPathExp, {bRecurse => false}); 319 320 foreach my $strKey ($strSortOrder eq 'reverse' ? sort {$b cmp $a} keys(%{$rhManifest}) : sort keys(%{$rhManifest})) 321 { 322 next if $strKey eq '.'; 323 next if defined($strExpression) && $strKey !~ $strExpression; 324 325 push(@{$rstryFileList}, $strKey); 326 } 327 328 # Return from function and log return values if any 329 return logDebugReturn 330 ( 331 $strOperation, 332 {name => 'stryFileList', value => $rstryFileList} 333 ); 334} 335 336#################################################################################################################################### 337# Build path/file/link manifest starting with base path and including all subpaths 338#################################################################################################################################### 339sub manifest 340{ 341 my $self = shift; 342 343 # Assign function parameters, defaults, and log debug info 344 my 345 ( 346 $strOperation, 347 $strPathExp, 348 $bRecurse, 349 ) = 350 logDebugParam 351 ( 352 __PACKAGE__ . '->manifest', \@_, 353 {name => 'strPathExp'}, 354 {name => 'bRecurse', optional => true, default => true}, 355 ); 356 357 my $rhManifest = $self->{oJSON}->decode( 358 $self->exec( 359 "--output=json" . ($bRecurse ? ' --recurse' : '') . " --repo=$self->{iRepo} repo-ls " . $self->escape($strPathExp))); 360 361 # Transform the manifest to the old format 362 foreach my $strKey (keys(%{$rhManifest})) 363 { 364 if ($rhManifest->{$strKey}{type} eq 'file') 365 { 366 $rhManifest->{$strKey}{type} = 'f'; 367 368 if (defined($rhManifest->{$strKey}{time})) 369 { 370 $rhManifest->{$strKey}{modified_time} = $rhManifest->{$strKey}{time}; 371 delete($rhManifest->{$strKey}{time}); 372 } 373 } 374 elsif ($rhManifest->{$strKey}{type} eq 'path') 375 { 376 $rhManifest->{$strKey}{type} = 'd'; 377 } 378 elsif ($rhManifest->{$strKey}{type} eq 'link') 379 { 380 $rhManifest->{$strKey}{type} = 'l'; 381 } 382 elsif ($rhManifest->{$strKey}{type} eq 'special') 383 { 384 $rhManifest->{$strKey}{type} = 's'; 385 } 386 else 387 { 388 confess "invalid file type '$rhManifest->{type}'"; 389 } 390 } 391 392 # Return from function and log return values if any 393 return logDebugReturn 394 ( 395 $strOperation, 396 {name => 'rhManifest', value => $rhManifest, trace => true} 397 ); 398} 399 400#################################################################################################################################### 401# Open file for reading 402#################################################################################################################################### 403sub openRead 404{ 405 my $self = shift; 406 407 # Assign function parameters, defaults, and log debug info 408 my 409 ( 410 $strOperation, 411 $strFile, 412 $bIgnoreMissing, 413 $strCipherPass, 414 ) = 415 logDebugParam 416 ( 417 __PACKAGE__ . '->openRead', \@_, 418 {name => 'strFile'}, 419 {name => 'bIgnoreMissing', optional => true, default => false}, 420 {name => 'strCipherPass', optional => true, redact => true}, 421 ); 422 423 # Return from function and log return values if any 424 return logDebugReturn 425 ( 426 $strOperation, 427 {name => 'rhFileIo', value => {strFile => $strFile, bIgnoreMissing => $bIgnoreMissing, strCipherPass => $strCipherPass}, 428 trace => true}, 429 ); 430} 431 432#################################################################################################################################### 433# Remove path and all files below it 434#################################################################################################################################### 435sub pathRemove 436{ 437 my $self = shift; 438 439 # Assign function parameters, defaults, and log debug info 440 my 441 ( 442 $strOperation, 443 $strPath, 444 $bRecurse, 445 ) = 446 logDebugParam 447 ( 448 __PACKAGE__ . '->pathRemove', \@_, 449 {name => 'strPath'}, 450 {name => 'bRecurse', optional => true, default => false}, 451 ); 452 453 $self->exec("--repo=$self->{iRepo} repo-rm " . ($bRecurse ? '--recurse ' : '') . $self->escape($strPath)); 454 455 # Return from function and log return values if any 456 return logDebugReturn($strOperation); 457} 458 459#################################################################################################################################### 460# put - writes a buffer out to storage all at once 461#################################################################################################################################### 462sub put 463{ 464 my $self = shift; 465 466 # Assign function parameters, defaults, and log debug info 467 my 468 ( 469 $strOperation, 470 $strFile, 471 $tContent, 472 $strCipherPass, 473 $bRaw, 474 ) = 475 logDebugParam 476 ( 477 __PACKAGE__ . '->put', \@_, 478 {name => 'strFile'}, 479 {name => 'tContent', required => false}, 480 {name => 'strCipherPass', optional => true, redact => true}, 481 {name => 'bRaw', optional => true, default => false}, 482 ); 483 484 # Check invalid params 485 if ($bRaw && defined($strCipherPass)) 486 { 487 confess &log(ERROR, 'bRaw and strCipherPass cannot both be set'); 488 } 489 490 # Put file 491 my $strCommand = 492 "$self->{strCommand}" . (defined($strCipherPass) ? ' --cipher-pass=' . $self->escape($strCipherPass) : '') . 493 ($bRaw ? ' --raw' : '') . " --repo=$self->{iRepo} repo-put " . $self->escape($strFile); 494 495 my $oBuffer = new pgBackRestTest::Common::Io::Buffered( 496 new pgBackRestTest::Common::Io::Handle($strCommand), $self->{iTimeoutIo}, $self->{lBufferMax}); 497 my $oProcess = new pgBackRestTest::Common::Io::Process($oBuffer, $strCommand); 498 499 if (defined($tContent)) 500 { 501 $oBuffer->write(\$tContent); 502 } 503 504 close($oBuffer->handleWrite()); 505 506 my $tResult; 507 508 while (!$oBuffer->eof()) 509 { 510 $oBuffer->read(\$tResult, $self->{lBufferMax}, false); 511 } 512 513 close($oBuffer->handleRead()); 514 $oProcess->close(); 515 516 # Return from function and log return values if any 517 return logDebugReturn($strOperation); 518} 519 520#################################################################################################################################### 521# Remove file 522#################################################################################################################################### 523sub remove 524{ 525 my $self = shift; 526 527 # Assign function parameters, defaults, and log debug info 528 my 529 ( 530 $strOperation, 531 $strFile, 532 ) = 533 logDebugParam 534 ( 535 __PACKAGE__ . '->remove', \@_, 536 {name => 'xFileExp'}, 537 ); 538 539 $self->exec("--repo=$self->{iRepo} repo-rm " . $self->escape($strFile)); 540 541 # Return from function and log return values if any 542 return logDebugReturn($strOperation); 543} 544 545#################################################################################################################################### 546# Cache storage so it can be retrieved quickly 547#################################################################################################################################### 548my $oRepoStorage; 549 550#################################################################################################################################### 551# storageRepoCommandSet 552#################################################################################################################################### 553my $strStorageRepoCommand; 554my $strStorageRepoType; 555 556sub storageRepoCommandSet 557{ 558 # Assign function parameters, defaults, and log debug info 559 my 560 ( 561 $strOperation, 562 $strCommand, 563 $strStorageType, 564 ) = 565 logDebugParam 566 ( 567 __PACKAGE__ . '::storageRepoCommandSet', \@_, 568 {name => 'strCommand'}, 569 {name => 'strStorageType'}, 570 ); 571 572 $strStorageRepoCommand = $strCommand; 573 $strStorageRepoType = $strStorageType; 574 575 # Return from function and log return values if any 576 return logDebugReturn($strOperation); 577} 578 579push @EXPORT, qw(storageRepoCommandSet); 580 581#################################################################################################################################### 582# storageRepo - get repository storage 583#################################################################################################################################### 584sub storageRepo 585{ 586 # Assign function parameters, defaults, and log debug info 587 my 588 ( 589 $strOperation, 590 $strStanza, 591 $iRepo, 592 ) = 593 logDebugParam 594 ( 595 __PACKAGE__ . '::storageRepo', \@_, 596 {name => 'strStanza', optional => true, trace => true}, 597 {name => 'iRepo', optional => true, default => 1, trace => true}, 598 ); 599 600 # Create storage if not defined 601 if (!defined($oRepoStorage->{$iRepo})) 602 { 603 $oRepoStorage->{$iRepo} = new pgBackRestTest::Common::StorageRepo( 604 $strStorageRepoCommand, $strStorageRepoType, 64 * 1024, 60, $iRepo); 605 } 606 607 # Return from function and log return values if any 608 return logDebugReturn 609 ( 610 $strOperation, 611 {name => 'oStorageRepo', value => $oRepoStorage->{$iRepo}, trace => true}, 612 ); 613} 614 615push @EXPORT, qw(storageRepo); 616 617#################################################################################################################################### 618# Getters 619#################################################################################################################################### 620sub capability {shift->type() eq STORAGE_POSIX} 621sub type {shift->{strType}} 622 6231; 624