The Perl Toolchain Summit needs more sponsors. If your company depends on Perl, please support this very important event.
package Devel::Platform::Info::Linux;

use strict;
use warnings;

use vars qw($VERSION);
$VERSION = '0.16';

=encoding utf8

=head1 NAME

Devel::Platform::Info::Linux - Retrieve Linux platform metadata

=cut

#----------------------------------------------------------------------------

my %commands = (
    '_issue1'   => 'cat /etc/issue',
    '_issue2'   => 'cat /etc/.issue',
    '_uname'    => 'uname -a',
    '_lsb'      => 'lsb_release -a',
    'kname'     => 'uname -s',
    'kvers'     => 'uname -r',
    'osname'    => 'uname -o',
    'archname'  => 'uname -m',
);

my %default = ();

# http://en.wikipedia.org/wiki/Arch_Linux#Versions
my %archlinux = (
    '0.1'           => 'Homer',
    '0.2'           => 'Vega',
    '0.3'           => 'Firefly',
    '0.4'           => 'Dragon',
    '0.5'           => 'Nova',
    '0.6'           => 'Widget',
    '0.7'           => 'Wombat',
    '0.8'           => 'Voodoo',
    '2007.05'       => 'Duke',
    '2007.08'       => "Don't Panic",
    '2008.06'       => 'Overlord',
    '2009.02'       => '2009.02',
    '2009.08'       => '2009.08',
    '2010.05'       => '2010.05',
    '2011.08.19'    => '2011.08.19',
);

# previously CrunchBang Linux hasn't normally used codenames, however from
# version 10 they are using Debian releases as a base, and thus are using 
# Muppet Show characters to compliment the Toy Story characters as used by
# Debian.

my %crunch = (
    '8.04.02'       => 'Back in Black',
    '10'            => 'Statler',
    '11'            => 'Waldorf',
);

# http://en.wikipedia.org/wiki/Debian#Release_history
my %debian = (
    '1.1'       => 'buzz',
    '1.2'       => 'rex',
    '1.3'       => 'bo',
    '2.0'       => 'hamm',
    '2.1'       => 'slink',
    '2.2'       => 'potato',
    '3.0'       => 'woody',
    '3.1'       => 'sarge',
    '4.0'       => 'etch',
    '4.1'       => 'etch',
    '4.2'       => 'etch',
    '4.3'       => 'etch',
    '4.4'       => 'etch',
    '4.5'       => 'etch',
    '4.6'       => 'etch',
    '4.7'       => 'etch',
    '4.8'       => 'etch',
    '4.9'       => 'etch',
    '5.0'       => 'lenny',
    '5.1'       => 'lenny',
    '5.2'       => 'lenny',
    '5.3'       => 'lenny',
    '5.4'       => 'lenny',
    '5.5'       => 'lenny',
    '5.6'       => 'lenny',
    '5.7'       => 'lenny',
    '5.8'       => 'lenny',
    '5.9'       => 'lenny',
    '5.10'      => 'lenny',
    '6.0'       => 'squeeze',
    '6.1'       => 'squeeze',
    '6.2'       => 'squeeze',
    '6.3'       => 'squeeze',
    '6.4'       => 'squeeze',
    '6.5'       => 'squeeze',
    '6.6'       => 'squeeze',
    '6.7'       => 'squeeze',
    '6.8'       => 'squeeze',
    '6.9'       => 'squeeze',
    '6.10'      => 'squeeze',
    '7.0'       => 'wheezy',
    '7.1'       => 'wheezy',
    '7.2'       => 'wheezy',
    '7.3'       => 'wheezy',
    '7.4'       => 'wheezy',
    '7.5'       => 'wheezy',
    '7.6'       => 'wheezy',
    '7.7'       => 'wheezy',
    '7.8'       => 'wheezy',
    '7.9'       => 'wheezy',
    '7.10'      => 'wheezy',
    '8.0'       => 'jessie',
    '8.1'       => 'jessie',
    '8.2'       => 'jessie',
    '8.3'       => 'jessie',
    '8.4'       => 'jessie',
    '9.0'       => 'stretch',
    '10.0'      => 'buster',
);

