The Perl Toolchain Summit needs more sponsors. If your company depends on Perl, please support this very important event.
use strict;
use warnings;

package Solaris::SMF::Service;
$Solaris::SMF::Service::VERSION = '1.0.1';
# ABSTRACT: Encapsulate Solaris 10 services in Perl

use Params::Validate qw( validate validate_pos :types );
use Log::Any qw($log);
use Carp;


sub _svcs {
    my $self = shift;
    local $ENV{PATH} = '/bin:/usr/bin:/sbin:/usr/sbin';
    open my $svc_list, '-|', " svcs -aH '$self->{FMRI}' 2>/dev/null"
        or croak 'Unable to query SMF services';
    while ( my $svc_line = <$svc_list> ) {
        my ( $state, $date, $FMRI ) = (
            $svc_line =~ m/
                ^
                ([^\s]+)        # Current state
                [\s]+
                ([^\s]+)        # Date this state was set
                [\s]+
                ( (?: svc: | lrc: ) [^\s]+ ) # FMRI
                \n?
                $
        /xms
        );
        if ($FMRI) {
            close $svc_list;
            return ( $state, $date );
        }
    }
    croak "Unable to determine status of $self->{FMRI}";
}

sub _svcprop {
    $log->is_trace && $log->trace( '_svcprop ' . join( ',', @_ ) );
    my $self = shift;
    local $ENV{PATH} = '/bin:/usr/bin:/sbin:/usr/sbin';
    open my $svcprop_list, '-|', " svcprop '$self->{FMRI}' 2>/dev/null"
        or croak 'Unable to query SMF service properties';
    my %properties;
    while ( my $svcprop_line = <$svcprop_list> ) {
        my ( $name, $type, $value ) = (
            $svcprop_line =~ m/
                ^
                ([^\s]+)        # Property name
                [\s]+
                ([^\s]+)        # Type of property
                [\s]+
                ([^\s]*[^\n]*)        # Value of property
                $
        /xms
        );
        if ($name) {
            $properties{$name}{type}  = $type;
            $properties{$name}{value} = $value;
        }
        $log->is_trace && $log->tracef( '$name: %s $type: %s $value: %s',
            $name, $type, $value );
    }
    $log->is_trace && $log->tracef( '$properties: %s', \%properties );
    return \%properties;
}

sub _svcadm {
    $log->is_trace && $log->trace( '_svcadm ' . join( ',', @_ ) );
    my $self          = shift;
    my $svcadm_action = shift;
    local $ENV{PATH} = '/bin:/usr/bin:/sbin:/usr/sbin';
    open my $svc_adm, '-|', " svcadm $svcadm_action '$self->{FMRI}' 2>&1"
        or croak 'Unable to administer SMF services';
    close $svc_adm;
}


sub new {
    my $class = shift;
    my $FMRI  = shift;
    $log->is_trace && $log->trace("$class -> new( '$FMRI' )");
    my $service = bless {}, __PACKAGE__;
    $service->{FMRI} = $FMRI;
    return $service;
}


sub status {
    $log->is_trace && $log->trace( 'status ' . join( ',', @_ ) );
    my $self = shift;
    my ( $status, $date ) = $self->_svcs();
    $log->is_trace && $log->tracef( '$status: %s $date: %s', $status, $date );
    return $status;
}


sub FMRI {
    $log->is_trace && $log->trace( 'FMRI ' . join( ',', @_ ) );
    my $self = shift;
    return $self->{FMRI};
}


sub properties {
    $log->is_trace && $log->trace( 'properties ' . join( ',', @_ ) );
    my $self       = shift;
    my $properties = $self->_svcprop();
    return %{$properties};
}


sub property {
    $log->is_trace && $log->trace( 'property ' . join( ',', @_ ) );
    my $self            = shift;
    my $p               = validate_pos( @_, { type => SCALAR } );
    my ($property_name) = @{$p};

    my $properties = $self->_svcprop();
    $log->is_trace && $log->tracef( '$properties: %s', $properties );
    if ( defined $properties->{$property_name} ) {
        return $properties->{$property_name}{value};
    }
    else {
        carp "Unable to find property '$property_name' for " . $self->{FMRI};
        undef;
    }
}


