#!/usr/bin/env ruby
# coding: utf-8
#
# Syd: rock-solid application kernel
# dev/cave-force.rb: Cave subcommand to generate force rules using package contents.
# Copyright (c) 2024, 2026 Ali Polatel <alip@chesswob.org>
# SPDX-License-Identifier: GPL-3.0

require 'Paludis'

require 'getoptlong'
require 'open3'

include Paludis

def sum(path, alg)
  stdout, stderr, status = Open3.capture3('syd-sum', '-a', alg, path)
  if status.success?
    stdout.split(' ')[0]
  else
    # If the command failed, raise an error with the stderr output
    raise "#{stderr}"
  end
end

def is_elf(location)
  begin
    File.open(location, "rb") do |file|
      signature = file.read(4)
      return signature == "\x7FELF"
    end
  rescue
    return false
  end
end

NAME = File::basename($0, '.rb')
Log.instance.log_level = LogLevel::Warning
Log.instance.program_name = NAME

version = "0.1.0"

opts = GetoptLong.new(
  [ '--help',         '-h', GetoptLong::NO_ARGUMENT ],
  [ '--version',      '-V', GetoptLong::NO_ARGUMENT ],
  [ '--log-level',          GetoptLong::REQUIRED_ARGUMENT ],
  [ '--environment',  '-E', GetoptLong::REQUIRED_ARGUMENT ],
  [ '--algorithm',    '-a', GetoptLong::REQUIRED_ARGUMENT ],
  [ '--insecure',     '-i', GetoptLong::NO_ARGUMENT ],
  [ '--syd',          '-s', GetoptLong::NO_ARGUMENT ])

$envspec = ""
$sprefix = false
$use_md5 = false
$algorithm = 'sha3-512'

opts.each do | opt, arg |
  case opt
  when '--help'
    puts <<HELP
Usage: #{NAME} [options] spec...

Options:
  --help, -h             Display a help message
  --version, -V          Display program version
  --log-level            Set log level (debug, qa, warning, silent)
  --environment, -E      Environment specification (class:suffix, both parts
                         optional, class must be 'paludis' if specified)
  --algorithm, -a ALG    Hash algorithm for syd-sum (default: sha3-512)
  --insecure, -i         Do not rehash with syd-sum
                         Directly use the MD5 checksums in package contents
  --syd, -s              Prefix rules with /dev/syd/

Queries installed executable files by the given specs.
Queries file contents for MD5 checksums.
Writes syd force sandboxing rules using the information.
HELP
    exit 0
  when '--version'
    puts NAME + " " + version + " (Paludis Version: " + Version + ")"
    exit 0
  when '--log-level'
    case arg
    when 'debug'
      Log.instance.log_level = LogLevel::Debug
    when 'qa'
      Log.instance.log_level = LogLevel::Qa
    when 'warning'
      Log.instance.log_level = LogLevel::Warning
    when 'silent'
      Log.instance.log_level = LogLevel::Silent
    else
      $stderr.puts "Bad --log-level value " + arg
      exit 1
    end
  when '--environment'
    $envspec = arg
  when '--algorithm'
    $algorithm = arg
  when '--insecure'
    $use_md5 = true
  when '--syd'
    $sprefix = true
  end
end

$env = EnvironmentFactory.instance.create($envspec)
if ARGV.empty?
  $stderr.puts "No specs supplied"
  exit 1
end

prefix = '/dev/syd/' if $sprefix
ARGV.each do |spec|
  ids = $env[Selection::AllVersionsSorted.new(
        Generator::Matches.new(Paludis::parse_user_package_dep_spec(spec, $env, [:allow_wildcards]), nil, []) |
        Filter::InstalledAtRoot.new("/"))]
  ids.each do |id|
    contents = id.contents
    next unless contents
    contents.each do |content|
      # Filter non-files.
      next unless content.kind_of? ContentsFileEntry

      # Filter non-executables.
      location = content.location_key.parse_value
      next unless is_elf(location)

      # Find and parse MD5sum
      checksum = nil
      if $use_md5
        content.each_metadata do |key|
          if key.kind_of? MetadataStringKey
            value = key.parse_value
            if value.length == 32 # MD5sum
              checksum = value
              break
            end
          end
        end
        next if checksum.nil?
      else
        checksum = sum(location, $algorithm)
      end
      alg = $use_md5 ? 'md5' : $algorithm
      puts "#{prefix}force+#{location}:#{alg}:#{checksum}:kill"
    end
  end
end
