#!/usr/local/bin/php
<?php

/*
 * Copyright (C) 2022-2026 Deciso B.V.
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 * 1. Redistributions of source code must retain the above copyright notice,
 *    this list of conditions and the following disclaimer.
 *
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES,
 * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
 * AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
 * AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY,
 * OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 */

require_once 'config.inc';
require_once 'filter.inc';
require_once 'interfaces.inc';
require_once 'util.inc';

/* gather all relevant vlan's (new/updated and removed) into a single list */
$all_vlans = [];
$all_parents = [];
$vfilename = '/tmp/.vlans.removed';
if (file_exists($vfilename) && filesize($vfilename) > 0) {
    $handle = fopen($vfilename, 'r+');
    if (flock($handle, LOCK_EX)) {
        fseek($handle, 0);
        foreach (explode("\n", fread($handle, filesize($vfilename))) as $line) {
            if (!isset($all_vlans[$line]) && trim($line) != '') {
                $all_vlans[$line] = [];
            }
        }
        fseek($handle, 0);
        ftruncate($handle, 0);
        flock($handle, LOCK_UN);
    }
}

/* merge configured vlans */
foreach (config_read_array('vlans', 'vlan', false) as $vlan) {
    $all_vlans[$vlan['vlanif']] = $vlan;
    if (!isset($all_parents[$vlan['if']])) {
        $all_parents[$vlan['if']] = 0;
    }
    $all_parents[$vlan['if']]++;
}

/* handle existing vlans */
foreach (legacy_interfaces_details() as $device => $details) {
    if (empty($details['vlan'])) {
        continue;
    }
    if (!isset($all_vlans[$device])) {
        continue;
    }
    if (empty($all_vlans[$device])) {
        /* option 1: removed vlan */
        legacy_interface_destroy($device);
    } else {
        $vlan = $all_vlans[$device];
        if (empty($vlan['proto'])) {
            $vlan['proto'] = empty($all_parents[$vlan['vlanif']]) ? '802.1q' : '802.1ad';
        }
        $cvlan = $details['vlan'];
        if ($vlan['tag'] != $cvlan['tag'] || $vlan['if'] != $cvlan['parent']) {
            /* option 2: changed vlan, unlink and relink */
            /*
             * XXX: legacy code used interface_configure() in these cases,
             * but since you cannot change a tag or a parent for an assigned
             * interface.  At the moment that does not seem to make much sense.
             */
            legacy_vlan_remove_tag($vlan['vlanif']);
            legacy_vlan_tag($vlan['vlanif'], $vlan['if'], $vlan['tag'], $vlan['pcp'], $vlan['proto']);
        } else {
            /* option 3: only pcp or proto changed, which can be altered instantly */
            if ($vlan['pcp'] != $cvlan['pcp']) {
                legacy_vlan_pcp($vlan['vlanif'], $vlan['pcp']);
            }
            if ($vlan['proto'] != $cvlan['proto']) {
                legacy_vlan_proto($vlan['vlanif'], $vlan['proto']);
            }
        }
    }
    unset($all_vlans[$device]);
}

/* configure new */
foreach (array_keys($all_vlans) as $device) {
    interfaces_vlan_configure($device);
}

ifgroup_setup();
