=pod

=head1 NAME

Dir::TempChdir - Temporarily change the current working directory, return safely.

=head1 SYNOPSIS

  use Cwd 'getcwd';
  use Dir::TempChdir;

  chdir '/tmp';
  mkdir 'foo';
  mkdir 'foo/bar';
  print getcwd(), "\n"; # prints /tmp

  {
    my $tcd = Dir::TempChdir->new('foo'); # chdir to foo
    print "$tcd\n"; # prints /tmp/foo

    $tcd->pushd('bar'); # chdir to bar
    print "$tcd\n"; # prints /tmp/foo/bar

    rename '/tmp/foo', '/tmp/foo.old'; # Mallory tries to fool us

    $tcd->popd(); # chdir back to the directory formerly known as foo
    print "$tcd\n"; # prints /tmp/foo.old (Phew! We're still safe!)
  }

  # $tcd went out of scope: we're back in /tmp/.
  print getcwd(), "\n"; # prints /tmp

=head1 DESCRIPTION

Dir::TempChdir allows to change the current working directory temporarily,
returning to the previous directory automatically when the instance goes out
of scope.

It uses a directory handle instead of a path to remember the previous
directory, and it maintains a directory stack so that you can change
directories back and forth as much as you like.

Using handles ensures you can return to the actual directory you came from (in
terms of the inode) even if it was renamed, removed or replaced by a symlink
in the meantime (both File::chdir and File::pushd are susceptible to such
attacks). However, not all systems support this approach. See L</"USING THE
MODULE UNSAFELY"> if you're up for a thrill.

=head1 METHODS

=over 4

=item C<new([$dir])>

Creates a new Dir::TempChdir instance.

If called with a path or handle C<$dir>, it calls C<pushd($dir)> to change to
the corresponding directory.

Returns undef on error.

The instance stringifies to the current working directory (or undef if that
doesn't exist anymore).

=item C<pushd($dir)>

Changes to the directory given by the path or handle C<$dir> and pushes a
handle referring to the origin directory onto the directory stack.

Returns undef on error.

=item C<popd()>

Removes the top entry from the directory stack, changes to the corresponding
directory and closes it's handle.

Returns undef on error or when the directory stack is empty. You can
distinguish these two situations by checking if C<$!> is set.

=item C<backout()>

Changes to the directory at the bottom end of the directory stack and clears
the stack (thereby closing all open directory handles).

This is done automatically when the instance created by C<new()> goes out of
scope or is undef'd.

Returns undef on error or when the directory stack is empty. You can
distinguish these two situations by checking if C<$!> is set.

=item C<stack_size()>

Returns the number of elements on the directory stack.

=item C<errno()>

Returns the value of C<$!> from the last C<pushd()>, C<popd()> or
C<backout()>.

=item C<error()>

Returns a descriptive text of the last error from C<pushd()>, C<popd()> or
C<backout()>.

=back

=head1 ERROR HANDLING

If C<pushd()>, C<popd()> or C<backout()> fail they return undef. You can get
the corresponding errno from C<$!> as usual or from C<< $tcd->errno() >>. If
it is set, a more descriptive error message is available from C<<
$tcd->error() >>.

If an error occurs when the instance goes out of scope or during
initialization with C<new($dir)> you can get the errno via C<<
Dir::TempChdir->errno() >> and the descriptive error message via C<<
Dir::TempChdir->error() >>.

The descriptive error messages contain the original directory path (if
available) which will be wrong if a directory was renamed, removed or replaced
by a symlink during the lifetime of the TempChdir instance.

=head1 USING THE MODULE UNSAFELY

If your system lacks support for fchdir(), C<use Dir::TempChdir;> will die on
purpose because the core feature of this module is the use of directory
handles.

If you still want to use the module on such a system, you can do so at your
own peril with C<use Dir::TempChdir '-IGNORE_UNSAFE_CHDIR_SECURITY_RISK';>.
This is in all caps and inconveniently long to make it unmistakably clear to
everybody that you wish to invite a cornucopia of troubles.

In this case, C<popd()> calls chdir() with the absolute path of the previous
directory (which C<pushd()> determined by calling getcwd()). Good luck.

=head1 NOTES

Don't use Dir::TempChdir with threads (processes are okay, though).

As a non-root user you might not be able to change back to a previous
directory if its permissions have changed.

This module lives in the Dir namespace because it deals with directories and
nothing but directories.

=head1 SEE ALSO

=over

=item *
L<File::chdir>

=item *
L<File::pushd>

=back

=head1 AUTHOR

Initially hacked together by Carsten Gaebler.

=head1 LICENSE

This library is free software. You can redistribute and/or modify it under the
terms of the Do What The Fuck You Want To Public License, Version 2, as
published by Sam Hocevar. See the COPYING file or L<http://www.wtfpl.net/> for
more details.

=cut
