04 October 2025, Saturday

(Perl) Config::TinyのINIファイル読み書きと、Stat::lsMode::format_modeをライブラリを使わずインライン実装する

サーバの大掃除のついでに、ユーザ権限でCPANライブラリを導入していたものを一掃したい。そのために、と、Stat::lsMode::format_modeをライブラリを使わずインライン実装する簡易関数をAIに書いてもらった。

Config::TinyのINIファイル読み書きを代替する関数

#!/usr/bin/perl

use Data::Dumper;

$ini_filename = "test.ini";

# *******************
# INI読み込み

my $config = read_ini($ini_filename);

if ( !defined($config) ) {
    print("ini file not found\n");
    exit(0);
}
my $str_temp = $config->{debug_section}->{debug_attr};

if ( !defined($str_temp) ) {
    printf("value not found\n");
}
else {
    printf( "[section] attr = %s\n", $str_temp );
}

print("---debug---\n");
print Dumper $config;
print("---debug---\n");

# *******************
# INI書き込み

$time = time();
( $sec, $min, $hour, $mday, $mon, $year, $wday ) = localtime($time);
$new_value = sprintf(
    "%04d-%02d-%02d %02d:%02d:%02d",
    $year + 1900, $mon + 1, $mday, $hour, $min, $sec
);

# 書き込み
my $config = {};
$config->{debug_section0}->{debug_attr0} = "dummy string 0";
$config->{debug_section}->{debug_attr}   = $new_value;
$config->{debug_section}->{debug_attr2}  = "dummy string 2";
$config->{debug_section2}->{debug_attr3} = "dummy string 3";
write_ini( $ini_filename, $config );

printf( "new value (%s) written\n", $new_value );

# *************
# INIファイルに書き込む (Config::Tiny 互換)
# param[0]: string ファイル名
# return:   hash 全ての値を格納したハッシュ{セクション名}{値名}
sub read_ini {
    my ($filename) = @_;
    open my $fh, '<', $filename or return undef;

    my %config;
    my $section = '';

    while ( my $line = <$fh> ) {
        chomp $line;
        $line =~ s/^\s+|\s+$//g;                           # 前後の空白除去
        next if $line eq '' or $line =~ /^;/;              # 空行・コメント除外

        if ( $line =~ /^\[(.+?)\]$/ ) {
            $section = $1;
            $config{$section} ||= {};
        }
        elsif ( $line =~ /^([^=]+?)\s*=\s*(.*)$/ ) {
            my ( $key, $value ) = ( $1, $2 );
            $config{$section}{$key} = $value;
        }
    }
    close $fh;
    return \%config;
}

# *************
# INIファイルに書き込む (Config::Tiny 互換)
# param[0]: string ファイル名
# param[1]: hash 全ての値を格納したハッシュ{セクション名}{値名}
sub write_ini {
    my ( $filename, $config_ref ) = @_;
    open my $fh, '>', $filename or return 0;

    foreach my $section ( sort keys %$config_ref ) {
        print $fh "[$section]\n";

        foreach my $key ( sort keys %{ $config_ref->{$section} } ) {
            my $value = $config_ref->{$section}{$key};
            print $fh "$key=$value\n";
        }
        print $fh "\n";
    }
    close $fh;
    return 1;
}

Stat::lsMode::format_modeを代替する関数

#!/usr/bin/perl
use File::Basename;

my $filepath;

if ( @ARGV == 1 ) {
    $filepath = $ARGV[0];
}
else {
    my $scriptname = basename( $0, '' );
    print("usage : $scriptname [target filepath]\n");
    exit(1);

}

print("target path = $filepath \n");

my @stats    = stat($filepath);
my $attr_int = $stats[2];

# $attr_int       = 0100755;  # -rwxr-xr-x  # debug
my $attr_formatted = format_mode($attr_int);

printf( "attr : %s (%X, %o)\n", $attr_formatted, $attr_int, $attr_int );

# *************
# stat の第3要素(モード情報)を人間が読める形式(例: -rw-r--r--)に変換する関数 (Stat::lsMode::format_mode と互換の関数)
sub format_mode {
    my ($mode) = @_;

    # ファイルタイプ
    my %file_types = (
        0xC000 => 's',    # socket
        0xA000 => 'l',    # symbolic link
        0x8000 => '-',    # regular file
        0x6000 => 'b',    # block device
        0x4000 => 'd',    # directory
        0x2000 => 'c',    # character device
        0x1000 => 'p',    # FIFO
    );
    my $type_char = '?';

    foreach my $type ( keys %file_types ) {
        if ( ( $mode & 0xF000 ) == $type ) {
            $type_char = $file_types{$type};
            last;
        }
    }

    # パーミッション
    my @rwx      = qw(r w x);
    my $perm_str = '';

    for my $i ( 0 .. 2 ) {
        my $shift = 6 - $i * 3;
        my $bits  = ( $mode >> $shift ) & 0x7;

        for my $j ( 0 .. 2 ) {
            $perm_str .= ( $bits & ( 1 << ( 2 - $j ) ) ) ? $rwx[$j] : '-';
        }
    }

    # setuid, setgid, sticky bit の処理
    substr( $perm_str, 2, 1 ) =
      ( $mode & 0x800 ) ? ( ( $perm_str =~ /^..x/ ) ? 's' : 'S' ) : substr( $perm_str, 2, 1 );
    substr( $perm_str, 5, 1 ) =
      ( $mode & 0x400 ) ? ( ( $perm_str =~ /^.....x/ ) ? 's' : 'S' ) : substr( $perm_str, 5, 1 );
    substr( $perm_str, 8, 1 ) =
      ( $mode & 0x200 ) ? ( ( $perm_str =~ /^........x/ ) ? 't' : 'T' ) : substr( $perm_str, 8, 1 );

    return $type_char . $perm_str;
}


Copilot AIがsetuid, setgid, sticky bitを検出するビット計算を間違っていたので、そこだけ修正したのが上のソースコード