/* * RHQ Management Platform * Copyright (C) 2010 Red Hat, Inc. * All rights reserved. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation version 2 of the License. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */ package org.rhq.bundle.ant.type; import java.io.File; import java.io.IOException; import java.util.Arrays; import java.util.HashSet; import java.util.Set; import java.util.TreeSet; import org.apache.tools.ant.BuildException; import org.apache.tools.ant.taskdefs.Chmod; import org.apache.tools.ant.taskdefs.Copy; import org.apache.tools.ant.taskdefs.Execute; import org.apache.tools.ant.taskdefs.optional.unix.Symlink; /** * An Ant task that installs a system startup/shutdown service. Currently only Red Hat Linux versions are supported. * * @author Ian Springer */ public class SystemServiceType extends AbstractBundleType { private static final String OS_NAME = System.getProperty("os.name"); private static final File REDHAT_RELEASE_FILE = new File("/etc/redhat-release"); private static final Set<Character> REDHAT_RUN_LEVELS = new HashSet<Character>(); static { for (char c = '0'; c <= '6'; c++) { REDHAT_RUN_LEVELS.add(c); } // TODO: Add 's' and/or 'S' depending on the flavor of UNIX. } private static final File INIT_DIR = new File("/etc/init.d"); private static final File SYSCONFIG_DIR = new File("/etc/sysconfig"); private static final File DEFAULT_ROOT = new File("/"); private String name; private File scriptFile; private File configFile; private String scriptFileName; private String configFileName; private boolean overwriteScript; private boolean overwriteConfig; private boolean overwriteLinks = true; private File root = DEFAULT_ROOT; private String startLevels; /** * An integer from 0-99 indicating the service's start order - services with a lower priority number are started * before services with a higher priority number. */ private Byte startPriority; /** * An integer from 0-99 indicating the service's stop order - services with a lower priority number are stopped * before services with a higher priority number. */ private Byte stopPriority; private Set<Character> startLevelChars; private Set<Character> stopLevelChars; private File scriptDestFile; private File configDestFile; public void validate() throws BuildException { validateAttributes(); this.scriptDestFile = new File(getInitDir(), this.name); this.configDestFile = new File(getSysConfigDir(), this.name); } public void init() throws BuildException { if (!"Linux".equals(OS_NAME) || !REDHAT_RELEASE_FILE.exists()) { throw new BuildException("The system-service element is only supported on Red Hat Linux systems."); } if (!this.scriptFile.exists() || this.scriptFile.isDirectory()) { throw new BuildException("The 'scriptFile' attribute must be set to the path of an existing regular file."); } if (this.configFile != null && (!this.configFile.exists() || this.configFile.isDirectory())) { throw new BuildException("The 'configFile' attribute must be set to the path of an existing regular file."); } } public void install() throws BuildException { // Install the config file if one was provided (e.g. /etc/sysconfig/named). if (this.configFile != null) { File sysconfigDir = getSysConfigDir(); if (!sysconfigDir.exists()) { sysconfigDir.mkdirs(); } if (!sysconfigDir.canWrite()) { throw new BuildException(sysconfigDir + " directory is not writeable."); } // Don't copy the file ourselves - let our parent DeploymentUnitType handle it, so the deployment metadata // (i.e. MD5) can be calculated and saved. //copyFile(this.configFile, this.configDestFile, this.overwriteConfig); setPermissions(this.configDestFile, "644"); } // Install the script itself (e.g. /etc/init.d/named). File initDir = getInitDir(); if (!initDir.exists()) { initDir.mkdirs(); } if (!initDir.canWrite()) { throw new BuildException(initDir + " directory is not writeable."); } getProject().log("Installing service script " + this.scriptDestFile + "..."); // Don't copy the file ourselves - let our parent DeploymentUnitType handle it, so the deployment metadata // (i.e. MD5) can be calculated and saved. //copyFile(this.scriptFile, scriptDestFile, this.overwriteScript); setPermissions(this.scriptDestFile, "755"); // Create the symlinks in the rcX.d dirs (e.g. /etc/rc3.d/S24named -> ../init.d/named) createScriptSymlinks(this.scriptDestFile, this.startPriority, this.startLevelChars, 'S'); createScriptSymlinks(this.scriptDestFile, this.stopPriority, this.stopLevelChars, 'K'); } private File getInitDir() { return new File(this.root, INIT_DIR.getPath().substring(1)); } public File getScriptDestFile() { return this.scriptDestFile; } private File getSysConfigDir() { return new File(this.root, SYSCONFIG_DIR.getPath().substring(1)); } public File getConfigDestFile() { return this.configDestFile; } public void start() throws BuildException { File scriptFile = getScriptDestFile(); String[] commandLine = { scriptFile.getAbsolutePath(), "start" }; try { executeCommand(commandLine); } catch (IOException e) { throw new BuildException("Failed to start " + this.name + " system service via command [" + Arrays.toString(commandLine) + "].", e); } } public void stop() throws BuildException { File scriptFile = getScriptDestFile(); String[] commandLine = { scriptFile.getAbsolutePath(), "stop" }; try { executeCommand(commandLine); } catch (IOException e) { throw new BuildException("Failed to stop " + this.name + " system service via command [" + Arrays.toString(commandLine) + "].", e); } } public void uninstall() throws BuildException { // TODO } public String getName() { return name; } public void setName(String name) { this.name = name; } public String getScriptFileName() { return scriptFileName; } public File getScriptFile() { return scriptFile; } public void setScriptFile(String scriptFile) { File file = new File(scriptFile); if (file.isAbsolute()) { throw new BuildException("Path specified by 'scriptFile' attribute (" + scriptFile + ") is not relative - it must be a relative path, relative to the Ant basedir."); } this.scriptFileName = scriptFile; this.scriptFile = getProject().resolveFile(scriptFile); } public String getConfigFileName() { return configFileName; } public File getConfigFile() { return configFile; } public void setConfigFile(String configFile) { File file = new File(configFile); if (file.isAbsolute()) { throw new BuildException("Path specified by 'configFile' attribute (" + configFile + ") is not relative - it must be a relative path, relative to the Ant basedir."); } this.configFileName = configFile; this.configFile = getProject().resolveFile(configFile); } public boolean isOverwriteScript() { return overwriteScript; } public void setOverwriteScript(boolean overwriteScript) { this.overwriteScript = overwriteScript; } public boolean isOverwriteConfig() { return overwriteConfig; } public void setOverwriteConfig(boolean overwriteConfig) { this.overwriteConfig = overwriteConfig; } public boolean isOverwriteLinks() { return overwriteLinks; } public void setOverwriteLinks(boolean overwriteLinks) { this.overwriteLinks = overwriteLinks; } public String getStartLevels() { return startLevels; } public void setStartLevels(String startLevels) { this.startLevels = startLevels; } public byte getStartPriority() { return startPriority; } public void setStartPriority(byte startPriority) { this.startPriority = startPriority; } public byte getStopPriority() { return stopPriority; } public void setStopPriority(byte stopPriority) { this.stopPriority = stopPriority; } public File getRoot() { return root; } public void setRoot(File root) { this.root = root; } /** * Ensure we have a consistent and legal set of attributes, and set * any internal flags necessary based on different combinations * of attributes. * * @throws BuildException if an error occurs */ protected void validateAttributes() throws BuildException { if (this.name == null) { throw new BuildException("The 'name' attribute is required."); } if (this.name.length() == 0) { throw new BuildException("The 'name' attribute must have a non-empty value."); } if (this.scriptFile == null) { throw new BuildException("The 'scriptFile' attribute is required."); } if (this.startLevels == null) { throw new BuildException("The 'startLevels' attribute is required."); } if (this.startLevels.length() == 0) { throw new BuildException("The 'startLevels' attribute must have a non-empty value."); } this.startLevelChars = parseLevels(this.startLevels); this.stopLevelChars = new TreeSet<Character>(); for (char level : REDHAT_RUN_LEVELS) { if (!this.startLevelChars.contains(level)) { this.stopLevelChars.add(level); } } if (this.startPriority == null) { throw new BuildException("The 'startPriority' attribute is required."); } if (this.startPriority < 0 || this.startPriority > 99) { throw new BuildException("The 'startPriority' attribute must be >=0 and <= 99."); } if (this.stopPriority == null) { throw new BuildException("The 'stopPriority' attribute is required."); } if (this.stopPriority < 0 || this.stopPriority > 99) { throw new BuildException("The 'startPriority' attribute must be >=0 and <= 99."); } if (!this.root.exists()) { this.root.mkdirs(); if (!this.root.exists()) { throw new BuildException("Failed to create root directory " + this.root + " as specified by 'root' attribute."); } } if (!this.root.isDirectory()) { throw new BuildException("The 'root' attribute must be set to the path of a directory."); } if (!this.root.equals(DEFAULT_ROOT)) { getProject().log("Using root " + this.root + "."); } } private static Set<Character> parseLevels(String levels) { Set<Character> levelChars = new TreeSet<Character>(); String[] tokens = levels.split("[ ]*,[ ]*"); for (String token : tokens) { if (!token.equals("")) { Character level; try { if (token.length() != 1) { throw new Exception(); } level = token.charAt(0); if (!REDHAT_RUN_LEVELS.contains(level)) { throw new Exception(); } } catch (Exception e) { throw new BuildException( "Invalid run level: " + token + " - the 'startLevels' attribute must be a comma-separated list of run levels - the valid levels are " + REDHAT_RUN_LEVELS + "."); } if (levelChars.contains(level)) { throw new BuildException("The 'startLevels' attribute defines run level " + level + " more than once."); } levelChars.add(level); } } return levelChars; } private void createScriptSymlinks(File scriptFile, byte priority, Set<Character> levels, char fileNamePrefix) { String priorityString = String.format("%02d", priority); for (char level : levels) { File rcDir = new File(this.root, "etc/rc" + level + ".d"); if (!rcDir.exists()) { rcDir.mkdirs(); } if (!rcDir.exists()) { throw new BuildException(rcDir + " does not exist."); } if (!rcDir.isDirectory()) { throw new BuildException(rcDir + " exists but is not a directory."); } if (!rcDir.isDirectory()) { throw new BuildException(rcDir + " directory is not writeable."); } File link = new File(rcDir, fileNamePrefix + priorityString + this.name); getProject().log("Creating symbolic link " + link + " referencing " + scriptFile + "..."); createSymlink(scriptFile, link, this.overwriteLinks); } } private void copyFile(File sourceFile, File destFile, boolean overwrite) { Copy copyTask = new Copy(); copyTask.setProject(getProject()); copyTask.init(); copyTask.setFile(sourceFile); copyTask.setTofile(destFile); copyTask.setOverwrite(overwrite); copyTask.execute(); } private void createSymlink(File targetFile, File linkFile, boolean overwrite) { Symlink symlinkTask = new Symlink(); symlinkTask.setProject(getProject()); symlinkTask.init(); symlinkTask.setResource(targetFile.getAbsolutePath()); symlinkTask.setLink(linkFile.getAbsolutePath()); symlinkTask.setOverwrite(overwrite); symlinkTask.execute(); } private int executeCommand(String[] commandLine) throws IOException { Execute executeTask = new Execute(); executeTask.setCommandline(commandLine); return executeTask.execute(); } private void setPermissions(File file, String perms) { Chmod chmodTask = new Chmod(); chmodTask.setProject(getProject()); chmodTask.init(); chmodTask.setFile(file); chmodTask.setPerm(perms); chmodTask.execute(); } }