#!/usr/bin/env python3
# Copyright (c) 2022 Arista Networks, Inc.  All rights reserved.
# Arista Networks, Inc. Confidential and Proprietary.

import os
import sqlite3
import subprocess
import sys

from EosVersion import VersionInfo
import Logging
import CorrectRpmDbLogs

BOOT_OPTIMIZATION_ERR = "Unable to determine booted optimization"
RPM_CORRECTION_FILE_ERR = "Unable to read RPM database correction file"
RPM_DB_CORRECTION_PATH = "/etc/RpmDbCorrection"
VERSION_PATH = "/etc/swi-version"

def getBootedOptimization():
   try:
      with open( VERSION_PATH ) as f:
         info = f.read()
         return VersionInfo( None, versionFile=info ).swiOptimization()
   except: # pylint: disable=bare-except
      Logging.log( CorrectRpmDbLogs.SYS_BOOT_RPM_DATABASE_UPDATE_ERROR,
                   BOOT_OPTIMIZATION_ERR )
      sys.exit( 1 )

def getPkgsToRemove( optim ):
   cmdOut = subprocess.check_output( [ "removeUnusedPackages", "-l" ],
                                     text=True )
   pkgs = set( cmdOut.splitlines() )

   # Superset optimizations don't have db correction files.
   # SWI_OPTIMIZATION=None for non-SWIM images such as CloudEOS.swi
   if optim.startswith( "Default" ) or optim == "None":
      return pkgs

   optimRpmDbCorrectionPath = os.path.join( RPM_DB_CORRECTION_PATH, optim )
   try:
      with open( optimRpmDbCorrectionPath ) as f:
         pkgs.update( f.read().splitlines() )
   except: # pylint: disable=bare-except
      Logging.log( CorrectRpmDbLogs.SYS_BOOT_RPM_DATABASE_UPDATE_ERROR,
                   RPM_CORRECTION_FILE_ERR )
      sys.exit( 1 )

   return pkgs

def maybeCorrectRpmDb():
   if not os.path.exists( RPM_DB_CORRECTION_PATH ):
      return

   optim = getBootedOptimization()

   pkgsToRemove = getPkgsToRemove( optim )

   cmdOut = subprocess.check_output( [ "rpm", "-qa", "--queryformat=%{NAME}\n" ],
                                     text=True )
   pkgsInstalled = set( cmdOut.splitlines() )

   pkgsToRemove.intersection_update( pkgsInstalled )

   if not pkgsToRemove:
      return

   subprocess.check_call(
      [ "rpm", "-e", "--nodeps" ] + list( pkgsToRemove ),
      stdout=subprocess.DEVNULL,
      stderr=subprocess.DEVNULL
   )

   # el9 uses sqlite and has WAL enabled, which can result in a huge
   # rpmdb.sqlite-wal file (400MB in some cases) when deleting a lot of
   # RPMs at once. So truncate the file after the RPM removals here.
   if os.path.exists( "/var/lib/rpm/rpmdb.sqlite" ):
      con = sqlite3.connect( "/var/lib/rpm/rpmdb.sqlite" )
      cur = con.cursor()
      cur.execute( "PRAGMA wal_checkpoint(TRUNCATE);" )
      con.commit()
      con.close()

   # WAL maybe 0 or may not exist - ensure it's there
   subprocess.check_call(
      [ 'rpm', '-qi', '--quiet', 'rpm' ],
      stdout=subprocess.DEVNULL,
      stderr=subprocess.DEVNULL
   )


if __name__ == '__main__':
   try:
      maybeCorrectRpmDb()
   except Exception as e: # pylint: disable=broad-except
      Logging.log( CorrectRpmDbLogs.SYS_BOOT_RPM_DATABASE_UPDATE_ERROR, e )
      sys.exit( 1 )
