package Device::Blkid::E2fsprogs;

our $VERSION = '0.22';

use 5.008000;
use strict;
use warnings;

require Exporter;

our @ISA = qw(Exporter);

our %EXPORT_TAGS = (
    'consts' => [
        qw(
          BLKID_DEV_FIND
          BLKID_DEV_CREATE
          BLKID_DEV_VERIFY
          BLKID_DEV_NORMAL
          )
    ],
    'funcs' => [
        qw(
          put_cache
          get_cache
          gc_cache
          dev_devname
          dev_iterate_begin
          dev_set_search
          dev_next
          dev_iterate_end
          devno_to_devname
          probe_all
          probe_all_new
          get_dev
          get_dev_size
          known_fstype
          verify
          get_tag_value
          get_devname
          tag_iterate_begin
          tag_next
          tag_iterate_end
          dev_has_tag
          find_dev_with_tag
          parse_tag_string
          parse_version_string
          get_library_version
          )
    ],
);
Exporter::export_ok_tags('consts');
Exporter::export_ok_tags('funcs');

use constant BLKID_DEV_FIND   => 0x0000;
use constant BLKID_DEV_CREATE => 0x0001;
use constant BLKID_DEV_VERIFY => 0x0002;
use constant BLKID_DEV_NORMAL => ( BLKID_DEV_CREATE | BLKID_DEV_VERIFY );

require XSLoader;
XSLoader::load( 'Device::Blkid::E2fsprogs', $VERSION );

1;
__END__

=head1 NAME

Device::Blkid::E2fsprogs - Perl interface to e2fsprogs-based libblkid (v1.33 - v1.41.4)

=head1 SYNOPSIS

  use Device::Blkid::E2fsprogs qw/ :funcs /;

  # Get a cache object from libblkid, checking for exception
  my $cache_file = '/etc/blkid/blkid.tab';

  local $@;
  my $cache - eval { get_cache($cache_file) };
  if ($@) {
      # Do something, log or die
      die "Error while obtaining cache file: $@";
  }

  # Get the device associated with a given blkid LABEL
  my $type = 'LABEL';
  my $label = 'SWAP';

  # Using the cache
  my $devname = get_devname($type, $value, $cache);

  # Get a Device::Blkid::E2fsprogs::Device object
  my $device = get_dev($cache, $devname, $flags);

  # Get device iterator, checking for exceptions
  local $@;
  my $dev_iter = eval { dev_iterate_begin($cache) };
  if ($@) {
      # Handle exception
  }

  # And now iterate over list of devices
  if ( $device = dev_next($dev_iter) ) {
      do_something_with_device($device);
  }

  # To explicitly force memory deallocation on an allocated object
  undef $cache; 

=head3 Important Note

This library only exposes the older e2fsprogs versions of libblkid ( numbered 1.xx.x)
and not the newer and preferred util-linux-ng versions ( v2.15 or better ). In almost
every case you would be advised to use Bastian Friedrich's util-linux-ng based Device::Blkid
module as the newer lib interface is (mostly) backward compatible with the old one. This
module would prove useful in any situation where for any reason you are limited on your
systems to a 1.xx.x libblkid version which is a part of the e2fsprogs package. Incidentally,
libblkid version numbering is based upon the version of either util-linux-ng or e2fsprogs of
which it was a part and as such, e2fsprogs based versions of the library were all numbered
v1.xx.x whereas util-linux-ng versions are numbered as v2.15 or better which was the version
of util-linux-ng in which it was added to that package. So just to be clear, when in doubt
you are advised to grab Bastian's newer util-linux-ng based libblkid interface module unless
you have some specific reason as to why you can't, perhaps something similar to what led me
to write this version.

While the newer util-linux-ng version of libblkid is reportedly backward compatible with
the old e2fsprogs version, I have personally witnessed in my testing one instance where an
older version 1.xx call did not return a block label as it should have. In this case, the
call in question has a more modern version 2.xx.x counterpart and I am not sure if this is
a case of unannounced deprecation to removal I have stumbled upon or some other factor. As
such, if you are moving older e2fsprogs compliant libblkid client code to a system running
version 2.xx.x of the library, you would be well advised to thoroughly test it for
compatibility with the new interface.

