| line | stmt | bran | cond | sub | pod | time | code | 
| 1 |  |  |  |  |  |  | package DataStore::CAS::FS; | 
| 2 | 4 |  |  | 4 |  | 706838 | use 5.008; | 
|  | 4 |  |  |  |  | 15 |  | 
|  | 4 |  |  |  |  | 173 |  | 
| 3 | 4 |  |  | 4 |  | 718 | use Moo 1.000007; | 
|  | 4 |  |  |  |  | 15264 |  | 
|  | 4 |  |  |  |  | 32 |  | 
| 4 | 4 |  |  | 4 |  | 3119 | use Carp; | 
|  | 4 |  |  |  |  | 8 |  | 
|  | 4 |  |  |  |  | 285 |  | 
| 5 | 4 |  |  | 4 |  | 787 | use Try::Tiny 0.11; | 
|  | 4 |  |  |  |  | 1373 |  | 
|  | 4 |  |  |  |  | 235 |  | 
| 6 | 4 |  |  | 4 |  | 23 | use File::Spec 3.33; | 
|  | 4 |  |  |  |  | 107 |  | 
|  | 4 |  |  |  |  | 96 |  | 
| 7 | 4 |  |  | 4 |  | 713 | use DataStore::CAS 0.02; | 
|  | 4 |  |  |  |  | 28825 |  | 
|  | 4 |  |  |  |  | 25513 |  | 
| 8 |  |  |  |  |  |  |  | 
| 9 |  |  |  |  |  |  | our $VERSION= '0.011000'; | 
| 10 |  |  |  |  |  |  |  | 
| 11 |  |  |  |  |  |  | require DataStore::CAS::FS::Dir; | 
| 12 |  |  |  |  |  |  | require DataStore::CAS::FS::DirCodec::Universal; | 
| 13 |  |  |  |  |  |  | require DataStore::CAS::FS::DirCodec::Minimal; | 
| 14 |  |  |  |  |  |  | require DataStore::CAS::FS::DirCodec::Unix; | 
| 15 |  |  |  |  |  |  |  | 
| 16 |  |  |  |  |  |  | # ABSTRACT: Virtual Filesystem backed by Content-Addressable Storage | 
| 17 |  |  |  |  |  |  |  | 
| 18 |  |  |  |  |  |  |  | 
| 19 |  |  |  |  |  |  | has store             => ( is => 'ro', required => 1, isa => \&_validate_cas ); | 
| 20 |  |  |  |  |  |  | has root_entry        => ( is => 'rwp', required => 1 ); | 
| 21 |  |  |  |  |  |  | has case_insensitive  => ( is => 'ro', default => sub { 0 } ); | 
| 22 |  |  |  |  |  |  |  | 
| 23 | 2 |  |  | 2 | 1 | 21 | sub hash_of_null         { $_[0]->store->hash_of_null } | 
| 24 |  |  |  |  |  |  | has hash_of_empty_dir => ( is => 'lazy' ); | 
| 25 |  |  |  |  |  |  |  | 
| 26 |  |  |  |  |  |  | has dir_cache         => ( is => 'rw', default => sub { DataStore::CAS::FS::DirCache->new() } ); | 
| 27 |  |  |  |  |  |  |  | 
| 28 |  |  |  |  |  |  | # _nodes is a tree of nodes, each of the form: | 
| 29 |  |  |  |  |  |  | # $node= { | 
| 30 |  |  |  |  |  |  | #   dirent  => $Dir_Entry,  # mandatory | 
| 31 |  |  |  |  |  |  | #   dir     => $CAS_FS_Dir, # optional, created on demand | 
| 32 |  |  |  |  |  |  | #   subtree => { | 
| 33 |  |  |  |  |  |  | #     KEY1 => $node1, | 
| 34 |  |  |  |  |  |  | #     KEY2 => $node2, | 
| 35 |  |  |  |  |  |  | #     ... | 
| 36 |  |  |  |  |  |  | #   } | 
| 37 |  |  |  |  |  |  | #   changed => 1 # set if a path override has happened here, or in any child node | 
| 38 |  |  |  |  |  |  | #   invalid => 1 # set whenever a path override deletes this node | 
| 39 |  |  |  |  |  |  | # } | 
| 40 |  |  |  |  |  |  | # | 
| 41 |  |  |  |  |  |  | #  If 'case_insensitive' is true, the keys will all be upper-case, but the $Dir_Entry | 
| 42 |  |  |  |  |  |  | #  objects will contain the correct-case name. | 
| 43 |  |  |  |  |  |  | # | 
| 44 |  |  |  |  |  |  | has _nodes   => ( is => 'rw' ); | 
| 45 |  |  |  |  |  |  |  | 
| 46 |  |  |  |  |  |  |  | 
| 47 |  |  |  |  |  |  | sub _build_hash_of_empty_dir { | 
| 48 | 2 |  |  | 2 |  | 975 | my $self= shift; | 
| 49 | 2 |  |  |  |  | 26 | my $empty= DataStore::CAS::FS::DirCodec::Minimal->encode([],{}); | 
| 50 | 2 |  |  |  |  | 105 | return $self->store->put_scalar($empty); | 
| 51 |  |  |  |  |  |  | } | 
| 52 |  |  |  |  |  |  |  | 
| 53 |  |  |  |  |  |  | sub _validate_cas { | 
| 54 | 7 |  |  | 7 |  | 181 | my $cas= shift; | 
| 55 | 7 | 50 | 33 |  |  | 333 | ref($cas) && ref($cas)->can('get') && ref($cas)->can('put') | 
|  |  |  | 33 |  |  |  |  | 
| 56 |  |  |  |  |  |  | or croak "Invalid CAS object: $cas" | 
| 57 |  |  |  |  |  |  | }; | 
| 58 |  |  |  |  |  |  |  | 
| 59 |  |  |  |  |  |  | sub BUILDARGS { | 
| 60 | 7 |  |  | 7 | 0 | 33614 | my $class= shift; | 
| 61 | 7 | 50 | 33 |  |  | 70 | my %p= (@_ == 1 && ref $_[0] eq 'HASH')? %{$_[0]} : @_; | 
|  | 0 |  |  |  |  | 0 |  | 
| 62 |  |  |  |  |  |  | # Root is an alias for root_entry | 
| 63 | 7 | 50 |  |  |  | 35 | if (defined $p{root}) { | 
| 64 | 7 | 50 |  |  |  | 30 | defined $p{root_entry} | 
| 65 |  |  |  |  |  |  | and croak "Specify only one of 'root' or 'root_entry'"; | 
| 66 | 7 |  |  |  |  | 24 | $p{root_entry}= delete $p{root}; | 
| 67 |  |  |  |  |  |  | } | 
| 68 | 7 |  |  |  |  | 176 | return \%p; | 
| 69 |  |  |  |  |  |  | } | 
| 70 |  |  |  |  |  |  |  | 
| 71 |  |  |  |  |  |  | sub BUILD { | 
| 72 | 7 |  |  | 7 | 0 | 112 | my ($self, $args)= @_; | 
| 73 | 7 |  |  |  |  | 24 | my @invalid= grep { !$self->can($_) } keys %$args; | 
|  | 14 |  |  |  |  | 82 |  | 
| 74 | 7 | 50 |  |  |  | 27 | croak "Invalid param(s): ".join(', ', @invalid) | 
| 75 |  |  |  |  |  |  | if @invalid; | 
| 76 |  |  |  |  |  |  |  | 
| 77 | 7 | 50 | 33 |  |  | 130 | croak "Missing/Invalid parameter 'dir_cache'" | 
| 78 |  |  |  |  |  |  | unless defined $self->dir_cache and $self->dir_cache->can('clear'); | 
| 79 |  |  |  |  |  |  |  | 
| 80 |  |  |  |  |  |  | # coerce root_entry to an actual DirEnt object | 
| 81 | 7 |  |  |  |  | 29 | my $root= $self->root_entry; | 
| 82 | 7 | 50 |  |  |  | 27 | defined $root | 
| 83 |  |  |  |  |  |  | or croak "root_entry is required"; | 
| 84 | 7 | 100 | 66 |  |  | 122 | unless (ref $root && ref($root)->isa('DataStore::CAS::FS::DirEnt')) { | 
| 85 | 2 | 0 |  |  |  | 24 | $self->_set_root_entry( | 
|  |  | 50 |  |  |  |  |  | 
|  |  | 50 |  |  |  |  |  | 
| 86 |  |  |  |  |  |  | DataStore::CAS::FS::DirEnt->new({ | 
| 87 |  |  |  |  |  |  | type => 'dir', | 
| 88 |  |  |  |  |  |  | name => '', | 
| 89 |  |  |  |  |  |  | # Assume scalars are digest_hash values. | 
| 90 |  |  |  |  |  |  | !ref $root? ( ref => $root ) | 
| 91 |  |  |  |  |  |  | # Hashrefs might be empty, to indicate an empty directory | 
| 92 |  |  |  |  |  |  | : ref $root eq 'HASH'? ( ref => $self->hash_of_empty_dir, %$root ) | 
| 93 |  |  |  |  |  |  | # Is it a ::File or ::Dir object? | 
| 94 |  |  |  |  |  |  | : ref($root)->can('hash')? ( ref => $root->hash ) | 
| 95 |  |  |  |  |  |  | # Else take a guess that it is a digest_hash wrapped in an object | 
| 96 |  |  |  |  |  |  | : ( ref => "$root" ) | 
| 97 |  |  |  |  |  |  | }) | 
| 98 |  |  |  |  |  |  | ); | 
| 99 |  |  |  |  |  |  | } | 
| 100 | 7 | 50 | 33 |  |  | 435 | croak "Invalid parameter 'root_entry'" | 
|  |  |  | 33 |  |  |  |  | 
|  |  |  | 33 |  |  |  |  | 
| 101 |  |  |  |  |  |  | unless ref $self->root_entry | 
| 102 |  |  |  |  |  |  | and ref($self->root_entry)->can('type') | 
| 103 |  |  |  |  |  |  | and $self->root_entry->type eq 'dir' | 
| 104 |  |  |  |  |  |  | and defined $self->root_entry->ref; | 
| 105 |  |  |  |  |  |  | # If they gave us a 'root_entry', make sure we can load it | 
| 106 | 7 | 50 |  |  |  | 306 | $self->get_dir($self->root_entry->ref) | 
| 107 |  |  |  |  |  |  | or croak "Unable to load root directory '".$self->root_entry->ref."'"; | 
| 108 |  |  |  |  |  |  | } | 
| 109 |  |  |  |  |  |  |  | 
| 110 |  |  |  |  |  |  |  | 
| 111 |  |  |  |  |  |  | sub get { | 
| 112 | 2 |  |  | 2 | 1 | 15 | (shift)->store->get(@_); | 
| 113 |  |  |  |  |  |  | } | 
| 114 |  |  |  |  |  |  |  | 
| 115 |  |  |  |  |  |  |  | 
| 116 |  |  |  |  |  |  | sub get_dir { | 
| 117 | 124 |  |  | 124 | 1 | 806 | my ($self, $hash_or_file, $flags)= @_; | 
| 118 | 124 | 50 | 33 |  |  | 710 | my ($hash, $file)= (ref $hash_or_file and $hash_or_file->can('hash')) | 
| 119 |  |  |  |  |  |  | ? ( $hash_or_file->hash, $hash_or_file ) | 
| 120 |  |  |  |  |  |  | : ( $hash_or_file, undef ); | 
| 121 |  |  |  |  |  |  |  | 
| 122 | 124 |  |  |  |  | 648 | my $dir= $self->dir_cache->get($hash); | 
| 123 | 124 | 100 |  |  |  | 667 | return $dir if defined $dir; | 
| 124 |  |  |  |  |  |  |  | 
| 125 |  |  |  |  |  |  | # Return undef if the directory doesn't exist. | 
| 126 |  |  |  |  |  |  | return undef | 
| 127 | 48 | 50 | 33 |  |  | 315 | unless defined ($file ||= $self->store->get($hash)); | 
| 128 |  |  |  |  |  |  |  | 
| 129 |  |  |  |  |  |  | # Deserialize directory.  This can throw exceptions if it isn't a valid encoding. | 
| 130 | 48 |  |  |  |  | 1019 | $dir= DataStore::CAS::FS::DirCodec->load($file); | 
| 131 |  |  |  |  |  |  | # Cache it | 
| 132 | 48 |  |  |  |  | 343 | $self->dir_cache->put($dir); | 
| 133 | 48 |  |  |  |  | 863 | return $dir; | 
| 134 |  |  |  |  |  |  | } | 
| 135 |  |  |  |  |  |  |  | 
| 136 |  |  |  |  |  |  |  | 
| 137 | 0 |  |  | 0 | 1 | 0 | sub put        { (shift)->store->put(@_) } | 
| 138 | 0 |  |  | 0 | 1 | 0 | sub put_scalar { (shift)->store->put_scalar(@_) } | 
| 139 | 0 |  |  | 0 | 1 | 0 | sub put_file   { (shift)->store->put_file(@_) } | 
| 140 | 0 |  |  | 0 | 1 | 0 | sub put_handle { (shift)->store->put_handle(@_) } | 
| 141 | 0 |  |  | 0 | 1 | 0 | sub validate   { (shift)->store->validate(@_) } | 
| 142 |  |  |  |  |  |  |  | 
| 143 |  |  |  |  |  |  |  | 
| 144 |  |  |  |  |  |  | sub path { | 
| 145 | 10 |  |  | 10 | 1 | 9148 | bless { filesystem => (shift), path_names => [ map { File::Spec->splitdir($_) } @_ ] }, | 
|  | 18 |  |  |  |  | 247 |  | 
| 146 |  |  |  |  |  |  | 'DataStore::CAS::FS::Path'; | 
| 147 |  |  |  |  |  |  | } | 
| 148 |  |  |  |  |  |  |  | 
| 149 |  |  |  |  |  |  |  | 
| 150 |  |  |  |  |  |  | sub path_if_exists { | 
| 151 | 2 |  |  | 2 | 1 | 6802 | my $self= shift; | 
| 152 | 2 |  |  |  |  | 9 | my $path= $self->path(@_); | 
| 153 | 2 | 100 |  |  |  | 13 | $path->resolve({no_die => 1})? $path : undef; | 
| 154 |  |  |  |  |  |  | } | 
| 155 |  |  |  |  |  |  |  | 
| 156 |  |  |  |  |  |  |  | 
| 157 |  |  |  |  |  |  | sub tree_iterator { | 
| 158 | 3 |  |  | 3 | 1 | 7289 | my $self= shift; | 
| 159 | 3 | 50 | 33 |  |  | 32 | my %p= (@_ == 1 && ref $_[0] eq 'HASH')? %{$_[0]} : @_; | 
|  | 0 |  |  |  |  | 0 |  | 
| 160 | 3 |  |  |  |  | 34 | return DataStore::CAS::FS::TreeIterator->new( | 
| 161 |  |  |  |  |  |  | path => [], | 
| 162 |  |  |  |  |  |  | %p, | 
| 163 |  |  |  |  |  |  | fs => $self | 
| 164 |  |  |  |  |  |  | ); | 
| 165 |  |  |  |  |  |  | } | 
| 166 |  |  |  |  |  |  |  | 
| 167 |  |  |  |  |  |  |  | 
| 168 |  |  |  |  |  |  |  | 
| 169 |  |  |  |  |  |  | sub resolve_path { | 
| 170 | 21 |  |  | 21 | 1 | 552 | my ($self, $path, $flags)= @_; | 
| 171 | 21 |  | 50 |  |  | 119 | $flags ||= {}; | 
| 172 |  |  |  |  |  |  |  | 
| 173 | 21 |  |  |  |  | 108 | my $ret= $self->_resolve_path(undef, $path, { follow_symlinks => 1, %$flags }); | 
| 174 |  |  |  |  |  |  |  | 
| 175 |  |  |  |  |  |  | # Array means success, scalar means error. | 
| 176 | 21 | 50 |  |  |  | 92 | if (ref($ret) eq 'ARRAY') { | 
| 177 |  |  |  |  |  |  | # The user wants directory entries, not "nodes". | 
| 178 | 21 |  |  |  |  | 165 | $_= $_->{dirent} for @$ret; | 
| 179 | 21 |  |  |  |  | 668 | return $ret; | 
| 180 |  |  |  |  |  |  | } | 
| 181 |  |  |  |  |  |  |  | 
| 182 |  |  |  |  |  |  | # else, got an error... | 
| 183 | 0 | 0 |  |  |  | 0 | ${$flags->{error_out}}= $ret | 
|  | 0 |  |  |  |  | 0 |  | 
| 184 |  |  |  |  |  |  | if ref $flags->{error_out}; | 
| 185 | 0 | 0 |  |  |  | 0 | croak $ret unless $flags->{no_die}; | 
| 186 | 0 |  |  |  |  | 0 | return undef; | 
| 187 |  |  |  |  |  |  | } | 
| 188 |  |  |  |  |  |  |  | 
| 189 |  |  |  |  |  |  | sub _resolve_path { | 
| 190 | 241 |  |  | 241 |  | 479 | my ($self, $nodes, $path_names, $flags)= @_; | 
| 191 |  |  |  |  |  |  |  | 
| 192 | 241 | 100 |  |  |  | 1413 | my @path= ref($path_names)? @$path_names : File::Spec->splitdir($path_names); | 
| 193 | 241 |  | 100 |  |  | 853 | $nodes ||= []; | 
| 194 | 241 | 100 | 100 |  |  | 1005 | push @$nodes, ($self->{_nodes} ||= { dirent => $self->root_entry }) | 
| 195 |  |  |  |  |  |  | unless @$nodes; | 
| 196 |  |  |  |  |  |  |  | 
| 197 | 241 |  |  |  |  | 304 | my $mkdir_defaults; | 
| 198 |  |  |  |  |  |  | sub _build_mkdir_defaults { | 
| 199 | 3 |  |  | 3 |  | 20 | my $flags= shift; | 
| 200 | 3 | 50 |  |  |  | 13 | my @ret= %{$flags->{mkdir_defaults}} | 
|  | 0 |  |  |  |  | 0 |  | 
| 201 |  |  |  |  |  |  | if defined $flags->{mkdir_defaults}; | 
| 202 | 3 |  |  |  |  | 10 | push @ret, type => 'dir', ref => undef; | 
| 203 |  |  |  |  |  |  | \@ret | 
| 204 | 3 |  |  |  |  | 36 | } | 
| 205 |  |  |  |  |  |  |  | 
| 206 | 241 |  |  |  |  | 670 | while (@path) { | 
| 207 | 761 |  |  |  |  | 1704 | my $ent= $nodes->[-1]{dirent}; | 
| 208 | 761 |  |  |  |  | 882 | my $dir; | 
| 209 |  |  |  |  |  |  |  | 
| 210 |  |  |  |  |  |  | # Support for "symlink" is always UNIX-based (or compatible) | 
| 211 |  |  |  |  |  |  | # As support for other systems' symbolic paths are added, they | 
| 212 |  |  |  |  |  |  | # will be given unique '->type' values, and appropriate handling. | 
| 213 | 761 | 100 | 66 |  |  | 7715507 | if ($ent->type eq 'symlink' and $flags->{follow_symlinks}) { | 
| 214 |  |  |  |  |  |  | # Sanity check on symlink entry | 
| 215 | 4 |  |  |  |  | 130 | my $target= $ent->ref; | 
| 216 | 4 | 50 | 33 |  |  | 53 | defined $target and length $target | 
| 217 |  |  |  |  |  |  | or return 'Invalid symbolic link "'.$ent->name.'"'; | 
| 218 |  |  |  |  |  |  |  | 
| 219 | 4 |  |  |  |  | 24 | unshift @path, split('/', $target, -1); | 
| 220 | 4 |  |  |  |  | 10 | pop @$nodes; | 
| 221 |  |  |  |  |  |  |  | 
| 222 |  |  |  |  |  |  | # If an absolute link, we start over from the root | 
| 223 | 4 | 50 |  |  |  | 20 | @$nodes= ( $nodes->[0] ) | 
| 224 |  |  |  |  |  |  | if $path[0] eq ''; | 
| 225 |  |  |  |  |  |  |  | 
| 226 | 4 |  |  |  |  | 19 | next; | 
| 227 |  |  |  |  |  |  | } | 
| 228 |  |  |  |  |  |  |  | 
| 229 | 757 | 50 |  |  |  | 24407 | if ($ent->type ne 'dir') { | 
| 230 | 0 | 0 | 0 |  |  | 0 | return 'Cannot descend into directory entry "'.$ent->name.'" of type "'.$ent->type.'"' | 
| 231 |  |  |  |  |  |  | unless ($flags->{mkdir}||0) > 1; | 
| 232 |  |  |  |  |  |  | # Here, mkdir flag converts entry into a directory | 
| 233 | 0 |  | 0 |  |  | 0 | $nodes->[-1]{dirent}= $ent->clone(@{ $mkdir_defaults ||= _build_mkdir_defaults($flags)}); | 
|  | 0 |  |  |  |  | 0 |  | 
| 234 |  |  |  |  |  |  | } | 
| 235 |  |  |  |  |  |  |  | 
| 236 |  |  |  |  |  |  | # Get the next path component, ignoring empty and '.' | 
| 237 | 757 |  |  |  |  | 2138 | my $name= shift @path; | 
| 238 | 757 | 100 | 66 |  |  | 7539 | next unless defined $name and length $name and ($name ne '.'); | 
|  |  |  | 100 |  |  |  |  | 
| 239 |  |  |  |  |  |  |  | 
| 240 |  |  |  |  |  |  | # We handle '..' procedurally, moving up one real directory and *not* backing out of a symlink. | 
| 241 |  |  |  |  |  |  | # This is the same way the kernel does it, but perhaps shell behavior is preferred... | 
| 242 | 638 | 100 |  |  |  | 1815 | if ($name eq '..') { | 
| 243 | 9 | 50 |  |  |  | 29 | return "Cannot access '..' at root directory" | 
| 244 |  |  |  |  |  |  | unless @$nodes > 1; | 
| 245 | 9 |  |  |  |  | 12 | pop @$nodes; | 
| 246 | 9 |  |  |  |  | 44 | next; | 
| 247 |  |  |  |  |  |  | } | 
| 248 |  |  |  |  |  |  |  | 
| 249 |  |  |  |  |  |  | # If this directory has an in-memory override for this name, use it | 
| 250 | 629 |  |  |  |  | 749 | my $subnode; | 
| 251 | 629 | 100 |  |  |  | 1821 | if ($nodes->[-1]{subtree}) { | 
| 252 | 516 | 50 |  |  |  | 1881 | my $key= $self->case_insensitive? uc $name : $name; | 
| 253 | 516 |  |  |  |  | 1593 | $subnode= $nodes->[-1]{subtree}{$key}; | 
| 254 |  |  |  |  |  |  | } | 
| 255 |  |  |  |  |  |  |  | 
| 256 |  |  |  |  |  |  | # Else we need to find the name within the current directory | 
| 257 | 629 | 100 | 100 |  |  | 4493 | if (!defined $subnode && (defined $nodes->[-1]{dir} || defined $ent->ref)) { | 
|  |  |  | 66 |  |  |  |  | 
| 258 |  |  |  |  |  |  | # load it if it isn't cached | 
| 259 | 196 | 50 | 66 |  |  | 2928 | ($nodes->[-1]{dir} ||= $self->get_dir($ent->ref)) | 
| 260 |  |  |  |  |  |  | or return 'Failed to open directory "'.$ent->name.' ('.$ent->ref.')"'; | 
| 261 |  |  |  |  |  |  |  | 
| 262 |  |  |  |  |  |  | # See if the directory contains this entry | 
| 263 | 196 | 100 |  |  |  | 870 | if (defined (my $subent= $nodes->[-1]{dir}->get_entry($name))) { | 
| 264 | 190 |  |  |  |  | 577 | $subnode= { dirent => $subent }; | 
| 265 | 190 | 50 |  |  |  | 817 | my $key= $self->case_insensitive? uc $name : $name; | 
| 266 |  |  |  |  |  |  | # Weak reference, until _apply_overrides is called. | 
| 267 | 190 |  |  |  |  | 1647 | Scalar::Util::weaken( $nodes->[-1]{subtree}{$key}= $subnode ); | 
| 268 |  |  |  |  |  |  | } | 
| 269 |  |  |  |  |  |  | } | 
| 270 |  |  |  |  |  |  |  | 
| 271 |  |  |  |  |  |  | # If we haven't found one, or if it is 0 (deleted), either create or die. | 
| 272 | 629 | 100 |  |  |  | 1709 | if (!$subnode) { | 
| 273 |  |  |  |  |  |  | # If we're supposed to create virtual entries, do so | 
| 274 | 15 | 100 | 100 |  |  | 108 | if ($flags->{mkdir} or $flags->{partial}) { | 
| 275 | 6 |  | 66 |  |  | 30 | $subnode= { | 
| 276 |  |  |  |  |  |  | invalid => 1, # not valid until _apply_overrides | 
| 277 |  |  |  |  |  |  | dirent => DataStore::CAS::FS::DirEnt->new( | 
| 278 |  |  |  |  |  |  | name => $name, | 
| 279 |  |  |  |  |  |  | # It is a directory if there are more path components to resolve. | 
| 280 | 12 | 100 |  |  |  | 83 | (@path? @{ $mkdir_defaults ||= _build_mkdir_defaults($flags)} : ()) | 
| 281 |  |  |  |  |  |  | ) | 
| 282 |  |  |  |  |  |  | }; | 
| 283 |  |  |  |  |  |  | } | 
| 284 |  |  |  |  |  |  | # Else it doesn't exist and we fail. | 
| 285 |  |  |  |  |  |  | else { | 
| 286 | 3 |  |  |  |  | 11 | my $dir_path= File::Spec->catdir(map { $_->{dirent}->name } @$nodes); | 
|  | 4 |  |  |  |  | 150 |  | 
| 287 | 3 | 50 |  |  |  | 20 | return "Directory \"$dir_path\" is not present in storage" | 
| 288 |  |  |  |  |  |  | unless defined $nodes->[-1]{dir}; | 
| 289 | 3 |  |  |  |  | 22 | return "No such directory entry \"$name\" at \"$dir_path\""; | 
| 290 |  |  |  |  |  |  | } | 
| 291 |  |  |  |  |  |  | } | 
| 292 |  |  |  |  |  |  |  | 
| 293 | 626 |  |  |  |  | 2787 | push @$nodes, $subnode; | 
| 294 |  |  |  |  |  |  | } | 
| 295 |  |  |  |  |  |  |  | 
| 296 | 238 |  |  |  |  | 668 | $nodes; | 
| 297 |  |  |  |  |  |  | } | 
| 298 |  |  |  |  |  |  |  | 
| 299 |  |  |  |  |  |  |  | 
| 300 |  |  |  |  |  |  | sub get_dir_entries { | 
| 301 | 10 |  |  | 10 | 1 | 26 | my ($self, $path)= @_; | 
| 302 | 10 |  |  |  |  | 38 | my $nodes= $self->_resolve_path(undef, $path); | 
| 303 | 10 | 50 |  |  |  | 52 | ref $nodes | 
| 304 |  |  |  |  |  |  | or croak $nodes; | 
| 305 | 10 |  |  |  |  | 56 | return $self->_get_dir_entries($nodes->[-1]); | 
| 306 |  |  |  |  |  |  | } | 
| 307 |  |  |  |  |  |  |  | 
| 308 |  |  |  |  |  |  | sub readdir { | 
| 309 | 10 |  |  | 10 | 1 | 471 | my $self= shift; | 
| 310 | 10 |  |  |  |  | 24 | my @names= map { $_->name } @{ $self->get_dir_entries(@_) }; | 
|  | 17 |  |  |  |  | 550 |  | 
|  | 10 |  |  |  |  | 43 |  | 
| 311 | 10 | 50 |  |  |  | 132 | return wantarray? @names : \@names; | 
| 312 |  |  |  |  |  |  | } | 
| 313 |  |  |  |  |  |  |  | 
| 314 |  |  |  |  |  |  | # This method combines the original directory with its overrides. | 
| 315 |  |  |  |  |  |  | sub _get_dir_entries { | 
| 316 | 52 |  |  | 52 |  | 94 | my ($self, $node)= @_; | 
| 317 | 52 |  |  |  |  | 104 | my $ent= $node->{dirent}; | 
| 318 | 52 | 50 |  |  |  | 1992 | croak "Can't get listing for non-directory" | 
| 319 |  |  |  |  |  |  | unless $ent->type eq 'dir'; | 
| 320 | 52 |  |  |  |  | 125 | my %dirents; | 
| 321 |  |  |  |  |  |  | # load dir if it isn't cached | 
| 322 | 52 | 100 | 100 |  |  | 1807 | if (!defined $node->{dir} && defined $ent->ref) { | 
| 323 | 45 | 50 |  |  |  | 1578 | defined ( $node->{dir}= $self->get_dir($ent->ref) ) | 
| 324 |  |  |  |  |  |  | or return 'Failed to open directory "'.$ent->name.' ('.$ent->ref.')"'; | 
| 325 |  |  |  |  |  |  | } | 
| 326 | 52 |  |  |  |  | 184 | my $caseless= $self->case_insensitive; | 
| 327 | 52 | 100 |  |  |  | 156 | if (defined $node->{dir}) { | 
| 328 | 47 |  |  |  |  | 212 | my $iter= $node->{dir}->iterator; | 
| 329 | 47 |  |  |  |  | 132 | my $dirent; | 
| 330 | 47 | 50 |  |  |  | 162 | $dirents{$caseless? uc($dirent->name) : $dirent->name}= $dirent | 
| 331 |  |  |  |  |  |  | while defined ($dirent= $iter->()); | 
| 332 |  |  |  |  |  |  | } | 
| 333 | 52 | 100 |  |  |  | 307 | if (my $t= $node->{subtree}) { | 
| 334 | 6 |  |  |  |  | 31 | for (keys %$t) { | 
| 335 | 8 |  |  |  |  | 15 | my $subnode= $t->{$_}; | 
| 336 | 8 | 50 |  |  |  | 25 | next unless defined $subnode; | 
| 337 | 8 | 50 | 66 |  |  | 72 | die "BUG" if ref $subnode && $subnode->{invalid}; | 
| 338 | 8 | 100 |  |  |  | 25 | if (ref $subnode) { | 
| 339 | 7 | 50 |  |  |  | 45 | $dirents{$_}= $subnode->{dirent} | 
| 340 |  |  |  |  |  |  | if $subnode->{changed}; | 
| 341 |  |  |  |  |  |  | } else { | 
| 342 | 1 |  |  |  |  | 4 | delete $dirents{$_}; | 
| 343 |  |  |  |  |  |  | } | 
| 344 |  |  |  |  |  |  | } | 
| 345 |  |  |  |  |  |  | } | 
| 346 | 52 |  |  |  |  | 309 | return [ map { $dirents{$_} } sort keys %dirents ]; | 
|  | 109 |  |  |  |  | 436 |  | 
| 347 |  |  |  |  |  |  | } | 
| 348 |  |  |  |  |  |  |  | 
| 349 |  |  |  |  |  |  |  | 
| 350 |  |  |  |  |  |  | sub set_path { | 
| 351 | 4 |  |  | 4 | 1 | 823 | my ($self, $path, $newent, $flags)= @_; | 
| 352 | 4 |  | 50 |  |  | 32 | $flags ||= {}; | 
| 353 | 4 |  |  |  |  | 34 | my $nodes= $self->_resolve_path(undef, $path, { follow_symlinks => 1, partial => 1, %$flags }); | 
| 354 | 4 | 50 |  |  |  | 40 | croak $nodes unless ref $nodes; | 
| 355 |  |  |  |  |  |  | # replace the final entry, after applying defaults | 
| 356 | 4 | 100 |  |  |  | 26 | if (!$newent) { | 
| 357 |  |  |  |  |  |  | # unlink request.  Ignore if node didn't exist. | 
| 358 | 1 | 50 |  |  |  | 5 | return if $nodes->[-1]{invalid}; | 
| 359 |  |  |  |  |  |  |  | 
| 360 |  |  |  |  |  |  | # Can't unlink the root node | 
| 361 | 1 | 50 |  |  |  | 5 | croak "Can't unlink root node" | 
| 362 |  |  |  |  |  |  | unless @$nodes > 1; | 
| 363 |  |  |  |  |  |  |  | 
| 364 | 1 |  |  |  |  | 4 | $nodes->[-1]{invalid}= 1; | 
| 365 |  |  |  |  |  |  | # Recursively invalidate all nodes beneath this one | 
| 366 | 1 |  |  |  |  | 4 | &_invalidate_subtree for ($nodes->[-1]); | 
| 367 |  |  |  |  |  |  |  | 
| 368 |  |  |  |  |  |  | # Mark in prev node that this item is gone | 
| 369 | 1 | 50 |  |  |  | 37 | my $key= $self->case_insensitive? uc $nodes->[-1]{dirent}->name : $nodes->[-1]{dirent}->name; | 
| 370 | 1 |  |  |  |  | 3 | pop @$nodes; | 
| 371 | 1 |  |  |  |  | 6 | $nodes->[-1]{subtree}{$key}= 0; | 
| 372 |  |  |  |  |  |  | } else { | 
| 373 | 3 | 0 | 33 |  |  | 164 | if (ref $newent eq 'HASH' or !defined $newent->name or !defined $newent->type) { | 
|  |  |  | 33 |  |  |  |  | 
| 374 | 3 | 50 |  |  |  | 9 | my %ent_hash= %{ref $newent eq 'HASH'? $newent : $newent->as_hash}; | 
|  | 3 |  |  |  |  | 38 |  | 
| 375 | 3 | 100 |  |  |  | 150 | $ent_hash{name}= $nodes->[-1]{dirent}->name | 
| 376 |  |  |  |  |  |  | unless defined $ent_hash{name}; | 
| 377 | 3 | 50 | 33 |  |  | 32 | defined $ent_hash{name} && length $ent_hash{name} | 
| 378 |  |  |  |  |  |  | or die "No name for new dir entry"; | 
| 379 | 3 | 100 | 50 |  |  | 96 | $ent_hash{type}= $nodes->[-1]{dirent}->type || 'file' | 
| 380 |  |  |  |  |  |  | unless defined $ent_hash{type}; | 
| 381 | 3 |  |  |  |  | 23 | $newent= DataStore::CAS::FS::DirEnt->new(\%ent_hash); | 
| 382 |  |  |  |  |  |  | } | 
| 383 | 3 |  |  |  |  | 12 | $nodes->[-1]{dirent}= $newent; | 
| 384 | 3 |  |  |  |  | 27 | delete $nodes->[-1]{dir}; | 
| 385 |  |  |  |  |  |  | # Recursively invalidate all nodes beneath this one | 
| 386 | 3 |  |  |  |  | 19 | &_invalidate_subtree for ($nodes->[-1]); | 
| 387 |  |  |  |  |  |  | } | 
| 388 |  |  |  |  |  |  | # Now connect nodes with strong references, and mark as changed | 
| 389 | 4 |  |  |  |  | 23 | $self->_apply_overrides($nodes); | 
| 390 |  |  |  |  |  |  | } | 
| 391 |  |  |  |  |  |  | sub _invalidate_subtree { | 
| 392 | 23 | 100 |  | 23 |  | 108 | if ($_->{subtree}) { | 
| 393 | 10 |  | 33 |  |  | 16 | ++$_->{invalid} && &_invalidate_subtree for grep { ref $_ } values %{delete $_->{subtree}}; | 
|  | 11 |  |  |  |  | 60 |  | 
|  | 10 |  |  |  |  | 31 |  | 
| 394 |  |  |  |  |  |  | } | 
| 395 |  |  |  |  |  |  | } | 
| 396 |  |  |  |  |  |  |  | 
| 397 |  |  |  |  |  |  |  | 
| 398 |  |  |  |  |  |  | sub update_path { | 
| 399 | 2 |  |  | 2 | 1 | 419 | my ($self, $path, $changes, $flags)= @_; | 
| 400 | 2 |  | 50 |  |  | 16 | $flags ||= {}; | 
| 401 | 2 |  |  |  |  | 17 | my $nodes= $self->_resolve_path(undef, $path, { follow_symlinks => 1, partial => 1, %$flags }); | 
| 402 | 2 | 50 |  |  |  | 12 | croak $nodes unless ref $nodes; | 
| 403 |  |  |  |  |  |  |  | 
| 404 |  |  |  |  |  |  | # update the final entry, after applying defaults | 
| 405 | 2 |  |  |  |  | 8 | my $entref= \$nodes->[-1]{dirent}; | 
| 406 | 2 | 100 | 66 |  |  | 72 | my $old_dir_ref= defined $$entref->type && $$entref->type eq 'dir'? $$entref->ref : undef; | 
| 407 | 2 | 100 |  |  |  | 70 | $$entref= $$entref->clone( | 
|  |  | 0 |  |  |  |  |  | 
|  |  | 50 |  |  |  |  |  | 
| 408 |  |  |  |  |  |  | (defined $$entref->type? () : ( type => 'file' )), | 
| 409 |  |  |  |  |  |  | ref $changes eq 'HASH'? %$changes | 
| 410 |  |  |  |  |  |  | : ref $changes eq 'ARRAY'? @$changes | 
| 411 |  |  |  |  |  |  | : croak 'parameter "changes" must be a hashref or arrayref' | 
| 412 |  |  |  |  |  |  | ); | 
| 413 | 2 | 100 |  |  |  | 72 | my $new_dir_ref= $$entref->type eq 'dir'? $$entref->ref : undef; | 
| 414 |  |  |  |  |  |  |  | 
| 415 |  |  |  |  |  |  | # If we changed the type of a directory, or changed which digest_hash it | 
| 416 |  |  |  |  |  |  | # refers to, then we should clear the subtree under this node. | 
| 417 | 2 | 50 | 100 |  |  | 35 | if (($old_dir_ref || '') ne ($new_dir_ref || '') && $nodes->[-1]{subtree}) { | 
|  |  |  | 100 |  |  |  |  | 
|  |  |  | 66 |  |  |  |  | 
| 418 |  |  |  |  |  |  | # Recursively invalidate all nodes beneath this one | 
| 419 | 0 |  |  |  |  | 0 | &_invalidate_subtree for ($nodes->[-1]); | 
| 420 | 0 |  |  |  |  | 0 | delete $nodes->[-1]{dir}; | 
| 421 |  |  |  |  |  |  | } | 
| 422 |  |  |  |  |  |  |  | 
| 423 | 2 |  |  |  |  | 10 | $self->_apply_overrides($nodes); | 
| 424 |  |  |  |  |  |  | } | 
| 425 |  |  |  |  |  |  |  | 
| 426 |  |  |  |  |  |  | sub _apply_overrides { | 
| 427 | 9 |  |  | 9 |  | 21 | my ($self, $nodes)= @_; | 
| 428 |  |  |  |  |  |  | # Ensure that each node is connected to the previous via 'subtree'. | 
| 429 |  |  |  |  |  |  | # When we find the first changed node, we assume the rest are connected. | 
| 430 | 9 |  |  |  |  | 16 | my $prev; | 
| 431 | 9 |  |  |  |  | 31 | for (reverse @$nodes) { | 
| 432 | 25 | 100 |  |  |  | 80 | if ($prev) { | 
| 433 | 16 | 50 |  |  |  | 605 | my $key= $self->case_insensitive? uc($prev->{dirent}->name) : $prev->{dirent}->name; | 
| 434 | 16 |  |  |  |  | 84 | $_->{subtree}{$key}= $prev; | 
| 435 |  |  |  |  |  |  | } | 
| 436 | 25 | 100 | 66 |  |  | 119 | last if $_->{changed} && !$_->{invalid}; | 
| 437 | 21 |  |  |  |  | 47 | delete $_->{invalid}; | 
| 438 | 21 |  |  |  |  | 54 | $_->{changed}= 1; | 
| 439 | 21 |  |  |  |  | 53 | $prev= $_; | 
| 440 |  |  |  |  |  |  | } | 
| 441 |  |  |  |  |  |  | # Finally, make sure the root override is set | 
| 442 | 9 |  |  |  |  | 28 | $self->{_nodes}= $nodes->[0]; | 
| 443 | 9 |  |  |  |  | 49 | 1; | 
| 444 |  |  |  |  |  |  | } | 
| 445 |  |  |  |  |  |  |  | 
| 446 |  |  |  |  |  |  |  | 
| 447 |  |  |  |  |  |  | sub mkdir { | 
| 448 | 4 |  |  | 4 | 1 | 723 | my ($self, $path)= @_; | 
| 449 | 4 |  |  |  |  | 24 | my $nodes= $self->_resolve_path(undef, $path, { follow_symlinks => 1, mkdir => 1 }); | 
| 450 | 4 | 50 |  |  |  | 21 | croak $nodes unless ref $nodes; | 
| 451 | 4 | 100 |  |  |  | 151 | unless (defined $nodes->[-1]{dirent}->type) { | 
| 452 | 3 |  |  |  |  | 51 | $nodes->[-1]{dirent}= $nodes->[-1]{dirent}->clone(type => 'dir'); | 
| 453 | 3 |  |  |  |  | 15 | $self->_apply_overrides($nodes); | 
| 454 |  |  |  |  |  |  | } | 
| 455 | 4 |  |  |  |  | 23 | 1; | 
| 456 |  |  |  |  |  |  | } | 
| 457 |  |  |  |  |  |  |  | 
| 458 |  |  |  |  |  |  |  | 
| 459 |  |  |  |  |  |  | sub touch { | 
| 460 | 1 |  |  | 1 | 1 | 691 | my ($self, $path)= @_; | 
| 461 | 1 |  |  |  |  | 17 | $self->update_path($path, { mtime => time() }); | 
| 462 |  |  |  |  |  |  | } | 
| 463 |  |  |  |  |  |  |  | 
| 464 |  |  |  |  |  |  |  | 
| 465 |  |  |  |  |  |  | sub unlink { | 
| 466 | 1 |  |  | 1 | 1 | 3 | my ($self, $path)= @_; | 
| 467 | 1 |  |  |  |  | 7 | $self->set_path($path, undef); | 
| 468 |  |  |  |  |  |  | } | 
| 469 |  |  |  |  |  |  | *rmdir = *unlink; | 
| 470 |  |  |  |  |  |  |  | 
| 471 |  |  |  |  |  |  | # TODO: write copy and move and rename | 
| 472 |  |  |  |  |  |  |  | 
| 473 |  |  |  |  |  |  |  | 
| 474 |  |  |  |  |  |  | sub rollback { | 
| 475 | 0 |  |  | 0 | 1 | 0 | my $self= shift; | 
| 476 | 0 | 0 | 0 |  |  | 0 | if ($self->{_nodes} && $self->{_nodes}{changed}) { | 
| 477 | 0 |  |  |  |  | 0 | &invalidate_node for ($self->{_nodes}); | 
| 478 | 0 |  |  |  |  | 0 | $self->{_nodes}= undef; | 
| 479 |  |  |  |  |  |  | } | 
| 480 | 0 |  |  |  |  | 0 | 1; | 
| 481 |  |  |  |  |  |  | } | 
| 482 |  |  |  |  |  |  |  | 
| 483 |  |  |  |  |  |  |  | 
| 484 |  |  |  |  |  |  | sub commit { | 
| 485 | 2 |  |  | 2 | 1 | 7 | my $self= shift; | 
| 486 | 2 | 50 | 33 |  |  | 41 | if ($self->_nodes && $self->_nodes->{changed}) { | 
| 487 | 2 |  |  |  |  | 7 | my $root_node= $self->_nodes; | 
| 488 | 2 | 50 |  |  |  | 87 | croak "Root override must be a directory" | 
| 489 |  |  |  |  |  |  | unless $root_node->{dirent}->type eq 'dir'; | 
| 490 | 2 |  |  |  |  | 12 | my $hash= $self->_commit_recursive($root_node); | 
| 491 | 2 |  |  |  |  | 144 | $self->{root_entry}= $root_node->{dirent}->clone(ref => $hash); | 
| 492 | 2 |  |  |  |  | 10 | $self->{_nodes}= undef; | 
| 493 |  |  |  |  |  |  | } | 
| 494 | 2 |  |  |  |  | 18 | 1; | 
| 495 |  |  |  |  |  |  | } | 
| 496 |  |  |  |  |  |  |  | 
| 497 |  |  |  |  |  |  | # Takes a subtree of the datastructure generated by apply_path and encodes it | 
| 498 |  |  |  |  |  |  | # as a directory, recursively encoding any subtrees first, then returns the | 
| 499 |  |  |  |  |  |  | # hash of that subdir. | 
| 500 |  |  |  |  |  |  | sub _commit_recursive { | 
| 501 | 10 |  |  | 10 |  | 14 | my ($self, $node)= @_; | 
| 502 |  |  |  |  |  |  |  | 
| 503 | 10 |  |  |  |  | 12 | my %changes; | 
| 504 |  |  |  |  |  |  | my @entries; | 
| 505 | 10 | 50 |  |  |  | 27 | if (my $subtree= $node->{subtree}) { | 
| 506 | 10 |  |  |  |  | 13 | while (my ($k, $v)= each %{$node->{subtree}}) { | 
|  | 21 |  |  |  |  | 74 |  | 
| 507 | 11 | 50 | 33 |  |  | 101 | $changes{$k}= $v | 
|  |  |  | 66 |  |  |  |  | 
| 508 |  |  |  |  |  |  | if defined $v && ($v eq 0 || $v->{changed}); | 
| 509 |  |  |  |  |  |  | } | 
| 510 |  |  |  |  |  |  | } | 
| 511 |  |  |  |  |  |  |  | 
| 512 |  |  |  |  |  |  | # If no changes, return original directory (if it exists) | 
| 513 | 10 | 100 | 66 |  |  | 59 | return $node->{dirent}->ref | 
| 514 |  |  |  |  |  |  | if !%changes && defined $node->{dirent}->ref; | 
| 515 |  |  |  |  |  |  |  | 
| 516 |  |  |  |  |  |  | # Walk the directory entries and filter out any that have been overridden. | 
| 517 | 9 | 100 | 66 |  |  | 184 | if (defined $node->{dir} || defined $node->{dirent}->ref) { | 
| 518 | 4 | 50 | 33 |  |  | 17 | ($node->{dir} ||= $self->get_dir($node->{dirent}->ref)) | 
| 519 |  |  |  |  |  |  | or croak 'Failed to open directory "'.$node->{dirent}->name.' ('.$node->{dirent}->ref.')"'; | 
| 520 |  |  |  |  |  |  |  | 
| 521 | 4 |  |  |  |  | 7 | my ($iter, $ent); | 
| 522 | 4 |  |  |  |  | 11 | my $caseless= $self->case_insensitive; | 
| 523 | 4 |  |  |  |  | 20 | for ($iter= $node->{dir}->iterator; defined ($ent= $iter->()); ) { | 
| 524 | 12 | 50 |  |  |  | 378 | push @entries, $ent unless $changes{$caseless? uc($ent->name) : $ent->name}; | 
|  |  | 100 |  |  |  |  |  | 
| 525 |  |  |  |  |  |  | } | 
| 526 |  |  |  |  |  |  | } | 
| 527 |  |  |  |  |  |  |  | 
| 528 |  |  |  |  |  |  | # Now append the modified entries. | 
| 529 |  |  |  |  |  |  | # Skip the "0"s, which represent files to unlink. | 
| 530 | 9 |  |  |  |  | 27 | for (grep { ref $_ } values %changes) { | 
|  | 10 |  |  |  |  | 24 |  | 
| 531 |  |  |  |  |  |  | # Check if node is a dir and needs committed | 
| 532 | 10 | 50 | 66 |  |  | 243 | if ($_->{subtree} and $_->{dirent}->type eq 'dir' and $_->{changed}) { | 
|  |  |  | 66 |  |  |  |  | 
| 533 | 8 |  |  |  |  | 56 | my $hash= $self->_commit_recursive($_); | 
| 534 | 8 |  |  |  |  | 6727 | $_->{dirent}= $_->{dirent}->clone( ref => $hash ); | 
| 535 |  |  |  |  |  |  | } | 
| 536 |  |  |  |  |  |  |  | 
| 537 | 10 |  |  |  |  | 52 | push @entries, $_->{dirent}; | 
| 538 |  |  |  |  |  |  | } | 
| 539 |  |  |  |  |  |  |  | 
| 540 |  |  |  |  |  |  | # Invalidate all children of this node | 
| 541 | 9 |  |  |  |  | 31 | &_invalidate_subtree for ($node); | 
| 542 |  |  |  |  |  |  |  | 
| 543 |  |  |  |  |  |  | # Now re-encode the directory, using the same type as orig_dir | 
| 544 | 9 | 50 |  |  |  | 28 | return $self->hash_of_empty_dir | 
| 545 |  |  |  |  |  |  | unless @entries; | 
| 546 | 9 | 100 |  |  |  | 44 | my $format= $node->{dir}->format | 
| 547 |  |  |  |  |  |  | if $node->{dir}; | 
| 548 | 9 | 100 |  |  |  | 32 | $format= 'universal' unless defined $format; | 
| 549 | 9 |  |  |  |  | 66 | return DataStore::CAS::FS::DirCodec->put($self->store, $format, \@entries, {}); | 
| 550 |  |  |  |  |  |  | } | 
| 551 |  |  |  |  |  |  |  | 
| 552 |  |  |  |  |  |  | package DataStore::CAS::FS::Path; | 
| 553 | 4 |  |  | 4 |  | 76 | use strict; | 
|  | 4 |  |  |  |  | 9 |  | 
|  | 4 |  |  |  |  | 139 |  | 
| 554 | 4 |  |  | 4 |  | 57 | use warnings; | 
|  | 4 |  |  |  |  | 13 |  | 
|  | 4 |  |  |  |  | 5467 |  | 
| 555 |  |  |  |  |  |  |  | 
| 556 |  |  |  |  |  |  |  | 
| 557 |  |  |  |  |  |  | # main attributes | 
| 558 | 5 |  |  | 5 |  | 38 | sub path_names       { $_[0]{path_names} } | 
| 559 | 7 |  |  | 7 |  | 54 | sub filesystem       { $_[0]{filesystem} } | 
| 560 |  |  |  |  |  |  | #sub _node_path       { $_[0]{_node_path} } | 
| 561 |  |  |  |  |  |  |  | 
| 562 |  |  |  |  |  |  | # convenience accessors | 
| 563 | 0 |  |  | 0 |  | 0 | sub path_name_list   { @{$_[0]->path_names} } | 
|  | 0 |  |  |  |  | 0 |  | 
| 564 | 13 |  |  | 13 |  | 14 | sub path_dirent_list { map { $_->{dirent} } @{$_[0]->resolve} } | 
|  | 67 |  |  |  |  | 145 |  | 
|  | 13 |  |  |  |  | 33 |  | 
| 565 | 13 |  |  | 13 |  | 116 | sub path_dirents     { [ $_[0]->path_dirent_list ] } | 
| 566 | 5 |  |  | 5 |  | 30 | sub dirent           { $_[0]->resolve->[-1]{dirent} } | 
| 567 | 98 |  |  | 98 |  | 231 | sub type             { $_[0]->resolve->[-1]{dirent}->type } | 
| 568 | 0 |  |  | 0 |  | 0 | sub name             { $_[0]->resolve->[-1]{dirent}->name } | 
| 569 | 0 |  |  | 0 |  | 0 | sub depth            { -1 + @{$_[0]->resolve} } | 
|  | 0 |  |  |  |  | 0 |  | 
| 570 |  |  |  |  |  |  |  | 
| 571 |  |  |  |  |  |  |  | 
| 572 |  |  |  |  |  |  | sub canonical_path { | 
| 573 | 0 |  |  | 0 |  | 0 | my $self= shift; | 
| 574 | 0 |  | 0 |  |  | 0 | $self->{canonical_path} ||= do { | 
| 575 | 0 |  |  |  |  | 0 | my $name= $self->path_names; | 
| 576 | 0 | 0 |  |  |  | 0 | my $path= '/'.join('/', grep { length && $_ ne '.' } @$name); | 
|  | 0 |  |  |  |  | 0 |  | 
| 577 | 0 | 0 | 0 |  |  | 0 | $path .= '/' if $name->[-1] eq '' || $name->[-1] eq '.'; | 
| 578 | 0 |  |  |  |  | 0 | $path =~ s|//+|/|g; | 
| 579 | 0 |  |  |  |  | 0 | $path; | 
| 580 |  |  |  |  |  |  | }; | 
| 581 |  |  |  |  |  |  | } | 
| 582 |  |  |  |  |  |  |  | 
| 583 |  |  |  |  |  |  |  | 
| 584 |  |  |  |  |  |  | sub resolved_canonical_path { | 
| 585 | 92 |  |  | 92 |  | 500 | my $x= $_[0]->resolve; | 
| 586 |  |  |  |  |  |  | # ignore name of root entry | 
| 587 | 92 |  |  |  |  | 288 | return '/'.join('/', map { $_->{dirent}->name } @$x[1..$#$x]); | 
|  | 399 |  |  |  |  | 12866 |  | 
| 588 |  |  |  |  |  |  | } | 
| 589 |  |  |  |  |  |  |  | 
| 590 |  |  |  |  |  |  |  | 
| 591 |  |  |  |  |  |  | sub resolve { | 
| 592 |  |  |  |  |  |  | # See if we can re-use the previous result... | 
| 593 | 214 |  |  | 214 |  | 490 | my $nodes= $_[0]{_node_path}; | 
| 594 | 214 | 100 | 100 |  |  | 1449 | return $nodes if $nodes && ref $nodes->[-1] && !$nodes->[-1]{invalid}; | 
|  |  |  | 66 |  |  |  |  | 
| 595 |  |  |  |  |  |  | # Only re-resolve the nodes which have been invalidated. | 
| 596 |  |  |  |  |  |  | # This is part of an optimization to create half-resolved path objects. | 
| 597 | 103 |  |  |  |  | 169 | my ($self, $flags)= @_; | 
| 598 | 103 |  |  |  |  | 141 | my (@valid_nodes, $sub_path); | 
| 599 | 103 | 100 |  |  |  | 248 | if ($nodes) { | 
| 600 | 2 |  |  |  |  | 8 | for (@$nodes) { | 
| 601 | 4 | 100 | 66 |  |  | 35 | last if !ref $_ || $_->{invalid}; | 
| 602 | 2 |  |  |  |  | 9 | push @valid_nodes, $_; | 
| 603 |  |  |  |  |  |  | } | 
| 604 | 2 | 50 |  |  |  | 8 | $sub_path= [ map { ref $_? $_->{dirent}->name : $_ } @{$nodes}[ scalar(@valid_nodes) .. $#$nodes ] ]; | 
|  | 2 |  |  |  |  | 16 |  | 
|  | 2 |  |  |  |  | 9 |  | 
| 605 |  |  |  |  |  |  | } else { | 
| 606 | 101 |  |  |  |  | 176 | $sub_path= $self->{path_names}; | 
| 607 |  |  |  |  |  |  | } | 
| 608 | 103 | 100 |  |  |  | 346 | $flags= { follow_symlinks => 1, $flags? %$flags : () }; | 
| 609 | 103 |  |  |  |  | 330 | $nodes= $self->{filesystem}->_resolve_path(\@valid_nodes, $sub_path, $flags); | 
| 610 | 103 | 100 |  |  |  | 306 | if (ref $nodes) { | 
| 611 | 100 |  |  |  |  | 3675 | return ($self->{_node_path}= $nodes); | 
| 612 |  |  |  |  |  |  | } else { | 
| 613 |  |  |  |  |  |  | # else, got an error... | 
| 614 | 3 | 50 |  |  |  | 20 | ${$flags->{error_out}}= $nodes | 
|  | 0 |  |  |  |  | 0 |  | 
| 615 |  |  |  |  |  |  | if ref $flags->{error_out}; | 
| 616 | 3 | 50 |  |  |  | 15 | Carp::croak $nodes unless $flags->{no_die}; | 
| 617 | 3 |  |  |  |  | 38 | return undef; | 
| 618 |  |  |  |  |  |  | } | 
| 619 |  |  |  |  |  |  | } | 
| 620 |  |  |  |  |  |  |  | 
| 621 |  |  |  |  |  |  |  | 
| 622 |  |  |  |  |  |  | sub path { | 
| 623 | 6 |  |  | 6 |  | 13 | my $self= shift; | 
| 624 | 6 |  |  |  |  | 19 | my @sub_names= map { File::Spec->splitdir($_) } @_; | 
|  | 11 |  |  |  |  | 259 |  | 
| 625 | 6 |  |  |  |  | 84 | bless { | 
| 626 |  |  |  |  |  |  | filesystem => $self->{filesystem}, | 
| 627 | 2 |  |  |  |  | 51 | path_names => [ @{$self->{path_names}}, @sub_names ], | 
| 628 | 6 | 100 |  |  |  | 27 | $self->{_node_path}? (_node_path => [ @{$self->{_node_path}}, @sub_names ]) : (), | 
| 629 |  |  |  |  |  |  | }, ref $self; | 
| 630 |  |  |  |  |  |  | } | 
| 631 |  |  |  |  |  |  |  | 
| 632 |  |  |  |  |  |  | sub path_if_exists { | 
| 633 | 3 |  |  | 3 |  | 52 | my $self= shift; | 
| 634 | 3 |  |  |  |  | 18 | my $path= $self->path(@_); | 
| 635 | 3 | 100 |  |  |  | 22 | $path->resolve({no_die => 1})? $path : undef; | 
| 636 |  |  |  |  |  |  | } | 
| 637 |  |  |  |  |  |  |  | 
| 638 |  |  |  |  |  |  |  | 
| 639 |  |  |  |  |  |  | sub mkdir { | 
| 640 | 1 |  |  | 1 |  | 2 | my $self= shift; | 
| 641 | 1 |  |  |  |  | 6 | $self->{filesystem}->mkdir($self->path_names, @_); | 
| 642 | 1 |  |  |  |  | 4 | $self; | 
| 643 |  |  |  |  |  |  | } | 
| 644 |  |  |  |  |  |  |  | 
| 645 |  |  |  |  |  |  |  | 
| 646 |  |  |  |  |  |  | sub file { | 
| 647 | 2 |  | 33 | 2 |  | 15 | $_[0]{file} ||= do { | 
| 648 | 2 |  |  |  |  | 11 | my $ent= $_[0]->dirent; | 
| 649 | 2 | 50 |  |  |  | 70 | $ent->type eq 'file' or Carp::croak "Path is not a file"; | 
| 650 | 2 | 50 |  |  |  | 66 | defined (my $hash= $ent->ref) or Carp::croak "File was not stored in CAS"; | 
| 651 | 2 |  |  |  |  | 14 | $_[0]->filesystem->get($hash); | 
| 652 |  |  |  |  |  |  | }; | 
| 653 |  |  |  |  |  |  | } | 
| 654 |  |  |  |  |  |  |  | 
| 655 |  |  |  |  |  |  | sub open { | 
| 656 | 2 |  |  | 2 |  | 27 | $_[0]->file->open | 
| 657 |  |  |  |  |  |  | } | 
| 658 |  |  |  |  |  |  |  | 
| 659 |  |  |  |  |  |  |  | 
| 660 |  |  |  |  |  |  | sub dir { | 
| 661 | 0 |  | 0 | 0 |  | 0 | $_[0]{dir} ||= do { | 
| 662 | 0 |  |  |  |  | 0 | my $ent= $_[0]->dirent; | 
| 663 | 0 | 0 |  |  |  | 0 | $ent->type eq 'dir' or Carp::croak "Path is not a directory"; | 
| 664 | 0 | 0 |  |  |  | 0 | defined (my $hash= $ent->ref) or Carp::croak "Directory was not stored in CAS"; | 
| 665 | 0 |  |  |  |  | 0 | $_[0]->filesystem->get_dir($hash); | 
| 666 |  |  |  |  |  |  | } | 
| 667 |  |  |  |  |  |  | } | 
| 668 |  |  |  |  |  |  |  | 
| 669 |  |  |  |  |  |  |  | 
| 670 |  |  |  |  |  |  | sub readdir { | 
| 671 | 2 |  |  | 2 |  | 22 | $_[0]{filesystem}->readdir($_[0]->path_names) | 
| 672 |  |  |  |  |  |  | } | 
| 673 |  |  |  |  |  |  |  | 
| 674 |  |  |  |  |  |  |  | 
| 675 |  |  |  |  |  |  | sub tree_iterator { | 
| 676 | 1 |  |  | 1 |  | 4 | my $self= shift; | 
| 677 | 1 | 50 | 33 |  |  | 8 | my %p= (@_ == 1 && ref $_[0] eq 'HASH')? %{$_[0]} : @_; | 
|  | 0 |  |  |  |  | 0 |  | 
| 678 | 1 |  |  |  |  | 14 | $self->filesystem->tree_iterator(%p, path => $self->path_names); | 
| 679 |  |  |  |  |  |  | } | 
| 680 |  |  |  |  |  |  |  | 
| 681 |  |  |  |  |  |  | package DataStore::CAS::FS::TreeIterator; | 
| 682 | 4 |  |  | 4 |  | 30 | use strict; | 
|  | 4 |  |  |  |  | 8 |  | 
|  | 4 |  |  |  |  | 131 |  | 
| 683 | 4 |  |  | 4 |  | 22 | use warnings; | 
|  | 4 |  |  |  |  | 8 |  | 
|  | 4 |  |  |  |  | 98 |  | 
| 684 | 4 |  |  | 4 |  | 24 | use Carp; | 
|  | 4 |  |  |  |  | 6 |  | 
|  | 4 |  |  |  |  | 3060 |  | 
| 685 |  |  |  |  |  |  |  | 
| 686 |  |  |  |  |  |  |  | 
| 687 | 0 |  |  | 0 |  | 0 | sub _fields { $_[0]->($_[0]) } | 
| 688 |  |  |  |  |  |  |  | 
| 689 |  |  |  |  |  |  | sub new { | 
| 690 | 3 |  |  | 3 |  | 8 | my $class= shift; | 
| 691 | 3 | 50 | 33 |  |  | 56 | my $self= bless { (@_ == 1 && ref $_[0] eq 'HASH')? %{$_[0]} : @_ }, $class; | 
|  | 0 |  |  |  |  | 0 |  | 
| 692 |  |  |  |  |  |  | defined $self->{$_} || croak "'$_' is required" | 
| 693 | 3 |  | 33 |  |  | 37 | for qw( path fs ); | 
| 694 | 3 |  |  |  |  | 12 | $self->{path_nodes}= \my @nodes; | 
| 695 | 3 |  |  |  |  | 9 | $self->{dirstack}= \my @dirstack; | 
| 696 | 3 |  |  |  |  | 10 | $self->{names}= \my @names; | 
| 697 | 3 |  |  |  |  | 10 | $self->{filterref}= \my $filter; | 
| 698 | 3 |  |  |  |  | 9 | $filter= delete $self->{filter}; | 
| 699 | 3 |  |  |  |  | 8 | my $fs= $self->{fs}; | 
| 700 | 3 |  |  |  |  | 15 | $self->_init; | 
| 701 |  |  |  |  |  |  | return bless sub { | 
| 702 | 101 | 100 | 66 | 101 |  | 649 | return $self if @_ && ref($_[0]) eq $class; | 
| 703 | 97 | 50 |  |  |  | 315 | return undef unless @dirstack; | 
| 704 |  |  |  |  |  |  | # back out of a directory hierarchy that we have finished | 
| 705 | 97 |  |  |  |  | 195 | while (!@{$dirstack[-1]}) { | 
|  | 147 |  |  |  |  | 452 |  | 
| 706 | 55 |  |  |  |  | 105 | pop @dirstack; # back out of directory | 
| 707 | 55 |  |  |  |  | 84 | pop @nodes; | 
| 708 | 55 |  |  |  |  | 106 | pop @names; | 
| 709 | 55 | 100 |  |  |  | 164 | return undef unless @dirstack; | 
| 710 |  |  |  |  |  |  | } | 
| 711 |  |  |  |  |  |  | # Iterate path leaf, by removing last leaf, and resolving using the | 
| 712 |  |  |  |  |  |  | #  next name. | 
| 713 | 92 |  |  |  |  | 128 | pop @nodes; | 
| 714 | 92 |  |  |  |  | 173 | $names[-1]= shift @{$dirstack[-1]}; | 
|  | 92 |  |  |  |  | 231 |  | 
| 715 | 92 |  |  |  |  | 452 | $fs->_resolve_path(\@nodes, [ $names[-1] ]); | 
| 716 | 92 |  |  |  |  | 635 | my $p= bless { | 
| 717 |  |  |  |  |  |  | path_names => \@names, | 
| 718 |  |  |  |  |  |  | path_dirents  => \@nodes, | 
| 719 |  |  |  |  |  |  | filesystem => $fs, | 
| 720 |  |  |  |  |  |  | }, 'DataStore::CAS::FS::Path'; | 
| 721 |  |  |  |  |  |  | # If a dir, push it onto the stack | 
| 722 | 92 | 100 |  |  |  | 225 | if ($p->type eq 'dir') { | 
| 723 | 42 |  |  |  |  | 84 | push @dirstack, [ map { $_->name } @{ $fs->_get_dir_entries($nodes[-1]) } ]; | 
|  | 92 |  |  |  |  | 2960 |  | 
|  | 42 |  |  |  |  | 163 |  | 
| 724 | 42 |  |  |  |  | 140 | push @nodes, undef; | 
| 725 | 42 |  |  |  |  | 75 | push @names, undef; | 
| 726 |  |  |  |  |  |  | } | 
| 727 | 92 |  |  |  |  | 384 | return $p; | 
| 728 | 3 |  |  |  |  | 40 | }, $class; | 
| 729 |  |  |  |  |  |  | } | 
| 730 |  |  |  |  |  |  |  | 
| 731 |  |  |  |  |  |  | sub _init { | 
| 732 | 5 |  |  | 5 |  | 13 | my $self= shift; | 
| 733 |  |  |  |  |  |  | # _resolve_path returns a string on failure | 
| 734 | 5 |  |  |  |  | 9 | my $x; | 
| 735 | 5 | 50 |  |  |  | 67 | ref( $x= $self->{fs}->_resolve_path(undef, $self->{path}) ) | 
| 736 |  |  |  |  |  |  | or croak $x; | 
| 737 |  |  |  |  |  |  | # maintain an array of resolved path nodes, and an array of | 
| 738 |  |  |  |  |  |  | #  arrays of names-to-iterate for each directory | 
| 739 | 5 |  |  |  |  | 13 | @{$self->{path_nodes}}= @$x; | 
|  | 5 |  |  |  |  | 18 |  | 
| 740 | 5 |  |  |  |  | 46 | @{$self->{names}}= map { $_->{dirent}->name } @$x; | 
|  | 5 |  |  |  |  | 30 |  | 
|  | 13 |  |  |  |  | 390 |  | 
| 741 | 5 |  |  |  |  | 26 | @{$self->{dirstack}}= ([]) x @$x; | 
|  | 5 |  |  |  |  | 15 |  | 
| 742 | 5 |  |  |  |  | 46 | push @{$self->{dirstack}[-1]}, $self->{names}[-1]; | 
|  | 5 |  |  |  |  | 30 |  | 
| 743 |  |  |  |  |  |  | } | 
| 744 |  |  |  |  |  |  |  | 
| 745 |  |  |  |  |  |  | sub reset { | 
| 746 | 2 |  |  | 2 |  | 2385 | $_[0]->($_[0])->_init; | 
| 747 | 2 |  |  |  |  | 8 | 1; | 
| 748 |  |  |  |  |  |  | } | 
| 749 |  |  |  |  |  |  |  | 
| 750 |  |  |  |  |  |  | sub skip_dir { | 
| 751 | 2 |  |  | 2 |  | 16 | my $self= $_[0]->($_[0]); | 
| 752 | 2 |  |  |  |  | 7 | @{$self->{dirstack}[-1]}= () | 
|  | 2 |  |  |  |  | 10 |  | 
| 753 | 2 | 50 |  |  |  | 4 | if @{$self->{dirstack}}; | 
| 754 | 2 |  |  |  |  | 14 | 1; | 
| 755 |  |  |  |  |  |  | } | 
| 756 |  |  |  |  |  |  |  | 
| 757 |  |  |  |  |  |  | package DataStore::CAS::FS::DirCache; | 
| 758 | 4 |  |  | 4 |  | 26 | use strict; | 
|  | 4 |  |  |  |  | 9 |  | 
|  | 4 |  |  |  |  | 115 |  | 
| 759 | 4 |  |  | 4 |  | 32 | use warnings; | 
|  | 4 |  |  |  |  | 8 |  | 
|  | 4 |  |  |  |  | 1528 |  | 
| 760 |  |  |  |  |  |  |  | 
| 761 |  |  |  |  |  |  |  | 
| 762 |  |  |  |  |  |  | sub size { | 
| 763 | 0 | 0 |  | 0 |  | 0 | if (@_ > 1) { | 
| 764 | 0 |  |  |  |  | 0 | my ($self, $new_size)= @_; | 
| 765 | 0 |  |  |  |  | 0 | $self->{size}= $new_size; | 
| 766 | 0 |  |  |  |  | 0 | $self->{_recent}= []; | 
| 767 | 0 |  |  |  |  | 0 | $self->{_recent_idx}= 0; | 
| 768 |  |  |  |  |  |  | } | 
| 769 | 0 |  |  |  |  | 0 | $_[0]{size}; | 
| 770 |  |  |  |  |  |  | } | 
| 771 |  |  |  |  |  |  |  | 
| 772 |  |  |  |  |  |  | sub new { | 
| 773 | 7 |  |  | 7 |  | 18 | my $class= shift; | 
| 774 | 7 | 50 |  |  |  | 35 | my %p= ref($_[0])? %{$_[0]} : @_; | 
|  | 0 |  |  |  |  | 0 |  | 
| 775 | 7 |  | 50 |  |  | 50 | $p{size} ||= 32; | 
| 776 | 7 |  | 50 |  |  | 47 | $p{_by_hash} ||= {}; | 
| 777 | 7 |  | 50 |  |  | 46 | $p{_recent} ||= []; | 
| 778 | 7 |  | 50 |  |  | 46 | $p{_recent_idx} ||= 0; | 
| 779 | 7 |  |  |  |  | 187 | bless \%p, $class; | 
| 780 |  |  |  |  |  |  | } | 
| 781 |  |  |  |  |  |  |  | 
| 782 |  |  |  |  |  |  | sub clear { | 
| 783 | 0 |  |  | 0 |  | 0 | $_= undef for @{$_[0]{_recent}}; | 
|  | 0 |  |  |  |  | 0 |  | 
| 784 | 0 |  |  |  |  | 0 | $_[0]{_by_hash}= {}; | 
| 785 |  |  |  |  |  |  | } | 
| 786 |  |  |  |  |  |  |  | 
| 787 |  |  |  |  |  |  | sub get { | 
| 788 | 124 |  |  | 124 |  | 613 | return $_[0]{_by_hash}{$_[1]}; | 
| 789 |  |  |  |  |  |  | } | 
| 790 |  |  |  |  |  |  |  | 
| 791 |  |  |  |  |  |  | sub put { | 
| 792 | 48 |  |  | 48 |  | 89 | my ($self, $dir)= @_; | 
| 793 |  |  |  |  |  |  | # Hold onto a strong reference for a while. | 
| 794 | 48 |  |  |  |  | 159 | $self->{_recent}[ $self->{_recent_idx}++ ]= $dir; | 
| 795 | 48 | 50 |  |  |  | 87 | $self->{_recent_idx}= 0 if $self->{_recent_idx} > @{$self->{_recent}}; | 
|  | 48 |  |  |  |  | 234 |  | 
| 796 |  |  |  |  |  |  | # Index it using a weak reference. | 
| 797 | 48 |  |  |  |  | 203 | Scalar::Util::weaken( $self->{_by_hash}{$dir->hash}= $dir ); | 
| 798 |  |  |  |  |  |  | # Now, a nifty hack: we attach an object to watch for the destriction of the | 
| 799 |  |  |  |  |  |  | # directory.  Lazy references will get rid of the dir object, but this cleans | 
| 800 |  |  |  |  |  |  | # up our _by_hash index. | 
| 801 | 48 |  |  |  |  | 560 | $dir->{'#DataStore::CAS::FS::DirCacheCleanup'}= | 
| 802 |  |  |  |  |  |  | bless [ $self->{_by_hash}, $dir->hash ], 'DataStore::CAS::FS::DirCacheCleanup'; | 
| 803 |  |  |  |  |  |  | } | 
| 804 |  |  |  |  |  |  |  | 
| 805 |  |  |  |  |  |  | package DataStore::CAS::FS::DirCacheCleanup; | 
| 806 | 4 |  |  | 4 |  | 23 | use strict; | 
|  | 4 |  |  |  |  | 6 |  | 
|  | 4 |  |  |  |  | 140 |  | 
| 807 | 4 |  |  | 4 |  | 23 | use warnings; | 
|  | 4 |  |  |  |  | 8 |  | 
|  | 4 |  |  |  |  | 333 |  | 
| 808 |  |  |  |  |  |  |  | 
| 809 | 48 |  |  | 48 |  | 7602 | sub DESTROY { delete $_[0][0]{$_[0][1]}; } | 
| 810 |  |  |  |  |  |  |  | 
| 811 |  |  |  |  |  |  | 1; | 
| 812 |  |  |  |  |  |  |  | 
| 813 |  |  |  |  |  |  | __END__ |