sub property_type {
    $log->is_trace && $log->trace( 'property_type ' . join( ',', @_ ) );
    my $self            = shift;
    my $p               = validate_pos( @_, { type => SCALAR } );
    my ($property_name) = @{$p};

    my $properties = $self->_svcprop();
    $log->is_trace && $log->tracef( '$properties: %s', $properties );
    if ( defined $properties->{$property_name} ) {
        return $properties->{$property_name}{type};
    }
    else {
        carp "Unable to find property '$property_name' for " . $self->{FMRI};
        undef;
    }
}


sub disable {
    $log->is_trace && $log->trace( 'disable ' . join( ',', @_ ) );
    my $self = shift;
    return $self->_svcadm('disable');
}


sub stop {
    $log->is_trace && $log->trace( 'stop ' . join( ',', @_ ) );
    my $self = shift;
    return $self->_svcadm('disable -t');
}


sub enable {
    $log->is_trace && $log->trace( 'enable ' . join( ',', @_ ) );
    my $self = shift;
    return $self->_svcadm('enable');
}


sub start {
    $log->is_trace && $log->trace( 'start ' . join( ',', @_ ) );
    my $self = shift;
    return $self->_svcadm('enable -t');
}


sub refresh {
    $log->is_trace && $log->trace( 'refresh ' . join( ',', @_ ) );
    my $self = shift;
    return $self->_svcadm('refresh');
}


sub clear {
    $log->is_trace && $log->trace( 'clear ' . join( ',', @_ ) );
    my $self = shift;
    return $self->_svcadm('clear');
}


sub mark {
    $log->is_trace && $log->trace( 'mark ' . join( ',', @_ ) );
    my $self = shift;
    return $self->_svcadm('mark');
}

1;    # End of Solaris::SMF::Service

__END__

=pod

=encoding UTF-8

=head1 NAME

Solaris::SMF::Service - Encapsulate Solaris 10 services in Perl

=head1 VERSION

version 1.0.1

=head1 SYNOPSIS

Interface to Sun's Service Management Facility in Solaris 10. This module provides
a wrapper around 'svcs', 'svcadm' and 'svccfg'.

The SMF in Solaris is a replacement for inetd as well as the runlevel-based stopping
and starting of daemons. Service definitions are stored in an XML database.

The biggest advantages in using SMF are the resiliency support, consistent interface and
inter-service dependencies it offers. Services that die for any reason can be automatically
restarted by the operating system; all services can be enabled or disabled using the same
commands; and services can be started as soon as all the services they depend upon have
been started, rather than at a fixed point in the boot process.

=head1 METHODS


=head2 new

Create a new Service object. The parameter must be a valid, unique FMRI.


=head2 status

Get the current status of this service. Returns a string, 'disabled', 'enabled', 'offline'.


=head2 FMRI

Returns the Fault Managed Resource Identifier for this service.


=head2 properties

Returns all or some properties for this service.


=head2 property

Returns the value of a single property of this service.


=head2 property_type

Returns the type of a single property of this service.


=head2 disable

This instructs SMF to disable the service permanently. To disable temporarily,
that is until the next time the server is rebooted, use the 'stop' method.


=head2 stop

This instructs SMF to stop the service. It uses the -t flag to svcadm, so that
using this call will not prevent the service from starting the next time the
server reboots.


=head2 enable

This instructs SMF to enable the service permanently. To enable temporarily,
that is until the next time the server is rebooted, see the 'start' method.


=head2 start

This instructs SMF to start the service. This change is not made persistent
unless you use the 'enable' method.


=head2 refresh

This instructs SMF to refresh the service. Needed whenever alterations are
made to a service's properties. It acts as the analogue of a SQL 'commit'.


=head2 clear

This instructs SMF to clear the service's state, that is, to remove the
'failed' marker from it. This is needed prior to starting a failed service.


=head2 mark

This instructs SMF to mark the service as failed.

=head1 AUTHOR

Brad Macpherson <brad@teched-creations.com>

=head1 COPYRIGHT AND LICENSE

This software is copyright (c) 2015 by TecHed Creations Ltd.

This is free software; you can redistribute it and/or modify it under
the same terms as the Perl 5 programming language system itself.

=cut