This version has been implemented somewhat differently than Bastian's util-linux-ng build
of the library. He opted to keep much of his logic and processing in XSUB, mine is done
mostly in C; I have only used XSUB for my straight glue, everything else I kept in C. This is
not to be taken as any opinion of statement on PerlXS/XSUB, its merely a reflection of my own
background and tastes.

=head1 DESCRIPTION

This package provides a Perl interface to the e2fsprogs-based versions of libblkid (those
versions which begin with a 1). It does not support the larger and more robust API which has
been added and integrated into the libblkid library since its inclusion in the util-linux-ng
package. That said, the libblkid which now ships with util-linux-ng is reportedly backward
compatible with client code dependant upon the older, original library which would mean that
this module should work with it, albeit with the more limited selection of API calls. Please
see the preceding note for additional details.

Libblkid provides a means of identifying block devices as to their content (such as filesystems)
as well as allowing for the extraction of additional information such as filesystem labels,
volume labels, serial numbers, device numbers, unique identifiers, etc. The libblkid library
maintains a mapping of all of this composite information and maintains its association with
a given block device on the system. Libblkid is becoming more common in the configuration files
of modern linux distributions in places where former practice was to hard code in full device
names, such as fstab(5) and in lvm aware installers.

In addition to providing for low level probing of block devices for this information, the
library maintains an on disk cache file of this data. Use of the cache file is the preferred
way of accessing library mappings and data and it allows unpriviledged users read access to
this information as well. The blkid cache file is updated at boot and whenever any of the
low level probe functions in the library are called.

I have endeavored to provide a more Perlish interface to the library rather than just do
straight mappings or wrappers over the C functions. Most library calls will return an undef
on failure. Those which return data structures which are allocated in memory will throw an
exception catchable via an eval/if. Furthermore, several of the original library calls which
were passed in modifiable pointer arguments now return Perl hash representations of complex
types where this made sense. See the interface documentation below for details on each call.

Please read the README file in the package archive for instructions should you encounter any
problems while using this software package, as well as for instructions on building a debug
version and how to report any problems which you might have.

It is worth noting that between versions 1.33 and 1.41.4, the entire period which libblkid
was shipping as a part of the e2fsprogs package, the number of calls present in the API
expanded from 17 in the original release of the library back in 2003 to 25 when it was
migrated over to the util-linux-ng package in early 2009. This module supports dynamic
detection of the libblkid version on the target system from version 1.36 onward. This module
may be installed on systems running versions 1.33 to 1.35 of libblkid but this will require
some manual configuration of the package Makefile.PL. Please see the comments in that file
for additional details and instructions on doing this.

=head2 INSTALLATION NOTES

This package has made use of a customized L<Devel::CheckLib> module and Makefile.PL in an
attempt to detect the version of libblkid currently installed on the target system and to
then generate a PerlXS interface which directly targets and matches the API interface of
that libblkid version. This process is expected to work on all versions of libblkid later
than v1.35. Should you have any problems with this process, evident either in running the
Makefile.PL or in running make against the resulting Makefile, please see the Makefile.PL
for hints on troubleshooting. If you wish to report any problems with this version detection,
please include any output from their installation process as well as a copy of your
/usr/include/blkid/blkid.h file.

Please note, dynamic version detection is optional and you are free to select a manual build
target during the interactive configuration process.  Furthermore, the package Makefile.PL may
be directly edited to suit your specific needs. Please see that file for further details; I
have endeavored to keep it well commented, as well as all related changes and customizations
made to the L<Devel::CheckLib> package.

=head2 DEPENDENCIES

L<E2fsprogs v1.33-v1.41.4|http://e2fsprogs.sourceforge.net/>

While this package is compatible with any version of e2fsprogs-based libblkid, dynamic version
detection will only work on versions 1.36 and newer. It is possible to manually configure for
versions 1.33-1.35 by choosing the manual version selection option during the interactive
configuration phase of the Makefile.PL program. Users are also free to hand edit the Makefile.PL
script as well as the L<Devel::CheckLib> module to suit their particular needs; I have endeavored
to keep both files well commented and readable to this end.

=head2 EXPORT

Nothing is exported by default, but constants and package functions are available as follows:

