#!/usr/bin/perl

# Print values of C constants.

# Copyright (C) 2012  Simon Ruderich
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program.  If not, see <http://www.gnu.org/licenses/>.


use strict;
use warnings;

use Getopt::Long ();
use File::Temp ();

our $VERSION = '0.01';


my $option_help     = 0;
my $option_version  = 0;
my @option_include  = ();
my $option_pathconf = undef;
my $option_quiet    = 0;
my $option_sysconf  = 0;
my $option_type     = 'd';
if (not Getopt::Long::GetOptions(
            'help|h|?'     => \$option_help,
            'version|v'    => \$option_version,
            #
            'include|i=s'  => \@option_include,
            'pathconf|p=s' => \$option_pathconf,
            'quiet|q'      => \$option_quiet,
            'sysconf|s'    => \$option_sysconf,
            'type|t=s'     => \$option_type,
        )) {
    require Pod::Usage;
    Pod::Usage::pod2usage(2);
}
if ($option_help) {
    require Pod::Usage;
    Pod::Usage::pod2usage(1);
}
if ($option_version) {
    print <<EOF;
print-constant $VERSION  Copyright (C) 2012  Simon Ruderich

This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program.  If not, see <http://www.gnu.org/licenses/>.
EOF
    exit 0;
}

# We need the constant name as argument.
if (scalar @ARGV != 1) {
    require Pod::Usage;
    Pod::Usage::pod2usage(2);
}


my $constant = $ARGV[0];
# Check for non-constant name inputs - also prevents running arbitrary code on
# the machine.
if (not $constant =~ /^[a-zA-Z0-9_]+$/) {
    print STDERR "Invalid constant name: '$constant'!\n";
    exit 1;
}

# Some sanity checks.
if (not $option_type =~ /^[a-zA-Z]+$/) {
    print STDERR "Invalid type: '$option_type'!\n";
    exit 1;
}
if ($option_sysconf and defined $option_pathconf) {
    print STDERR "--sysconf and --pathconf can't be used together!\n";
    exit 1;
}
foreach my $include (@option_include) {
    if (not $include =~ m{^[a-zA-Z0-9./_-]+$}) {
        print STDERR "Invalid include: '$include'!\n";
        exit 1;
    }
}

my $constant_display = $constant;
# Pass the constant to sysconf()/pathconf() instead of using it directly.
if ($option_sysconf) {
    $constant = "sysconf($constant)";
    $constant_display = $constant;
} elsif (defined $option_pathconf) {
    $constant_display = "pathconf(\"$option_pathconf\", $constant)";
    # Use the current directory to prevent escape problems for the path, see
    # below.
    $constant = "pathconf(\".\", $constant)";
}


# Use "limits.h" and "unistd.h" as default include - but only if the user
# specified no other includes, in case it conflicts with "limits.h".
if (scalar @option_include == 0) {
    push @option_include, 'limits.h';
    push @option_include, 'unistd.h';
}


# Temporary directory where the following C program which prints the constant
# is built.
my $tempdir = File::Temp::tempdir(CLEANUP => 1) or die $!;
chdir $tempdir or die $!;

# Write the C file which prints the constant value.
my $c_template = <<'EOF';
#include <stdio.h>
#include <stdlib.h>
%s

int main(int argc, char **argv) {
    printf("%s\n", %s);

    return EXIT_SUCCESS;
}
EOF
open my $fh, '>', 'constant.c' or die $!;
printf $fh $c_template,
           join("\n", map { "#include <$_>" } @option_include),
           '%' . $option_type,
           $constant;
close $fh or die $!;

# Compile the constant C file.
my @args = ('cc', '-o', 'constant', 'constant.c');
if (system(@args) != 0) {
    print STDERR "Compilation failure (message above).\n";

    chdir; # see below
    exit 1;
}

# pathconf() needs a path, we use the current directory - therefore we have to
# chdir to the given path. This prevents escape problems with the path.
if (defined $option_pathconf) {
    chdir $option_pathconf;
}

my $value = `$tempdir/constant`;

if ($option_quiet) {
    print $value;
} else {
    print "$constant_display: $value";
}

# Change the working directory or File::Temp can't remove our temporary
# directory.
chdir;


__END__

=head1 NAME

print-constant - print values of C constants

=head1 SYNOPSIS

B<print-constant> [I<options>] I<CONSTANT_NAME>

=head1 DESCRIPTION

print-constant prints the values of C constants. Useful to find out certain
system constants without having to write a small C program to print them.

=head1 OPTIONS

=over 8

=item B<-i> I<include>, B<--include> I<include>

Use I<include> as include file. Adds C<#include E<lt>includeE<gt>> to the
beginning of the C file used to print the constant. By default C<limits.h> and
C<unistd.h> are used as headers. If this option is specified I<no> default
includes are used. Can be specified multiple times.

=item B<-p> I<path>, B<--pathconf> I<path>

Pass the constant to pathconf(3) and print the result. I<path> is the first
argument for pathconf(3). Conflicts with B<--sysconf>.

=item B<-q>, B<--quiet>

Display only the constant's value.

=item B<-s>, B<--sysconf>

Pass the constant to sysconf(3) and print the result. Conflicts with
B<--pathconf>.

=item B<-t> I<type>, B<--type> I<type>

Use I<type> as type of the constant, used in printf(3). By default C<d> is
used.

=item B<-h>, B<-?>, B<--help>

Print available options.

=item B<-v>, B<--version>

Print version number and license.

=back

=head1 AUTHOR

Simon Ruderich, E<lt>simon@ruderich.orgE<gt>

=head1 COPYRIGHT AND LICENSE

Copyright (C) 2012 by Simon Ruderich

This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program.  If not, see <http://www.gnu.org/licenses/>.

=head1 SEE ALSO

L<sysconf(3)>, L<pathconf(3)>

=cut