# Fedora naming scheme is no longer used:
# https://lists.fedoraproject.org/pipermail/advisory-board/2013-October/012209.html
# "The Fedora Board is terminating Release Names as they are currently
# fashioned following Fedora 20."

# http://en.wikipedia.org/wiki/Fedora_%28operating_system%29#Version_history
my %fedora = (
    '1'         => 'Yarrow',
    '2'         => 'Tettnang',
    '3'         => 'Heidelberg',
    '4'         => 'Stentz',
    '5'         => 'Bordeaux',
    '6'         => 'Zod',
    '7'         => 'Moonshine',
    '8'         => 'Werewolf',
    '9'         => 'Sulphur',
    '10'        => 'Cambridge',
    '11'        => 'Leonidas',
    '12'        => 'Constantine',
    '13'        => 'Goddard',
    '14'        => 'Laughlin',
    '15'        => 'Lovelock',
    '16'        => 'Verne',
    '17'        => 'Beefy Miracle',
    '18'        => 'Spherical Cow',
    '19'        => q[Schrödinger's Cat],
    '20'        => 'Heisenbug',
);

# http://en.wikipedia.org/wiki/Mandriva_Linux#Versions
my %mandriva = (
    '5.1'       => 'Venice',
    '5.2'       => 'Leeloo',
    '5.3'       => 'Festen',
    '6.0'       => 'Venus',
    '6.1'       => 'Helios',
    '7.0'       => 'Air',
    '7.1'       => 'Helium',
    '7.2'       => 'Odyssey (called Ulysses during beta)',
    '8.0'       => 'Traktopel',
    '8.1'       => 'Vitamin',
    '8.2'       => 'Bluebird',
    '9.0'       => 'Dolphin',
    '9.1'       => 'Bamboo',
    '9.2'       => 'FiveStar',
    '10.0'      => 'Community and Official',
    '10.1'      => 'Community',
    '10.1'      => 'Official',
    '10.2'      => 'Limited Edition 2005',
    '2006.0'    => 'Mandriva Linux 2006',
    '2007'      => 'Mandriva Linux 2007',
    '2007.1'    => 'Mandriva Linux 2007 Spring',
    '2008.0'    => 'Mandriva Linux 2008',
    '2008.1'    => 'Mandriva Linux 2008 Spring',
    '2009.0'    => 'Mandriva Linux 2009',
    '2009.1'    => 'Mandriva Linux 2009 Spring',
    '2010.0'    => 'Mandriva Linux 2010 (Adélie)',
    '2010.1'    => 'Mandriva Linux 2010 Spring',
    '2010.2'    => 'Mandriva Linux 2010.2',
    '2011.0'    => 'Hydrogen',
);

# http://en.wikipedia.org/wiki/Red_Hat_Linux#Version_history
# http://fedoraproject.org/wiki/History_of_Red_Hat_Linux
my %redhat = (
    '0.8'       => 'Preview',
    '0.9'       => 'Halloween',
    '1.0'       => q{Mother's Day},
    '1.1'       => q{Mother's Day+0.1},
    '2.0'       => '',
    '2.1'       => '',
    '3.0.3'     => 'Picasso',
    '3.95'      => 'Rembrandt',
    '4.0'       => 'Colgate',
    '4.1'       => 'Vanderbilt',
    '4.2'       => 'Biltmore',
    '4.95'      => 'Thunderbird',
    '4.96'      => 'Mustang',
    '5.0'       => 'Hurricane',
    '5.1'       => 'Manhattan',
    '5.2'       => 'Apollo',
    '5.9'       => 'Starbuck',
    '6.0'       => 'Hedwig',
    '6.0.95'    => 'Lorax',
    '6.1'       => 'Cartman',
    '6.1.95'    => 'Piglet',
    '6.2'       => 'Zoot',
    '6.2.98'    => 'Pinstripe',
    '7'         => 'Guinness',
    '7.0.90'    => 'Fisher',
    '7.0.91'    => 'Wolverine',
    '7.1'       => 'Seawolf',
    '7.1.92'    => 'Roswell',
    '7.2'       => 'Enigma',
    '7.2.92'    => 'Skipjack',
    '7.3'       => 'Valhalla',
    '7.3.92'    => 'Limbo',
    '7.3.93'    => 'Limbo',
    '7.3.94'    => 'Null',
    '8.0'       => 'Psyche',
    '8.0.93'    => 'Phoebe',
    '9'         => 'Shrike',
    '9.0.93'    => 'Severn',
);

# http://en.wikipedia.org/wiki/Red_Hat_Enterprise_Linux#Version_history
my %rhel = (
    '2.1 AS'    => 'Pensacola',
    '2.1'       => 'Pensacola',
    '2.1.1'     => 'Pensacola',
    '2.1.2'     => 'Pensacola',
    '2.1.3'     => 'Pensacola',
    '2.1.4'     => 'Pensacola',
    '2.1.5'     => 'Pensacola',
    '2.1.6'     => 'Pensacola',
    '2.1.7'     => 'Pensacola',
    '2.1 ES'    => 'Panama',
    '3'         => 'Taroon',
    '3.0'       => 'Taroon',
    '3.1'       => 'Taroon',
    '3.2'       => 'Taroon',
    '3.3'       => 'Taroon',
    '3.4'       => 'Taroon',
    '3.5'       => 'Taroon',
    '3.6'       => 'Taroon',
    '3.7'       => 'Taroon',
    '3.8'       => 'Taroon',
    '3.9'       => 'Taroon',
    '4'         => 'Nahant',
    '4.1'       => 'Nahant',
    '4.2'       => 'Nahant',
    '4.3'       => 'Nahant',
    '4.4'       => 'Nahant',
    '4.5'       => 'Nahant',
    '4.6'       => 'Nahant',
    '4.7'       => 'Nahant',
    '4.8'       => 'Nahant',
    '4.9'       => 'Nahant',
    '5'         => 'Tikanga',
    '5.1'       => 'Tikanga',
    '5.2'       => 'Tikanga',
    '5.3'       => 'Tikanga',
    '5.4'       => 'Tikanga',
    '5.5'       => 'Tikanga',
    '5.6'       => 'Tikanga',
    '5.7'       => 'Tikanga',
    '5.8'       => 'Tikanga',
    '6'         => 'Santiago',
    '6.1'       => 'Santiago',
    '6.2'       => 'Santiago',
    '6.3'       => 'Santiago',
    '6.4'       => 'Santiago',
    '6.5'       => 'Santiago',
    '7'         => 'Maipo',
);

# http://en.wikipedia.org/wiki/Scientific_Linux
my %scientific = (
    '3.0.1'     => 'Feynman',
    '3.0.2'     => 'Feynman',
    '3.0.3'     => 'Feynman',
    '3.0.4'     => 'Feynman',
    '3.0.5'     => 'Feynman',
    '3.0.6'     => 'Feynman',
    '3.0.7'     => 'Feynman',
    '3.0.8'     => 'Feynman',
    '3.0.9'     => 'Legacy',
    '4.0'       => 'Beryllium',
    '4.1'       => 'Beryllium',
    '4.2'       => 'Beryllium',
    '4.3'       => 'Beryllium',
    '4.4'       => 'Beryllium',
    '4.5'       => 'Beryllium',
    '4.6'       => 'Beryllium',
    '4.7'       => 'Beryllium',
    '4.8'       => 'Beryllium',
    '4.9'       => 'Beryllium',
    '5.0'       => 'Boron',
    '5.1'       => 'Boron',
    '5.2'       => 'Boron',
    '5.3'       => 'Boron',
    '5.4'       => 'Boron',
    '5.5'       => 'Boron',
    '5.6'       => 'Boron',
    '5.7'       => 'Boron',
    '5.8'       => 'Boron',
    '5.9'       => 'Boron',
    '6.0'       => 'Carbon',
    '6.1'       => 'Carbon',
    '6.2'       => 'Carbon',
    '6.3'       => 'Carbon',
    '6.4'       => 'Carbon',
    '6.5'       => 'Carbon',
    '6.6'       => 'Carbon',
    '6.7'       => 'Carbon',
    '6.8'       => 'Carbon',
    '6.9'       => 'Carbon',
    '6.10'      => 'Carbon',
    '6.11'      => 'Carbon',
    '7.0'       => 'Nitrogen',
    '7.1'       => 'Nitrogen',
    '7.2'       => 'Nitrogen',
);

# http://en.wikipedia.org/wiki/Ubuntu_%28operating_system%29#Releases
my %ubuntu = (
    '4.10'      => 'Warty Warthog',
    '5.04'      => 'Hoary Hedgehog',
    '5.10'      => 'Breezy Badger',
    '6.06'      => 'Dapper Drake',
    '6.10'      => 'Edgy Eft',
    '7.04'      => 'Feisty Fawn',
    '7.10'      => 'Gutsy Gibbon',
    '8.04'      => 'Hardy Heron',
    '8.10'      => 'Intrepid Ibex',
    '9.04'      => 'Jaunty Jackalope',
    '9.10'      => 'Karmic Koala',
    '10.04'     => 'Lucid Lynx',
    '10.10'     => 'Maverick Meerkat',
    '11.04'     => 'Natty Narwhal',
    '11.10'     => 'Oneiric Ocelot',
    '12.04'     => 'Precise Pangolin',
    '12.10'     => 'Quantal Quetzal',
    '13.04'     => 'Raring Ringtail',
    '13.10'     => 'Saucy Salamander',
    '14.04'     => 'Trusty Tahr',
    '14.10'     => 'Utopic Unicorn',
    '15.04'     => 'Vivid Vervet',
    '15.10'     => 'Wily Werewolf',
    '16.04'     => 'Xenial Xerus',
);

my %distributions = (
    'Adamantix'                 => { codenames => \%default,                        files => [ qw( /etc/adamantix_version ) ] },
    'Annvix'                    => { codenames => \%default,                        files => [ qw( /etc/annvix-release ) ] },
    'Arch Linux'                => { codenames => \%archlinux,                      files => [ qw( /etc/arch-release ) ] },
    'Arklinux'                  => { codenames => \%default,                        files => [ qw( /etc/arklinux-release ) ] },
    'Aurox Linux'               => { codenames => \%default,                        files => [ qw( /etc/aurox-release ) ] },
    'BlackCat'                  => { codenames => \%default,                        files => [ qw( /etc/blackcat-release ) ] },
    'Cobalt'                    => { codenames => \%default,                        files => [ qw( /etc/cobalt-release ) ] },
    'Conectiva'                 => { codenames => \%default,                        files => [ qw( /etc/conectiva-release ) ] },
    'CrunchBang Linux'          => { codenames => \%crunch,                         files => [ qw( /etc/lsb-release-crunchbang /etc/lsb-release ) ] },
    'Debian'                    => { codenames => \%debian,     key => 'debian',    files => [ qw( /etc/debian_version /etc/debian_release ) ] },
    'Fedora Core'               => { codenames => \%fedora,     key => 'fedora',    files => [ qw( /etc/fedora-release ) ] },
    'Gentoo Linux'              => { codenames => \%default,    key => 'gentoo',    files => [ qw( /etc/gentoo-release ) ] },
    'Immunix'                   => { codenames => \%default,                        files => [ qw( /etc/immunix-release ) ] },
    'Knoppix'                   => { codenames => \%default,                        files => [ qw( /etc/knoppix_version ) ] },
    'Libranet'                  => { codenames => \%default,                        files => [ qw( /etc/libranet_version ) ] },
    'Linux-From-Scratch'        => { codenames => \%default,                        files => [ qw( /etc/lfs-release ) ] },
    'Linux-PPC'                 => { codenames => \%default,                        files => [ qw( /etc/linuxppc-release ) ] },
    'Mandrake'                  => { codenames => \%mandriva,                       files => [ qw( /etc/mandrake-release ) ] },
    'Mandriva'                  => { codenames => \%mandriva,                       files => [ qw( /etc/mandriva-release /etc/mandrake-release /etc/mandakelinux-release ) ] },
    'Mandrake Linux'            => { codenames => \%mandriva,                       files => [ qw( /etc/mandriva-release /etc/mandrake-release /etc/mandakelinux-release ) ] },
    'MkLinux'                   => { codenames => \%default,                        files => [ qw( /etc/mklinux-release ) ] },
    'Novell Linux Desktop'      => { codenames => \%default,                        files => [ qw( /etc/nld-release ) ] },
    'Pardus'                    => { codenames => \%default,    key => 'pardus',    files => [ qw( /etc/pardus-release ) ] },
    'PLD Linux'                 => { codenames => \%default,                        files => [ qw( /etc/pld-release ) ] },
    'Red Flag'                  => { codenames => \%default,    key => 'redflag',   files => [ qw( /etc/redflag-release ) ] },
    'Red Hat Enterprise Linux'  => { codenames => \%rhel,       key => 'rhel',      files => [ qw( /etc/redhat-release /etc/redhat_version ) ] },
    'Red Hat Linux'             => { codenames => \%redhat,     key => 'redhat',    files => [ qw( /etc/redhat-release /etc/redhat_version ) ] },
    'Scientific Linux'          => { codenames => \%scientific,                     files => [ qw( /etc/lsb-release ) ] },
    'Slackware'                 => { codenames => \%default,    key => 'slackware', files => [ qw( /etc/slackware-version /etc/slackware-release ) ] },
    'SME Server'                => { codenames => \%default,                        files => [ qw( /etc/e-smith-release ) ] },
    'Sun JDS'                   => { codenames => \%default,                        files => [ qw( /etc/sun-release ) ] },
    'SUSE Linux'                => { codenames => \%default,    key => 'suse',      files => [ qw( /etc/SuSE-release /etc/novell-release ) ] },
    'SUSE Linux ES9'            => { codenames => \%default,    key => 'suse',      files => [ qw( /etc/sles-release ) ] },
    'Tiny Sofa'                 => { codenames => \%default,                        files => [ qw( /etc/tinysofa-release ) ] },
    'Trustix Secure Linux'      => { codenames => \%default,                        files => [ qw( /etc/trustix-release ) ] },
    'TurboLinux'                => { codenames => \%default,                        files => [ qw( /etc/turbolinux-release ) ] },
    'Ubuntu Linux'              => { codenames => \%ubuntu,                         files => [ qw( /etc/lsb-release ) ] },
    'UltraPenguin'              => { codenames => \%default,                        files => [ qw( /etc/ultrapenguin-release ) ] },
    'UnitedLinux'               => { codenames => \%default,                        files => [ qw( /etc/UnitedLinux-release ) ] },
    'VA-Linux/RH-VALE'          => { codenames => \%default,                        files => [ qw( /etc/va-release ) ] },
    'Yellow Dog'                => { codenames => \%default,                        files => [ qw( /etc/yellowdog-release ) ] },
    'Yoper'                     => { codenames => \%default,                        files => [ qw( /etc/yoper-release ) ] },
);

my %version_pattern = (
    'gentoo'    => 'Gentoo Base System version (.*)',
    'debian'    => '(.+)',
    'suse'      => 'VERSION = (.*)',
    'fedora'    => 'Fedora Core release (\d+) \(',
    'redflag'   => 'Red Flag (?:Desktop|Linux) (?:release |\()(.*?)(?: \(.+)?\)',
    'redhat'    => 'Red Hat Linux release (.*) \(',
    'rhel'      => 'Red Hat Enterprise Linux(?: Server)? release (.*) \(',
    'slackware' => '^Slackware (.+)$',
    'pardus'    => '^Pardus (.+)$',
);

my %oslabel_pattern = (
    'suse'      => '^(\S+)',
    'rhel'      => '(Red Hat Enterprise Linux(?: Server)?) release (.*) \(',
);

#----------------------------------------------------------------------------

sub new {
    my ($class) = @_;
    my $self = {};
    bless $self, $class;

    return $self;
}

sub get_info {
    my $self  = shift;

    for my $cmd (keys %commands) {
        $self->{cmds}{$cmd} = `$commands{$cmd} 2>/dev/null`;
        $self->{cmds}{$cmd} =~ s/\s+$//s;
        $self->{info}{$cmd} = $self->{cmds}{$cmd}   if($cmd !~ /^_/);
    }

    $self->{info}{osflag}       = $^O;
    $self->{info}{kernel}       = lc($self->{info}{kname}) . '-' . $self->{info}{kvers};

    $self->{info}{is32bit}      = $self->{info}{archname} !~ /_(64)$/ ? 1 : 0;
    $self->{info}{is64bit}      = $self->{info}{archname} =~ /_(64)$/ ? 1 : 0;

    if($self->{cmds}{'_lsb'}) {
        ($self->{info}{oslabel})    = $self->{cmds}{'_lsb'} =~ /Distributor ID:\s*(.*?)\n/si;
        ($self->{info}{osvers})     = $self->{cmds}{'_lsb'} =~ /Release:\s*(.*?)\n/si;
        ($self->{info}{codename})   = $self->{cmds}{'_lsb'} =~ /Codename:\s*(.*)\n?/si;
    } else {
        $self->_release_version();
    }

    $self->{info}{source}{$commands{$_}} = $self->{cmds}{$_}    for(keys %commands);
    return $self->{info};
}

#----------------------------------------------------------------------------

sub _release_version {
    my $self = shift;

    for my $label (keys %distributions) {
        for my $file (@{ $distributions{$label}->{files} }) {
            next    unless(-f $file);
            my $line = `cat $file 2>/dev/null`;

            my ($version,$oslabel);
            if($distributions{$label}->{key}) {
                if($version_pattern{ $distributions{$label}->{key} }) {
                    ($version) = $line =~ /$version_pattern{ $distributions{$label}->{key} }/si;
                }
                if($oslabel_pattern{ $distributions{$label}->{key} }) {
                    ($oslabel) = $line =~ /$oslabel_pattern{ $distributions{$label}->{key} }/si;
                }
            }

            $version = $line    unless($version);
            $version =~ s/\s*$//;

            unless($oslabel) {
                if($self->{cmds}{'_issue1'}) {
                    ($oslabel) = $self->{cmds}{'_issue1'} =~ /^(\S*)/;
                } elsif($self->{cmds}{'_issue2'}) {
                    ($oslabel) = $self->{cmds}{'_issue2'} =~ /^(\S*)/;
                }
                $oslabel ||= $label;    # a last resort
            }

            $self->{info}{oslabel}  = $oslabel;
            $self->{info}{osvers}   = $version;
            $commands{'_cat'} = "cat $file";
            $self->{cmds}{'_cat'}  = $line;

            for my $vers (keys %{ $distributions{$label}->{codenames} }) {
                if($version =~ /^$vers\b/) {
                    $self->{info}{codename} = $distributions{$label}->{codenames}{$vers};
                    return;
                }
            }

            return;
        }
    }
}

1;

__END__

=head1 SYNOPSIS

  use Devel::Platform::Info::Linux;
  my $info = Devel::Platform::Info::Linux->new();
  my $data = $info->get_info();

=head1 DESCRIPTION

This module is a driver to determine platform metadata regarding the Linux
operating system. It should be called indirectly via it's parent 
Devel::Platform::Info

=head1 INTERFACE

=head2 The Constructor

=over

=item * new

Simply constructs the object.

=back

=head2 Methods

=over 4

=item * get_info

Returns a hash reference to the Linux platform metadata.

Returns the following keys:

  source
  archname
  osname
  osvers
  oslabel
  is32bit
  is64bit
  osflag

  codename
  kernel
  kname
  kvers

=back

=head1 REFERENCES

The following links were used to understand how to retrieve the metadata:

  * http://distrowatch.com/
  * Wikipedia pages for various Linux and Unix based OSes
  * http://search.cpan.org/dist/Sys-Info-Driver-Linux

=head1 BUGS, PATCHES & FIXES

There are no known bugs at the time of this release. However, if you spot a
bug or are experiencing difficulties, that is not explained within the POD
documentation, please send bug reports and patches to the RT Queue (see below).

RT Queue: http://rt.cpan.org/Public/Dist/Display.html?Name=Devel-Platform-Info

=head1 AUTHORS

  Barbie (BARBIE) <barbie@cpan.org>
  Brian McCauley (NOBULL) <nobull67@gmail.com>
  Colin Newell (NEWELL) <newellc@cpan.org>
  Jon 'JJ' Allen (JONALLEN) <jj@jonallen.info>

=head1 COPYRIGHT & LICENSE

  Copyright (C) 2010-2016 Birmingham Perl Mongers

  This distribution is free software; you can redistribute it and/or
  modify it under the Artistic License 2.0.

=cut