1####################################################################################################################################
2# Posix Storage
3#
4# Implements storage functions for Posix-compliant file systems.
5####################################################################################################################################
6package pgBackRestTest::Common::StoragePosix;
7
8use strict;
9use warnings FATAL => qw(all);
10use Carp qw(confess);
11use English '-no_match_vars';
12
13use Exporter qw(import);
14    our @EXPORT = qw();
15use File::Basename qw(basename dirname);
16use Fcntl qw(:mode);
17use File::stat qw{lstat};
18
19use pgBackRestDoc::Common::Exception;
20use pgBackRestDoc::Common::Log;
21
22use pgBackRestTest::Common::StorageBase;
23use pgBackRestTest::Common::StoragePosixRead;
24use pgBackRestTest::Common::StoragePosixWrite;
25
26####################################################################################################################################
27# Package name constant
28####################################################################################################################################
29use constant STORAGE_POSIX_DRIVER                                      => __PACKAGE__;
30    push @EXPORT, qw(STORAGE_POSIX_DRIVER);
31
32####################################################################################################################################
33# new
34####################################################################################################################################
35sub new
36{
37    my $class = shift;
38
39    # Create the class hash
40    my $self = {};
41    bless $self, $class;
42
43    # Assign function parameters, defaults, and log debug info
44    (
45        my $strOperation,
46        $self->{bFileSync},
47        $self->{bPathSync},
48    ) =
49        logDebugParam
50        (
51            __PACKAGE__ . '->new', \@_,
52            {name => 'bFileSync', optional => true, default => true},
53            {name => 'bPathSync', optional => true, default => true},
54        );
55
56    # Set default temp extension
57    $self->{strTempExtension} = 'tmp';
58
59    # Return from function and log return values if any
60    return logDebugReturn
61    (
62        $strOperation,
63        {name => 'self', value => $self, trace => true}
64    );
65}
66
67####################################################################################################################################
68# exists - check if a path or file exists
69####################################################################################################################################
70sub exists
71{
72    my $self = shift;
73
74    # Assign function parameters, defaults, and log debug info
75    my
76    (
77        $strOperation,
78        $strFile,
79    ) =
80        logDebugParam
81        (
82            __PACKAGE__ . '->exists', \@_,
83            {name => 'strFile', trace => true},
84        );
85
86    # Does the path/file exist?
87    my $bExists = true;
88    my $oStat = lstat($strFile);
89
90    # Use stat to test if file exists
91    if (defined($oStat))
92    {
93        # Check that it is actually a file
94        $bExists = !S_ISDIR($oStat->mode) ? true : false;
95    }
96    else
97    {
98        # If the error is not entry missing, then throw error
99        if (!$OS_ERROR{ENOENT})
100        {
101            logErrorResult(ERROR_FILE_EXISTS, "unable to test if file '${strFile}' exists", $OS_ERROR);
102        }
103
104        $bExists = false;
105    }
106
107    # Return from function and log return values if any
108    return logDebugReturn
109    (
110        $strOperation,
111        {name => 'bExists', value => $bExists, trace => true}
112    );
113}
114
115####################################################################################################################################
116# info - get information for path/file
117####################################################################################################################################
118sub info
119{
120    my $self = shift;
121
122    # Assign function parameters, defaults, and log debug info
123    my
124    (
125        $strOperation,
126        $strPathFile,
127        $bIgnoreMissing,
128    ) =
129        logDebugParam
130        (
131            __PACKAGE__ . '->info', \@_,
132            {name => 'strFile', trace => true},
133            {name => 'bIgnoreMissing', optional => true, default => false, trace => true},
134        );
135
136    # Stat the path/file
137    my $oInfo = lstat($strPathFile);
138
139    # Check for errors
140    if (!defined($oInfo))
141    {
142        if (!($OS_ERROR{ENOENT} && $bIgnoreMissing))
143        {
144            logErrorResult($OS_ERROR{ENOENT} ? ERROR_FILE_MISSING : ERROR_FILE_OPEN, "unable to stat '${strPathFile}'", $OS_ERROR);
145        }
146    }
147
148    # Return from function and log return values if any
149    return logDebugReturn
150    (
151        $strOperation,
152        {name => 'oInfo', value => $oInfo, trace => true}
153    );
154}
155
156####################################################################################################################################
157# linkCreate
158####################################################################################################################################
159sub linkCreate
160{
161    my $self = shift;
162
163    # Assign function parameters, defaults, and log debug info
164    my
165    (
166        $strOperation,
167        $strSourcePathFile,
168        $strDestinationLink,
169        $bHard,
170        $bPathCreate,
171        $bIgnoreExists,
172    ) =
173        logDebugParam
174        (
175            __PACKAGE__ . '->linkCreate', \@_,
176            {name => 'strSourcePathFile', trace => true},
177            {name => 'strDestinationLink', trace => true},
178            {name => 'bHard', optional=> true, default => false, trace => true},
179            {name => 'bPathCreate', optional=> true, default => true, trace => true},
180            {name => 'bIgnoreExists', optional => true, default => false, trace => true},
181        );
182
183    if (!($bHard ? link($strSourcePathFile, $strDestinationLink) : symlink($strSourcePathFile, $strDestinationLink)))
184    {
185        my $strMessage = "unable to create link '${strDestinationLink}'";
186
187        # If parent path or source is missing
188        if ($OS_ERROR{ENOENT})
189        {
190            # Check if source is missing
191            if (!$self->exists($strSourcePathFile))
192            {
193                confess &log(ERROR, "${strMessage} because source '${strSourcePathFile}' does not exist", ERROR_FILE_MISSING);
194            }
195
196            if (!$bPathCreate)
197            {
198                confess &log(ERROR, "${strMessage} because parent does not exist", ERROR_PATH_MISSING);
199            }
200
201            # Create parent path
202            $self->pathCreate(dirname($strDestinationLink), {bIgnoreExists => true, bCreateParent => true});
203
204            # Create link
205            $self->linkCreate($strSourcePathFile, $strDestinationLink, {bHard => $bHard});
206        }
207        # Else if link already exists
208        elsif ($OS_ERROR{EEXIST})
209        {
210            if (!$bIgnoreExists)
211            {
212                confess &log(ERROR, "${strMessage} because it already exists", ERROR_PATH_EXISTS);
213            }
214        }
215        else
216        {
217            logErrorResult(ERROR_PATH_CREATE, ${strMessage}, $OS_ERROR);
218        }
219    }
220
221    # Return from function and log return values if any
222    return logDebugReturn($strOperation);
223}
224
225####################################################################################################################################
226# linkDestination - get destination of symlink
227####################################################################################################################################
228sub linkDestination
229{
230    my $self = shift;
231
232    # Assign function parameters, defaults, and log debug info
233    my
234    (
235        $strOperation,
236        $strLink,
237    ) =
238        logDebugParam
239        (
240            __PACKAGE__ . '->linkDestination', \@_,
241            {name => 'strLink', trace => true},
242        );
243
244    # Get link destination
245    my $strLinkDestination = readlink($strLink);
246
247    # Check for errors
248    if (!defined($strLinkDestination))
249    {
250        logErrorResult(
251            $OS_ERROR{ENOENT} ? ERROR_FILE_MISSING : ERROR_FILE_OPEN, "unable to get destination for link ${strLink}", $OS_ERROR);
252    }
253
254    # Return from function and log return values if any
255    return logDebugReturn
256    (
257        $strOperation,
258        {name => 'strLinkDestination', value => $strLinkDestination, trace => true}
259    );
260}
261
262####################################################################################################################################
263# list - list all files/paths in path
264####################################################################################################################################
265sub list
266{
267    my $self = shift;
268
269    # Assign function parameters, defaults, and log debug info
270    my
271    (
272        $strOperation,
273        $strPath,
274        $bIgnoreMissing,
275    ) =
276        logDebugParam
277        (
278            __PACKAGE__ . '->list', \@_,
279            {name => 'strPath', trace => true},
280            {name => 'bIgnoreMissing', optional => true, default => false, trace => true},
281        );
282
283    # Working variables
284    my @stryFileList;
285    my $hPath;
286
287    # Attempt to open the path
288    if (opendir($hPath, $strPath))
289    {
290        @stryFileList = grep(!/^(\.|\.\.)$/m, readdir($hPath));
291        close($hPath);
292    }
293    # Else process errors
294    else
295    {
296        # Ignore the error if the file is missing and missing files should be ignored
297        if (!($OS_ERROR{ENOENT} && $bIgnoreMissing))
298        {
299            logErrorResult($OS_ERROR{ENOENT} ? ERROR_FILE_MISSING : ERROR_FILE_OPEN, "unable to read path '${strPath}'", $OS_ERROR);
300        }
301    }
302
303    # Return from function and log return values if any
304    return logDebugReturn
305    (
306        $strOperation,
307        {name => 'stryFileList', value => \@stryFileList, ref => true, trace => true}
308    );
309}
310
311####################################################################################################################################
312# manifest - build path/file/link manifest starting with base path and including all subpaths
313####################################################################################################################################
314sub manifest
315{
316    my $self = shift;
317
318    # Assign function parameters, defaults, and log debug info
319    my
320    (
321        $strOperation,
322        $strPath,
323        $bIgnoreMissing,
324        $strFilter,
325    ) =
326        logDebugParam
327        (
328            __PACKAGE__ . '->manifest', \@_,
329            {name => 'strPath', trace => true},
330            {name => 'bIgnoreMissing', optional => true, default => false, trace => true},
331            {name => 'strFilter', optional => true, trace => true},
332        );
333
334    # Generate the manifest
335    my $hManifest = {};
336    $self->manifestRecurse($strPath, undef, 0, $hManifest, $bIgnoreMissing, $strFilter);
337
338    # Return from function and log return values if any
339    return logDebugReturn
340    (
341        $strOperation,
342        {name => 'hManifest', value => $hManifest, trace => true}
343    );
344}
345
346sub manifestRecurse
347{
348    my $self = shift;
349
350    # Assign function parameters, defaults, and log debug info
351    my
352    (
353        $strOperation,
354        $strPath,
355        $strSubPath,
356        $iDepth,
357        $hManifest,
358        $bIgnoreMissing,
359        $strFilter,
360    ) =
361        logDebugParam
362        (
363            __PACKAGE__ . '::manifestRecurse', \@_,
364            {name => 'strPath', trace => true},
365            {name => 'strSubPath', required => false, trace => true},
366            {name => 'iDepth', default => 0, trace => true},
367            {name => 'hManifest', required => false, trace => true},
368            {name => 'bIgnoreMissing', required => false, default => false, trace => true},
369            {name => 'strFilter', required => false, trace => true},
370        );
371
372    # Set operation and debug strings
373    my $strPathRead = $strPath . (defined($strSubPath) ? "/${strSubPath}" : '');
374    my $hPath;
375
376    # If this is the top level stat the path to discover if it is actually a file
377    my $oPathInfo = $self->info($strPathRead, {bIgnoreMissing => $bIgnoreMissing});
378
379    if (defined($oPathInfo))
380    {
381        # If the initial path passed is a file then generate the manifest for just that file
382        if ($iDepth == 0 && !S_ISDIR($oPathInfo->mode()))
383        {
384            $hManifest->{basename($strPathRead)} = $self->manifestStat($strPathRead);
385        }
386        # Else read as a normal directory
387        else
388        {
389            # Get a list of all files in the path (including .)
390            my @stryFileList = @{$self->list($strPathRead, {bIgnoreMissing => $iDepth != 0})};
391            unshift(@stryFileList, '.');
392            my $hFileStat = $self->manifestList($strPathRead, \@stryFileList, $strFilter);
393
394            # Loop through all subpaths/files in the path
395            foreach my $strFile (keys(%{$hFileStat}))
396            {
397                my $strManifestFile = $iDepth == 0 ? $strFile : ($strSubPath . ($strFile eq qw(.) ? '' : "/${strFile}"));
398                $hManifest->{$strManifestFile} = $hFileStat->{$strFile};
399
400                # Recurse into directories
401                if ($hManifest->{$strManifestFile}{type} eq 'd' && $strFile ne qw(.))
402                {
403                    $self->manifestRecurse($strPath, $strManifestFile, $iDepth + 1, $hManifest);
404                }
405            }
406        }
407    }
408
409    # Return from function and log return values if any
410    return logDebugReturn($strOperation);
411}
412
413sub manifestList
414{
415    my $self = shift;
416
417    # Assign function parameters, defaults, and log debug info
418    my
419    (
420        $strOperation,
421        $strPath,
422        $stryFile,
423        $strFilter,
424    ) =
425        logDebugParam
426        (
427            __PACKAGE__ . '->manifestList', \@_,
428            {name => 'strPath', trace => true},
429            {name => 'stryFile', trace => true},
430            {name => 'strFilter', required => false, trace => true},
431        );
432
433    my $hFileStat = {};
434
435    foreach my $strFile (@{$stryFile})
436    {
437        if ($strFile ne '.' && defined($strFilter) && $strFilter ne $strFile)
438        {
439            next;
440        }
441
442        $hFileStat->{$strFile} = $self->manifestStat("${strPath}" . ($strFile eq qw(.) ? '' : "/${strFile}"));
443
444        if (!defined($hFileStat->{$strFile}))
445        {
446            delete($hFileStat->{$strFile});
447        }
448    }
449
450    # Return from function and log return values if any
451    return logDebugReturn
452    (
453        $strOperation,
454        {name => 'hFileStat', value => $hFileStat, trace => true}
455    );
456}
457
458sub manifestStat
459{
460    my $self = shift;
461
462    # Assign function parameters, defaults, and log debug info
463    my
464    (
465        $strOperation,
466        $strFile,
467    ) =
468        logDebugParam
469        (
470            __PACKAGE__ . '->manifestStat', \@_,
471            {name => 'strFile', trace => true},
472        );
473
474    # Stat the path/file, ignoring any that are missing
475    my $oStat = $self->info($strFile, {bIgnoreMissing => true});
476
477    # Generate file data if stat succeeded (i.e. file exists)
478    my $hFile;
479
480    if (defined($oStat))
481    {
482        # Check for regular file
483        if (S_ISREG($oStat->mode))
484        {
485            $hFile->{type} = 'f';
486
487            # Get size
488            $hFile->{size} = $oStat->size;
489
490            # Get modification time
491            $hFile->{modification_time} = $oStat->mtime;
492        }
493        # Check for directory
494        elsif (S_ISDIR($oStat->mode))
495        {
496            $hFile->{type} = 'd';
497        }
498        # Check for link
499        elsif (S_ISLNK($oStat->mode))
500        {
501            $hFile->{type} = 'l';
502            $hFile->{link_destination} = $self->linkDestination($strFile);
503        }
504        # Not a recognized type
505        else
506        {
507            confess &log(ERROR, "${strFile} is not of type directory, file, or link", ERROR_FILE_INVALID);
508        }
509
510        # Get user name
511        $hFile->{user} = getpwuid($oStat->uid);
512
513        # Get group name
514        $hFile->{group} = getgrgid($oStat->gid);
515
516        # Get mode
517        if ($hFile->{type} ne 'l')
518        {
519            $hFile->{mode} = sprintf('%04o', S_IMODE($oStat->mode));
520        }
521    }
522
523    # Return from function and log return values if any
524    return logDebugReturn
525    (
526        $strOperation,
527        {name => 'hFile', value => $hFile, trace => true}
528    );
529}
530
531####################################################################################################################################
532# move - move path/file
533####################################################################################################################################
534sub move
535{
536    my $self = shift;
537
538    # Assign function parameters, defaults, and log debug info
539    my
540    (
541        $strOperation,
542        $strSourceFile,
543        $strDestinationFile,
544        $bPathCreate,
545    ) =
546        logDebugParam
547        (
548            __PACKAGE__ . '->move', \@_,
549            {name => 'strSourceFile', trace => true},
550            {name => 'strDestinationFile', trace => true},
551            {name => 'bPathCreate', default => false, trace => true},
552        );
553
554    # Get source and destination paths
555    my $strSourcePathFile = dirname($strSourceFile);
556    my $strDestinationPathFile = dirname($strDestinationFile);
557
558    # Move the file
559    if (!rename($strSourceFile, $strDestinationFile))
560    {
561        my $strMessage = "unable to move '${strSourceFile}'";
562
563        # If something is missing determine if it is the source or destination
564        if ($OS_ERROR{ENOENT})
565        {
566            if (!$self->exists($strSourceFile))
567            {
568                logErrorResult(ERROR_FILE_MISSING, "${strMessage} because it is missing");
569            }
570
571            if ($bPathCreate)
572            {
573                # Attempt to create the path - ignore exists here in case another process creates it first
574                $self->pathCreate($strDestinationPathFile, {bCreateParent => true, bIgnoreExists => true});
575
576                # Try move again
577                $self->move($strSourceFile, $strDestinationFile);
578            }
579            else
580            {
581                logErrorResult(ERROR_PATH_MISSING, "${strMessage} to missing path '${strDestinationPathFile}'");
582            }
583        }
584        # Else raise the error
585        else
586        {
587            logErrorResult(ERROR_FILE_MOVE, "${strMessage} to '${strDestinationFile}'", $OS_ERROR);
588        }
589    }
590
591    # Return from function and log return values if any
592    return logDebugReturn($strOperation);
593}
594
595####################################################################################################################################
596# openRead - open file for reading
597####################################################################################################################################
598sub openRead
599{
600    my $self = shift;
601
602    # Assign function parameters, defaults, and log debug info
603    my
604    (
605        $strOperation,
606        $strFile,
607        $bIgnoreMissing,
608    ) =
609        logDebugParam
610    (
611        __PACKAGE__ . '->openRead', \@_,
612        {name => 'strFile', trace => true},
613        {name => 'bIgnoreMissing', optional => true, default => false, trace => true},
614    );
615
616    my $oFileIO = new pgBackRestTest::Common::StoragePosixRead($self, $strFile, {bIgnoreMissing => $bIgnoreMissing});
617
618    # Return from function and log return values if any
619    return logDebugReturn
620    (
621        $strOperation,
622        {name => 'oFileIO', value => $oFileIO, trace => true},
623    );
624}
625
626####################################################################################################################################
627# openWrite - open file for writing
628####################################################################################################################################
629sub openWrite
630{
631    my $self = shift;
632
633    # Assign function parameters, defaults, and log debug info
634    my
635    (
636        $strOperation,
637        $strFile,
638        $strMode,
639        $strUser,
640        $strGroup,
641        $lTimestamp,
642        $bPathCreate,
643        $bAtomic,
644    ) =
645        logDebugParam
646    (
647        __PACKAGE__ . '->openWrite', \@_,
648        {name => 'strFile', trace => true},
649        {name => 'strMode', optional => true, trace => true},
650        {name => 'strUser', optional => true, trace => true},
651        {name => 'strGroup', optional => true, trace => true},
652        {name => 'lTimestamp', optional => true, trace => true},
653        {name => 'bPathCreate', optional => true, trace => true},
654        {name => 'bAtomic', optional => true, trace => true},
655    );
656
657    my $oFileIO = new pgBackRestTest::Common::StoragePosixWrite(
658        $self, $strFile,
659        {strMode => $strMode, strUser => $strUser, strGroup => $strGroup, lTimestamp => $lTimestamp, bPathCreate => $bPathCreate,
660            bAtomic => $bAtomic, bSync => $self->{bFileSync}});
661
662    # Return from function and log return values if any
663    return logDebugReturn
664    (
665        $strOperation,
666        {name => 'oFileIO', value => $oFileIO, trace => true},
667    );
668}
669
670####################################################################################################################################
671# owner - change ownership of path/file
672####################################################################################################################################
673sub owner
674{
675    my $self = shift;
676
677    # Assign function parameters, defaults, and log debug info
678    my
679    (
680        $strOperation,
681        $strFilePath,
682        $strUser,
683        $strGroup,
684    ) =
685        logDebugParam
686        (
687            __PACKAGE__ . '->owner', \@_,
688            {name => 'strFilePath', trace => true},
689            {name => 'strUser', optional => true, trace => true},
690            {name => 'strGroup', optional => true, trace => true},
691        );
692
693    # Only proceed if user or group was specified
694    if (defined($strUser) || defined($strGroup))
695    {
696        my $strMessage = "unable to set ownership for '${strFilePath}'";
697        my $iUserId;
698        my $iGroupId;
699
700        # If the user or group is not defined then get it by stat'ing the file.  This is because the chown function requires that
701        # both user and group be set.
702        my $oStat = $self->info($strFilePath);
703
704        if (!defined($strUser))
705        {
706            $iUserId = $oStat->uid;
707        }
708
709        if (!defined($strGroup))
710        {
711            $iGroupId = $oStat->gid;
712        }
713
714        # Lookup user if specified
715        if (defined($strUser))
716        {
717            $iUserId = getpwnam($strUser);
718
719            if (!defined($iUserId))
720            {
721                logErrorResult(ERROR_FILE_OWNER, "${strMessage} because user '${strUser}' does not exist");
722            }
723        }
724
725        # Lookup group if specified
726        if (defined($strGroup))
727        {
728            $iGroupId = getgrnam($strGroup);
729
730            if (!defined($iGroupId))
731            {
732                logErrorResult(ERROR_FILE_OWNER, "${strMessage} because group '${strGroup}' does not exist");
733            }
734        }
735
736        # Set ownership on the file if the user or group would be changed
737        if ($iUserId != $oStat->uid || $iGroupId != $oStat->gid)
738        {
739            if (!chown($iUserId, $iGroupId, $strFilePath))
740            {
741                logErrorResult(ERROR_FILE_OWNER, "${strMessage}", $OS_ERROR);
742            }
743        }
744    }
745
746    # Return from function and log return values if any
747    return logDebugReturn($strOperation);
748}
749
750####################################################################################################################################
751# pathCreate - create path
752####################################################################################################################################
753sub pathCreate
754{
755    my $self = shift;
756
757    # Assign function parameters, defaults, and log debug info
758    my
759    (
760        $strOperation,
761        $strPath,
762        $strMode,
763        $bIgnoreExists,
764        $bCreateParent,
765    ) =
766        logDebugParam
767        (
768            __PACKAGE__ . '->pathCreate', \@_,
769            {name => 'strPath', trace => true},
770            {name => 'strMode', optional => true, default => '0750', trace => true},
771            {name => 'bIgnoreExists', optional => true, default => false, trace => true},
772            {name => 'bCreateParent', optional => true, default => false, trace => true},
773        );
774
775    # Attempt to create the directory
776    if (!mkdir($strPath, oct($strMode)))
777    {
778        my $strMessage = "unable to create path '${strPath}'";
779
780        # If parent path is missing
781        if ($OS_ERROR{ENOENT})
782        {
783            if (!$bCreateParent)
784            {
785                confess &log(ERROR, "${strMessage} because parent does not exist", ERROR_PATH_MISSING);
786            }
787
788            # Create parent path
789            $self->pathCreate(dirname($strPath), {strMode => $strMode, bIgnoreExists => true, bCreateParent => $bCreateParent});
790
791            # Create path
792            $self->pathCreate($strPath, {strMode => $strMode, bIgnoreExists => true});
793        }
794        # Else if path already exists
795        elsif ($OS_ERROR{EEXIST})
796        {
797            if (!$bIgnoreExists)
798            {
799                confess &log(ERROR, "${strMessage} because it already exists", ERROR_PATH_EXISTS);
800            }
801        }
802        else
803        {
804            logErrorResult(ERROR_PATH_CREATE, ${strMessage}, $OS_ERROR);
805        }
806    }
807
808    # Return from function and log return values if any
809    return logDebugReturn($strOperation);
810}
811
812####################################################################################################################################
813# pathExists - check if path exists
814####################################################################################################################################
815sub pathExists
816{
817    my $self = shift;
818
819    # Assign function parameters, defaults, and log debug info
820    my
821    (
822        $strOperation,
823        $strPath,
824    ) =
825        logDebugParam
826        (
827            __PACKAGE__ . '->pathExists', \@_,
828            {name => 'strPath', trace => true},
829        );
830
831    # Does the path/file exist?
832    my $bExists = true;
833    my $oStat = lstat($strPath);
834
835    # Use stat to test if path exists
836    if (defined($oStat))
837    {
838        # Check that it is actually a path
839        $bExists = S_ISDIR($oStat->mode) ? true : false;
840    }
841    else
842    {
843        # If the error is not entry missing, then throw error
844        if (!$OS_ERROR{ENOENT})
845        {
846            logErrorResult(ERROR_FILE_EXISTS, "unable to test if path '${strPath}' exists", $OS_ERROR);
847        }
848
849        $bExists = false;
850    }
851
852    # Return from function and log return values if any
853    return logDebugReturn
854    (
855        $strOperation,
856        {name => 'bExists', value => $bExists, trace => true}
857    );
858}
859
860####################################################################################################################################
861# pathSync - perform fsync on path
862####################################################################################################################################
863sub pathSync
864{
865    my $self = shift;
866
867    # Assign function parameters, defaults, and log debug info
868    my
869    (
870        $strOperation,
871        $strPath,
872    ) =
873        logDebugParam
874        (
875            __PACKAGE__ . '->pathSync', \@_,
876            {name => 'strPath', trace => true},
877        );
878
879    open(my $hPath, "<", $strPath)
880        or confess &log(ERROR, "unable to open '${strPath}' for sync", ERROR_PATH_OPEN);
881    open(my $hPathDup, ">&", $hPath)
882        or confess &log(ERROR, "unable to duplicate '${strPath}' handle for sync", ERROR_PATH_OPEN);
883
884    $hPathDup->sync()
885        or confess &log(ERROR, "unable to sync path '${strPath}'", ERROR_PATH_SYNC);
886
887    close($hPathDup);
888    close($hPath);
889
890    # Return from function and log return values if any
891    return logDebugReturn($strOperation);
892}
893
894####################################################################################################################################
895# remove - remove path/file
896####################################################################################################################################
897sub remove
898{
899    my $self = shift;
900
901    # Assign function parameters, defaults, and log debug info
902    my
903    (
904        $strOperation,
905        $xstryPathFile,
906        $bIgnoreMissing,
907        $bRecurse,
908    ) =
909        logDebugParam
910        (
911            __PACKAGE__ . '->remove', \@_,
912            {name => 'xstryPathFile', trace => true},
913            {name => 'bIgnoreMissing', optional => true, default => false, trace => true},
914            {name => 'bRecurse', optional => true, default => false, trace => true},
915        );
916
917    # Working variables
918    my $bRemoved = true;
919
920    # Remove a tree
921    if ($bRecurse)
922    {
923        my $oManifest = $self->manifest($xstryPathFile, {bIgnoreMissing => true});
924
925        # Iterate all files in the manifest
926        foreach my $strFile (sort({$b cmp $a} keys(%{$oManifest})))
927        {
928            # remove directory
929            if ($oManifest->{$strFile}{type} eq 'd')
930            {
931                my $xstryPathFileRemove = $strFile eq '.' ? $xstryPathFile : "${xstryPathFile}/${strFile}";
932
933                if (!rmdir($xstryPathFileRemove))
934                {
935                    # Throw error if this is not an ignored missing path
936                    if (!($OS_ERROR{ENOENT} && $bIgnoreMissing))
937                    {
938                        logErrorResult(ERROR_PATH_REMOVE, "unable to remove path '${strFile}'", $OS_ERROR);
939                    }
940                }
941            }
942            # Remove file
943            else
944            {
945                $self->remove("${xstryPathFile}/${strFile}", {bIgnoreMissing => true});
946            }
947        }
948    }
949    # Only remove the specified file
950    else
951    {
952        foreach my $strFile (ref($xstryPathFile) ? @{$xstryPathFile} : ($xstryPathFile))
953        {
954            if (unlink($strFile) != 1)
955            {
956                $bRemoved = false;
957
958                # Throw error if this is not an ignored missing file
959                if (!($OS_ERROR{ENOENT} && $bIgnoreMissing))
960                {
961                    logErrorResult(
962                        $OS_ERROR{ENOENT} ? ERROR_FILE_MISSING : ERROR_FILE_OPEN, "unable to remove file '${strFile}'", $OS_ERROR);
963                }
964            }
965        }
966    }
967
968    # Return from function and log return values if any
969    return logDebugReturn
970    (
971        $strOperation,
972        {name => 'bRemoved', value => $bRemoved, trace => true}
973    );
974}
975
976####################################################################################################################################
977# Getters/Setters
978####################################################################################################################################
979sub className {STORAGE_POSIX_DRIVER}
980sub tempExtension {shift->{strTempExtension}}
981sub tempExtensionSet {my $self = shift; $self->{strTempExtension} = shift}
982sub type {STORAGE_POSIX}
983
9841;
985