001/* 002 * VM-Operator 003 * Copyright (C) 2023 Michael N. Lipp 004 * 005 * This program is free software: you can redistribute it and/or modify 006 * it under the terms of the GNU Affero General Public License as 007 * published by the Free Software Foundation, either version 3 of the 008 * License, or (at your option) any later version. 009 * 010 * This program is distributed in the hope that it will be useful, 011 * but WITHOUT ANY WARRANTY; without even the implied warranty of 012 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 013 * GNU Affero General Public License for more details. 014 * 015 * You should have received a copy of the GNU Affero General Public License 016 * along with this program. If not, see <https://www.gnu.org/licenses/>. 017 */ 018 019package org.jdrupes.vmoperator.runner.qemu; 020 021import java.io.IOException; 022import java.nio.file.Files; 023import java.nio.file.Path; 024import java.util.Objects; 025import java.util.logging.Level; 026import org.jdrupes.vmoperator.runner.qemu.commands.QmpSetDisplayPassword; 027import org.jdrupes.vmoperator.runner.qemu.commands.QmpSetPasswordExpiry; 028import org.jdrupes.vmoperator.runner.qemu.events.ConfigureQemu; 029import org.jdrupes.vmoperator.runner.qemu.events.MonitorCommand; 030import org.jdrupes.vmoperator.runner.qemu.events.RunnerStateChange.RunState; 031import org.jgrapes.core.Channel; 032import org.jgrapes.core.Component; 033import org.jgrapes.core.annotation.Handler; 034import org.jgrapes.util.events.FileChanged; 035import org.jgrapes.util.events.WatchFile; 036 037/** 038 * The Class DisplayController. 039 */ 040@SuppressWarnings("PMD.DataflowAnomalyAnalysis") 041public class DisplayController extends Component { 042 043 public static final String DISPLAY_PASSWORD_FILE = "display-password"; 044 public static final String PASSWORD_EXPIRY_FILE = "password-expiry"; 045 private String currentPassword; 046 private String protocol; 047 private final Path configDir; 048 049 /** 050 * Instantiates a new Display controller. 051 * 052 * @param componentChannel the component channel 053 * @param configDir 054 */ 055 @SuppressWarnings({ "PMD.AssignmentToNonFinalStatic", 056 "PMD.ConstructorCallsOverridableMethod" }) 057 public DisplayController(Channel componentChannel, Path configDir) { 058 super(componentChannel); 059 this.configDir = configDir; 060 fire(new WatchFile(configDir.resolve(DISPLAY_PASSWORD_FILE))); 061 } 062 063 /** 064 * On configure qemu. 065 * 066 * @param event the event 067 */ 068 @Handler 069 public void onConfigureQemu(ConfigureQemu event) { 070 if (event.runState() == RunState.TERMINATING) { 071 return; 072 } 073 protocol 074 = event.configuration().vm.display.spice != null ? "spice" : null; 075 updatePassword(); 076 } 077 078 /** 079 * Watch for changes of the password file. 080 * 081 * @param event the event 082 */ 083 @Handler 084 @SuppressWarnings("PMD.EmptyCatchBlock") 085 public void onFileChanged(FileChanged event) { 086 if (event.path().equals(configDir.resolve(DISPLAY_PASSWORD_FILE))) { 087 updatePassword(); 088 } 089 } 090 091 @SuppressWarnings("PMD.DataflowAnomalyAnalysis") 092 private void updatePassword() { 093 if (protocol == null) { 094 return; 095 } 096 if (setDisplayPassword()) { 097 setPasswordExpiry(); 098 } 099 } 100 101 private boolean setDisplayPassword() { 102 String password; 103 Path dpPath = configDir.resolve(DISPLAY_PASSWORD_FILE); 104 if (dpPath.toFile().canRead()) { 105 logger.finer(() -> "Found display password"); 106 try { 107 password = Files.readString(dpPath); 108 } catch (IOException e) { 109 logger.log(Level.WARNING, e, () -> "Cannot read display" 110 + " password: " + e.getMessage()); 111 return false; 112 } 113 } else { 114 logger.finer(() -> "No display password"); 115 return false; 116 } 117 118 if (Objects.equals(this.currentPassword, password)) { 119 return true; 120 } 121 this.currentPassword = password; 122 logger.fine(() -> "Updating display password"); 123 fire(new MonitorCommand(new QmpSetDisplayPassword(protocol, password))); 124 return true; 125 } 126 127 private void setPasswordExpiry() { 128 Path pePath = configDir.resolve(PASSWORD_EXPIRY_FILE); 129 if (!pePath.toFile().canRead()) { 130 return; 131 } 132 logger.finer(() -> "Found expiry time"); 133 String expiry; 134 try { 135 expiry = Files.readString(pePath); 136 } catch (IOException e) { 137 logger.log(Level.WARNING, e, () -> "Cannot read expiry" 138 + " time: " + e.getMessage()); 139 return; 140 } 141 logger.fine(() -> "Updating expiry time"); 142 fire(new MonitorCommand(new QmpSetPasswordExpiry(protocol, expiry))); 143 } 144 145}