package ru.thiflict import kotlinx.coroutines.* import ru.thiflict.DebugMode.DEBUG import java.io.File import kotlin.system.exitProcess // Конфигурация GPIO (BCM номера) object PinConfig { const val GREEN_LED = 17 const val YELLOW_LED = 27 const val RED_LED = 22 const val BUTTON = 24 } object DebugMode { const val DEBUG = true } // Состояния системы enum class SystemState { IDLE, SCANNING, CLEAN, INFECTED, ERROR, INIT } class USBVirusScanner { private var currentState = SystemState.INIT private var scanJob: Job? = null private var indicationJob: Job? = null private var isRunning = true private lateinit var greenLed: GPIOController private lateinit var yellowLed: GPIOController private lateinit var redLed: GPIOController private lateinit var buttonReader: ButtonListener private val scope = CoroutineScope(Dispatchers.IO + SupervisorJob()) fun initialize() { println(if (DEBUG) "Инициализация..." else "") try { // Проверяем доступность gpiod checkLibgpiod() greenLed = GPIOController(PinConfig.GREEN_LED) yellowLed = GPIOController(PinConfig.YELLOW_LED) redLed = GPIOController(PinConfig.RED_LED) // Выключаем все светодиоды greenLed.setValue(0) yellowLed.setValue(0) redLed.setValue(0) // Запускаем индикацию состояния startStateIndication() currentState = SystemState.IDLE Thread.sleep(1000) println("Инициализация завершена.") currentState = SystemState.IDLE } catch (e: Exception) { currentState = SystemState.ERROR println("❌ Ошибка инициализации: ${e.message}") e.printStackTrace() throw e } } private fun checkLibgpiod() { try { val process = ProcessBuilder("gpiodetect").start() val exitCode = process.waitFor() if (exitCode != 0) { throw Exception("gpiodetect не найден") } val output = process.inputStream.bufferedReader().readText() println("✅ libgpiod найден") if (output.isNotBlank()) { println("Доступные чипы: ${output.trim()}") } } catch (e: Exception) { throw Exception("libgpiod Не установлен!") } } fun buttonPress(defaultButtonState: Boolean): Boolean { var finalButtonState = defaultButtonState println(if (DEBUG) "Начало finalButtonState: $finalButtonState" else "") val buttonListener = ButtonListener() val initialState = buttonListener.startListening { isPressed -> if (isPressed) { finalButtonState = true } } println(if (DEBUG) "finalButtonState: $finalButtonState" else "") return finalButtonState } private fun startStateIndication() { indicationJob = scope.launch { while (isRunning) { when (currentState) { SystemState.INIT -> { greenLed.setValue(1) yellowLed.setValue(1) redLed.setValue(1) delay(200) greenLed.setValue(0) yellowLed.setValue(0) redLed.setValue(0) delay(200) } SystemState.IDLE -> { // Зелёный медленно мигает blinkLed(greenLed, 1000) yellowLed.setValue(0) redLed.setValue(0) } SystemState.SCANNING -> { // Жёлтый быстро мигает greenLed.setValue(0) blinkLed(yellowLed, 150) redLed.setValue(0) } SystemState.CLEAN -> { // Зелёный горит greenLed.setValue(1) yellowLed.setValue(0) redLed.setValue(0) delay(100) } SystemState.INFECTED -> { // Красный горит greenLed.setValue(0) yellowLed.setValue(0) redLed.setValue(1) delay(100) } SystemState.ERROR -> { // Красный мигает greenLed.setValue(0) yellowLed.setValue(0) blinkLed(redLed, 500) } } } } } private suspend fun blinkLed(led: GPIOController, intervalMs: Long) { led.setValue(1) delay(intervalMs / 2) led.setValue(0) delay(intervalMs / 2) } fun startScan() { if (scanJob?.isActive == true) { println("Сканирование уже выполняется") return } scanJob = scope.launch { currentState = SystemState.SCANNING println("=== Начало сканирования ===") try { // Поиск USB-устройств val usbDevices = findUSBDevices() if (usbDevices.isEmpty()) { println("Устройства не найдены") currentState = SystemState.ERROR delay(5000) currentState = SystemState.IDLE return@launch } println("📀 Найдены USB-устройства:") usbDevices.forEach { device -> println(" - ${device.mountPoint}") } println() var hasVirus = false val scanResults = mutableListOf() for (device in usbDevices) { println("🔍 Сканирование: ${device.mountPoint}") val result = scanWithClamAV(device.mountPoint) scanResults.add(result) if (result.isInfected) { hasVirus = true println("⚠️ ВНИМАНИЕ! Найдены угрозы!") result.infectedFiles.take(10).forEach { file -> println(" 🦠 ${file.take(100)}") } if (result.infectedFiles.size > 10) { println(" ... и ещё ${result.infectedFiles.size - 10} файлов") } } else if (result.error != null) { println("❌ Ошибка сканирования: ${result.error}") } else { println("✅ Сканирование завершено: угроз не найдено") } println() } // Сохраняем лог saveScanLog(scanResults) currentState = if (hasVirus) { SystemState.INFECTED } else { SystemState.CLEAN } val message = if (hasVirus) { "🦠 Найдены вирусы! Красный LED горит" } else { "✅ Флешка чиста! Зелёный LED горит" } println(message) // Держим состояние 5 секунд delay(5000) currentState = SystemState.IDLE } catch (e: Exception) { println("❌ Критическая ошибка: ${e.message}") e.printStackTrace() currentState = SystemState.ERROR delay(5000) currentState = SystemState.IDLE } println("=== Сканирование завершено ===\n") } } private fun findUSBDevices(): List { val devices = mutableListOf() try { // Проверяем /media директории val mediaDirs = listOf("/media", "/media/pi") for (mediaDirPath in mediaDirs) { val mediaDir = File(mediaDirPath) if (mediaDir.exists() && mediaDir.isDirectory) { mediaDir.listFiles()?.forEach { dir -> if (dir.isDirectory && dir.canRead()) { // Проверяем, что это не системная директория val isSystemDir = dir.name.startsWith(".") || dir.name == "root" || dir.name == "boot" if (!isSystemDir && devices.none { it.mountPoint == dir.absolutePath }) { // Дополнительная проверка: есть ли файлы в директории val hasFiles = dir.listFiles()?.isNotEmpty() == true if (hasFiles) { devices.add(USBDevice("usb", dir.absolutePath)) } } } } } } // Проверяем /proc/mounts для дополнительных точек монтирования val mountsFile = File("/proc/mounts") if (mountsFile.exists()) { mountsFile.readLines().forEach { line -> val parts = line.split(" ") if (parts.size >= 2) { val mountPoint = parts[1] if ((mountPoint.startsWith("/media")) && File(mountPoint).exists() && devices.none { it.mountPoint == mountPoint }) { devices.add(USBDevice("auto", mountPoint)) } } } } } catch (e: Exception) { println("Ошибка при поиске USB-устройств: ${e.message}") } return devices.distinctBy { it.mountPoint } } private fun scanWithClamAV(path: String): ScanResult { val infectedFiles = mutableListOf() val virusNames = mutableMapOf() try { // Проверяем установлен ли ClamAV val checkProcess = ProcessBuilder("which", "clamscan").start() val checkExitCode = checkProcess.waitFor() if (checkExitCode != 0) { return ScanResult( isInfected = false, infectedFiles = emptyList(), virusNames = emptyMap(), exitCode = -1, error = "ClamAV не установлен. Установите: sudo apt install clamav" ) } // Проверяем существование пути val pathFile = File(path) if (!pathFile.exists() || !pathFile.isDirectory) { return ScanResult( isInfected = false, infectedFiles = emptyList(), virusNames = emptyMap(), exitCode = -1, error = "Путь не существует или не является директорией: $path" ) } println(" Запуск clamscan (это может занять некоторое время)...") // Запускаем ClamAV val processBuilder = ProcessBuilder( "clamscan", "-r", "--stdout", "--infected", "--max-filesize=70M", "--max-scansize=150M", path ) processBuilder.redirectErrorStream(true) val process = processBuilder.start() val output = process.inputStream.bufferedReader().readText() val exitCode = process.waitFor() // Парсим вывод output.lines().forEach { line -> if (line.contains(" FOUND")) { val colonIndex = line.indexOf(": ") if (colonIndex > 0) { val fileName = line.substring(0, colonIndex) val virusInfo = line.substringAfter(" FOUND").trim() infectedFiles.add(fileName) virusNames[fileName] = virusInfo } } } val syncProccessBuilder = ProcessBuilder("sync") syncProccessBuilder.start() process.waitFor() val umountProcessBulder = ProcessBuilder("umount", "/dev/sda", "-f") umountProcessBulder.start() process.waitFor() return ScanResult( isInfected = infectedFiles.isNotEmpty(), infectedFiles = infectedFiles, virusNames = virusNames, exitCode = exitCode, error = if (exitCode != 0 && exitCode != 1) "ClamAV вернул код ошибки: $exitCode" else null ) } catch (e: Exception) { println(" Ошибка при выполнении clamscan: ${e.message}") return ScanResult( isInfected = false, infectedFiles = emptyList(), virusNames = emptyMap(), exitCode = -1, error = e.message ) } } private fun saveScanLog(results: List) { try { val logDir = File("/home/${System.getProperty("user.name")}/usb-scanner-logs") if (!logDir.exists()) { logDir.mkdirs() } val timestamp = System.currentTimeMillis() val logFile = File(logDir, "scan_$timestamp.log") logFile.bufferedWriter().use { writer -> writer.write("=== USB Virus Scanner Log ===\n") writer.write("Time: ${java.time.LocalDateTime.now()}\n\n") results.forEachIndexed { index, result -> writer.write("Device ${index + 1}:\n") if (result.isInfected) { writer.write("Status: INFECTED\n") writer.write("Found ${result.infectedFiles.size} infected files:\n") result.infectedFiles.take(20).forEach { file -> writer.write(" $file: ${result.virusNames[file]}\n") } if (result.infectedFiles.size > 20) { writer.write(" ... and ${result.infectedFiles.size - 20} more files\n") } } else if (result.error != null) { writer.write("Status: ERROR\n") writer.write("Error: ${result.error}\n") } else { writer.write("Status: CLEAN\n") } writer.write("\n") } } println("📝 Лог сохранён: ${logFile.absolutePath}") } catch (e: Exception) { println("Ошибка сохранения лога: ${e.message}") } } fun shutdown() { println("\nЗавершение работы...") isRunning = false indicationJob?.cancel() scanJob?.cancel() scope.cancel() // Выключаем все светодиоды try { if (::greenLed.isInitialized) { greenLed.setValue(0) yellowLed.setValue(0) redLed.setValue(0) } } catch (e: Exception) { // Игнорируем ошибки при закрытии } println("✅ Система остановлена") } } data class USBDevice( val device: String, val mountPoint: String ) data class ScanResult( val isInfected: Boolean, val infectedFiles: List, val virusNames: Map, val exitCode: Int, val error: String? = null ) fun main() { println(if (DebugMode.DEBUG) "Зелёный - ${PinConfig.GREEN_LED}" else "") println(if (DebugMode.DEBUG) "Жёлтый - ${PinConfig.YELLOW_LED}" else "") println(if (DebugMode.DEBUG) "Красный - ${PinConfig.RED_LED}" else "") println(if (DebugMode.DEBUG) "Кнопка - ${PinConfig.BUTTON}" else "") val defaultButtonState = false val scanner = USBVirusScanner() Runtime.getRuntime().addShutdownHook(Thread { scanner.shutdown() }) try { scanner.initialize() val buttonListener = ButtonListener() buttonListener.addClickListener { event -> scanner.startScan() } buttonListener.startListening() println("═══════════════════════════════════════") println(" USB Virus Scanner готов к работе") println("═══════════════════════════════════════") println("📌 Вставьте флешку и нажмите кнопку") println("📌 Зелёный LED мигает - режим ожидания") println("📌 Жёлтый LED мигает - идёт проверка") println("📌 Зелёный LED горит - флешка чиста") println("📌 Красный LED горит - найдены вирусы") println("📌 Для выхода нажмите Ctrl+C\n") while (true) { Thread.sleep(1000) } } catch (e: Exception) { println("❌ Критическая ошибка: ${e.message}") e.printStackTrace() scanner.shutdown() exitProcess(1) } }