#!/usr/bin/perl

package lxc_pve_prestart_hook;

use strict;
use warnings;

use Fcntl qw(O_DIRECTORY :mode);
use File::Path;
use POSIX;

use PVE::Cluster;
use PVE::LXC::Config;
use PVE::LXC::Setup;
use PVE::LXC::Tools;
use PVE::LXC;
use PVE::Storage;
use PVE::Syscall qw(:fsmount);
use PVE::Tools qw(AT_FDCWD O_PATH);

PVE::LXC::Tools::lxc_hook('pre-start', 'lxc', sub {
    my ($vmid, $vars, undef, undef) = @_;

    my $skiplock_flag_fn = "/run/lxc/skiplock-$vmid";
    my $skiplock = 1 if -e $skiplock_flag_fn;
    unlink $skiplock_flag_fn if $skiplock;

    PVE::Cluster::check_cfs_quorum(); # only start if we have quorum

    return undef if ! -f PVE::LXC::Config->config_file($vmid);

    my $conf = PVE::LXC::Config->load_config($vmid);
    if (!$skiplock && !PVE::LXC::Config->has_lock($conf, 'mounted')) {
	PVE::LXC::Config->check_lock($conf);
    }

    my $storage_cfg = PVE::Storage::config();

    my $vollist = PVE::LXC::Config->get_vm_volumes($conf);
    my $loopdevlist = PVE::LXC::Config->get_vm_volumes($conf, 'rootfs');

    PVE::Storage::activate_volumes($storage_cfg, $vollist);

    my $rootdir = $vars->{ROOTFS_PATH};

    # Delete any leftover reboot-trigger file
    unlink("/var/lib/lxc/$vmid/reboot");

    my $devlist_file = "/var/lib/lxc/$vmid/devices";
    unlink $devlist_file;
    my $devices = [];

    my (undef, $rootuid, $rootgid) = PVE::LXC::parse_id_maps($conf);

    # Unmount first when the user mounted the container with "pct mount".
    eval {
	PVE::Tools::run_command(['umount', '--recursive', $rootdir], outfunc => sub {}, errfunc => sub {});
    };

    my $setup_mountpoint;
    if (!PVE::LXC::Tools::can_use_new_mount_api()) {
	# Legacy mode for old kernels:
	$setup_mountpoint = sub {
	    my ($opt, $mountpoint) = @_;

	    my (undef, undef, $dev) = PVE::LXC::mountpoint_mount(
		$mountpoint,
		$rootdir,
		$storage_cfg,
		undef,
		$rootuid,
		$rootgid,
	    );
	    push @$devices, $dev if $dev && $mountpoint->{quota};
	};
    } else {
	# With newer kernels we stage mount points and then use move_mount().
	my $rootdir_fd = undef;
	$setup_mountpoint = sub {
	    my ($opt, $mountpoint) = @_;

	    my $dir = PVE::LXC::get_staging_mount_path($opt);
	    my (undef, undef, $dev, $mount_fd) = PVE::LXC::mountpoint_stage(
		$mountpoint,
		$dir,
		$storage_cfg,
		undef,
		$rootuid,
		$rootgid,
	    );

	    my $ddir;
	    if ($rootdir_fd) {
		# Mount relative to the rootdir fd.
		$ddir = './' . $mountpoint->{mp};
	    } else {
		# Assert that 'rootfs' is the first one:
		die "foreach_mount() error\n" if $opt ne 'rootfs';

		# Mount the rootfs absolutely (rootdir_fd=undef uses AT_FDCWD).
		# $rootdir is not controlled by the container, so this is fine.
		$ddir = $rootdir;
	    }

	    PVE::LXC::mountpoint_insert_staged(
		$mount_fd,
		$rootdir_fd,
		$ddir,
		$opt,
		$rootuid,
		$rootgid,
	    );

	    # From now on we mount inside our rootfs:
	    if (!$rootdir_fd) {
		$rootdir_fd = $mount_fd;
	    }

	    push @$devices, $dev if $dev && $mountpoint->{quota};
	};
    }

    PVE::LXC::Config->foreach_mountpoint($conf, $setup_mountpoint);

    my $lxc_setup = PVE::LXC::Setup->new($conf, $rootdir);
    $lxc_setup->pre_start_hook();

    if (@$devices) {
	my $devlist = '';
	foreach my $dev (@$devices) {
	    my ($mode, $rdev) = (stat($dev))[2,6];
	    next if !$mode || !S_ISBLK($mode) || !$rdev;
	    my $major = PVE::Tools::dev_t_major($rdev);
	    my $minor = PVE::Tools::dev_t_minor($rdev);
	    $devlist .= "b:$major:$minor:$dev\n";
	}
	PVE::Tools::file_set_contents($devlist_file, $devlist);
    }
});