To export libblkid defined constants, implement the following use pragma:

  use Device::Blkid::E2fsprogs qw/ :consts /;

To export this package's functions into the namespace, implement the following use pragma:

  use Device::Blkid::E2fsprogs qw/ :funcs /;

=head2 CONSTANTS

=over 4

=item  C<BLKID_DEV_CREATE>

Create and empty device structure if not found in the cache.

=item C<BLKID_DEV_VERIFY>

Make sure the device structure corresponds with reality.

=item C<BLKID_DEV_FIND>

Just look up a device entry and return undef if not found.

=item C<BLKID_DEV_NORMAL>

Get a valid device structure, either from the cache or by probing the block device.

=back

=head2 FUNCTIONS

=over 4

=item C<put_cache($cache)>

Write any changes to the blkid cache file and explicitly frees associated resources. C<put_cache($cache)>
should be called after you have been doing any work with a cache object.

C<v1.33>

=item C<get_cache($filename)>

Given a path to a cache file, return a blkid cache object reference. This reference is of type
C<Device::Blkid::E2fsprogs::Cache>. As with other allocated types, throws exception on fail state.

C<v1.33>

=item C<gc_cache($cache)>

Calling this performs a garbage cleanup on the specified cache by removing all non-existant devices.

C<v1.40>

=item C<dev_devname($device)>

Given a blkid device object, returns a string representation of the device (e.g., /dev/sda9), undef
if something went wrong. Device objects are of type C<Device::Blkid::E2fsprogs::Device>.

C<v1.33>

=item C<dev_iterate_begin($cache)>

Returns a device iterator object on the specified device cache. Device iterator onbects are of type
C<Device::Blkid::E2fsprogs::DevIter>. As in the case of other allocated types, throws exception on
fail state.

C<v1.33>

=item C<dev_set_search($dev_iter, $type, $value)>

This function places a search filter on the specified device iterator based upon the criteria passed
in on the final two arguments of the function. After this function has been called on the given iterator
with a type and value argument, the iterator will only return onjects which match the specified criteria.
Please note, the $type argument can also contain any valid blkid entity category, such as a LABEL or UUID
tag for example.

  # Set iterator to filter and match only on ext4 file systems
  dev_set_search($dev_iter, 'TYPE', 'ext4');

On success, returns a copy of the device iterator object or undef on fail.

C<v1.38>

=item C<dev_next($dev_iter, $device)>

Returns the next device object in the iteration. Check for undef as an end of list sentinal.

C<v1.33>

=item C<dev_iterate_end($dev_iter)>

Frees the allocated iterator object from memory, although this is redundant; simply undef'ing the object to
remove references to it or allowing it to go out of scope will also free the memory by design. (May be removed
in a future version).

C<v1.33>

=item C<devno_to_devname($devno)>

Given a device number, returns the associated device name (e.g., /dev/sda1) or undef if no match found.

C<v1.33>

=item C<probe_all($cache)>

Given a valid cache object, probes the underlying block devices on the system. Returns the cache object
instance on success, undef on fail.

C<v1.33>

=item C<probe_all_new($cache)>

Given a valid cache object, probes for new block devices on the system. Returns the cache object instance
on success, or undef in fail state.

C<v1.38>

=item C<get_dev($cache, $devname, $flags)>

Returns a device object based upon the input criteria. Please refer to the constants sections to see what
flags may be passed in to determine behaviour. Device objects are of type C<Device::Blkid::E2fsprogs::Device>.
Throws exception on failure to allocate the device object.

C<v1.33>

=item C<get_dev_size(int $fd)>

Given a device object passed in over a file descriptor, this function returns the size of that device.
Please note, this is a file descriptor and NOT a Perl file handle.  Please
see POSIX::open in perldoc for further details.

C<v1.33>

=item C<known_fstype($fstype)>

Determines if a file system type is known to libblkid. If the file system is known, it returns the input
argument string, otherwise undef is returned.

C<v1.34>

=item C<verify($cache, $device)>

Attempts to verify that the device object is a valid blkid device. Returns the instance of the valid device
object on success, otherwise undef is returned to indicate failure.

C<v1.36>

=item C<get_tag_value($cache, $tagname, $devname)>

Given a valid $cache object, $tagname and $devname, this function returns the value to which the tag refers.

  # Given the following and assuming them valid on this system
  my $tagname = 'LABEL';
  my $devname = '/dev/sda4';

  # The following say prints '/home' in this example
  my $tag_value = get_tag_value($cache, $tagname, $devname);
  say $tag_value;

C<v1.33>

=item C<get_devname($cache, $token, $value)>

Similar to the last call, given a valid $cache object and token and value parameters, will return the
devname of the block device.

C<v1.33>

=item C<tag_iterate_begin($device)>

Returns a tag iterater object on a valid device type, of type C<Device::Blkid::E2fsprogs::TagIter>. Throws
exception on fail state.

C<v1.33>

=item C<tag_next($tag_iter)>

Returns a hash reference containing the next available tag pairing from the list, or undef is returned
on failure.

  { type => "UUID", value => '83f076b3-7abd-4c32-83df-026e57373900' }

C<v1.33>

=item C<tag_iterate_end($tag_iter)>

Frees the memory allocated for the tag iterator object. This is redundant as the memory can be freed by
removing references to the object, undef'ing it or allowing it to leave scope.

C<v1.33>

=item C<dev_has_tag($device, $type, $value)>

Determines if the given device contains the specified tag. If it does, the device instance is returned,
otherwise undef.

C<v1.38>

=item C<find_dev_with_tag(cache, type, value)>

Given a tag type and value, crawls the blkid cache for a match and returns an instance of the device if
found, undef on failure.

=item C<parse_tag_string()>

Given an tag pair input value in C<type=value> format, returns a hash reference to a hash containing the
two constituent elements as key values. Returns undef in the event of a failure.

  { type => 'LABEL', value => '/boot' }

C<v1.33>

=item C<parse_version_string($ver_string)>

Given a standard dotted-decimal style version string, returns a raw integer-like representation of the
string, sans decimals.

C<v1.36>

=item C<get_library_version()>

Returns a hash reference containing the libblkid library version and release date as well as a raw integer
representation of the standard dotted-decimal formatted version string (see L</parse_version_string> above).
Returns undef on failure.

  { version => '1.41.4', date => '27-Jan-2009', raw => '1414' }  

C<v1.36>

=back

=head1 SEE ALSO

L<E2fsprogs project home page|http://e2fsprogs.sourceforge.net/>

L<blkid(8)>

L<PerlXS|http://perldoc.perl.org/perlxs.html>

L<Device::Blkid> - You should probably use this unless otherwise constrained.

L<Devel::CheckLib>

This package project is also hosted on Github at
git://github.com/raymroz/Device--Blkid--E2fsprogs.git

=head1 AUTHOR

Raymond Mroz, E<lt>mroz@cpan.orgE<gt>

=head1 COPYRIGHT AND LICENSE

Copyright (C) 2010 by Raymond Mroz

This library is free software; you can redistribute it and/or modify it under the same
terms as Perl itself, either Perl version 5.10.1 or, at your option, any later version
of Perl 5 you may have available.

=head1 TODO

Cleanup preprocessor code when everything checks out stable.

Consider eliminating redundant calls and implementing an even more 'Perlish' design.

Test scripts, test scripts, test scripts.

=head1 CREDITS

I would like to thank Bastian Friedrich for his L<Device::Blkid>. Given the scant supply
of documentation available on libblkid, especially the older, e2fsprogs-based versions,
his POD proved quite helpful as a source of documentation on the library and saved me a
load of time.  Thanks!

Thanks to David Cantrell, David Golden and Yasuhiro Matsumoto for L<Devel::CheckLib>. I
hacked it up a little bit to manage my dynamic version checks and build, hope you don't mind.

I would also like to thank Larry McInnis for the loan of some hardware on which to develop
this module. Most everything I have had been tied up and developing on the latest and greatest
version of Debian didn't make much sense.

=head1 BUGS

What's a bug? :)

No known bugs at this time. That said, this module is largely written in C and does contain
a number of memory allocations. While these allocations are done inside of libblkid itself,
I do make every attempt to free the memory explicitly when I am done with it. That said, leaks
are possible. Please report any issues as is detailed above.

=head1 DIRECTION

This is an early release of this module. It and its interface are subject to change at any
time. Please refer to all package documentation before reporting any problems.

=Cut
