/************************************************************************* * Copyright 2009-2012 Eucalyptus Systems, Inc. * * 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 3 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, see http://www.gnu.org/licenses/. * * Please contact Eucalyptus Systems, Inc., 6755 Hollister Ave., Goleta * CA 93117, USA or visit http://www.eucalyptus.com/licenses/ if you need * additional information or have any questions. * * This file may incorporate work covered under the following copyright * and permission notice: * * Software License Agreement (BSD License) * * Copyright (c) 2008, Regents of the University of California * All rights reserved. * * Redistribution and use of this software in source and binary forms, * with or without modification, are permitted provided that the * following conditions are met: * * Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * 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 BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "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 * COPYRIGHT OWNER OR CONTRIBUTORS 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. USERS OF THIS SOFTWARE ACKNOWLEDGE * THE POSSIBLE PRESENCE OF OTHER OPEN SOURCE LICENSED MATERIAL, * COPYRIGHTED MATERIAL OR PATENTED MATERIAL IN THIS SOFTWARE, * AND IF ANY SUCH MATERIAL IS DISCOVERED THE PARTY DISCOVERING * IT MAY INFORM DR. RICH WOLSKI AT THE UNIVERSITY OF CALIFORNIA, * SANTA BARBARA WHO WILL THEN ASCERTAIN THE MOST APPROPRIATE REMEDY, * WHICH IN THE REGENTS' DISCRETION MAY INCLUDE, WITHOUT LIMITATION, * REPLACEMENT OF THE CODE SO IDENTIFIED, LICENSING OF THE CODE SO * IDENTIFIED, OR WITHDRAWAL OF THE CODE CAPABILITY TO THE EXTENT * NEEDED TO COMPLY WITH ANY SUCH LICENSES OR RIGHTS. ************************************************************************/ package edu.ucsb.eucalyptus.util; import java.io.File; import java.util.Arrays; import java.util.List; import java.util.concurrent.Callable; import java.util.concurrent.Semaphore; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicBoolean; import org.apache.log4j.Logger; import com.eucalyptus.configurable.ConfigurableClass; import com.eucalyptus.configurable.ConfigurableField; import com.eucalyptus.configurable.ConfigurableProperty; import com.eucalyptus.configurable.ConfigurablePropertyException; import com.eucalyptus.configurable.PropertyChangeListener; import com.eucalyptus.configurable.PropertyChangeListeners; import com.eucalyptus.empyrean.Empyrean; import com.eucalyptus.system.Threads; import com.eucalyptus.util.EucalyptusCloudException; import com.eucalyptus.util.Exceptions; import com.google.common.base.Joiner; import com.google.common.base.Objects; import com.google.common.base.Predicate; import com.google.common.base.Predicates; import com.google.common.base.Supplier; import com.google.common.base.Suppliers; import com.google.common.collect.Iterables; import com.google.common.collect.Lists; import com.google.common.primitives.Primitives; @ConfigurableClass( root = "system.exec", description = "Parameters controlling the usage of system processes." ) public class SystemUtil { private static Logger LOG = Logger.getLogger( SystemUtil.class ); @ConfigurableField( description = "Maximum number of concurrent processes which match any of the patterns in system.exec.restricted_concurrent_ops.", initial = "2", changeListener = RestrictedOpChangeListener.class ) public static Integer MAX_RESTRICTED_CONCURRENT_OPS = 2; @ConfigurableField( description = "Comma-separated list of commands which are restricted by system.exec.max_restricted_concurrent_ops.", initial = "dd,gunzip,tar" ) public static String RESTRICTED_CONCURRENT_OPS = "dd,gunzip,tar"; @ConfigurableField( description = "Size of IO chunks for streaming IO", initial = "102400", changeListener = PropertyChangeListeners.IsPositiveInteger.class) public static Integer IO_CHUNK_SIZE = 102400; private static final AtomicBoolean acquiringPermits = new AtomicBoolean( false ); private static final Semaphore concurrentOps = new Semaphore( MAX_RESTRICTED_CONCURRENT_OPS ); public static class RestrictedOpChangeListener implements PropertyChangeListener<String> { @Override public void fireChange( ConfigurableProperty t, String newValueString ) throws ConfigurablePropertyException { int value = Integer.parseInt( t.getValue( ) ); int newValue = Integer.parseInt( newValueString ); if ( newValue < 1 ) { throw new ConfigurablePropertyException( t.getDisplayName( ) + " must be greater than or equal to 1." ); } final int permitDelta = newValue - value; LOG.debug( "Adjust number of RESTRICTED_CONCURRENT_OPS to be: " + newValue + " from " + value + "@" + concurrentOps.availablePermits( ) + "/" + concurrentOps.getQueueLength( ) + " (" + permitDelta + ")" ); if ( permitDelta > 0 ) { concurrentOps.release( permitDelta ); } else if ( permitDelta == 0 ) { //NOOP. } else if ( permitDelta < 0 ) { //need to remove permits from the semaphore. this involves calling .acquire() and may block if they are in use. do it in a thread. if ( acquiringPermits.compareAndSet( false, true ) ) { Threads.lookup( Empyrean.class ).submit( new Runnable( ) { @Override public void run ( ) { for( int i = 0 ; i < Math.abs( permitDelta ); i++ ) { try { concurrentOps.acquire( ); } catch ( InterruptedException ex ) { Exceptions.maybeInterrupted( ex ); break; } } acquiringPermits.set( false ); } } ); } else { throw new ConfigurablePropertyException( t.getDisplayName( ) + " is still draining permits from a previous request to change the settings. Please wait." ); } } } } private static final Supplier<Predicate<CharSequence>> restrictionFilter = Suppliers.memoizeWithExpiration( new Supplier<Predicate<CharSequence>>( ) { @Override public Predicate<CharSequence> get( ) { List<Predicate<CharSequence>> restrictionFilter = Lists.newArrayList( ); for ( String patternString : Arrays.asList( RESTRICTED_CONCURRENT_OPS.split( "," ) ) ) { Predicate<CharSequence> predicate = Predicates.containsPattern( patternString.trim( ) ); restrictionFilter.add( predicate ); } return Predicates.or( restrictionFilter ); } }, 30l, TimeUnit.SECONDS ); private static boolean maybeRestrictedOp(String... command) { if ( Iterables.any( Arrays.asList( command ).subList( 0, Math.min( command.length, 2 ) ), restrictionFilter.get( ) ) ) { try { concurrentOps.acquire( ); LOG.debug( "Started concurrent op: " + Joiner.on( " " ).join( command ) ); return true; } catch ( Exception ex ) { return false; } } else { return false; } } private static void releaseRestrictedOp(String... command) { concurrentOps.release( ); LOG.debug( "Ended concurrent op: " + Joiner.on( " " ).join( command ) ); } public static String run(String[] command) { return run(command, false); } /** * Executes an command and return its output. * @param command command to execute * @param failurePossible pass true if failure to execute command (an exit code != 0) is a valid outcome * @return */ public static String run(String[] command, boolean failurePossible) { boolean hasTicket = maybeRestrictedOp( command ); try { String commandString = ""; for(String part : command) { commandString += part + " "; } LOG.debug("Running command: " + commandString); Runtime rt = Runtime.getRuntime(); Process proc = rt.exec(command); StreamConsumer error = new StreamConsumer(proc.getErrorStream()); StreamConsumer output = new StreamConsumer(proc.getInputStream()); error.start(); output.start(); int returnValue = proc.waitFor(); output.join(); if(returnValue != 0) { if (failurePossible) return ""; else throw new EucalyptusCloudException(error.getReturnValue()); } return output.getReturnValue(); } catch (Exception t) { LOG.error(t, t); } finally { if ( hasTicket ) releaseRestrictedOp( command ); } return ""; } public static int runAndGetCode(String[] command) { boolean hasTicket = maybeRestrictedOp( command ); try { String commandString = ""; for(String part : command) { commandString += part + " "; } LOG.debug("Running command: " + commandString); Runtime rt = Runtime.getRuntime(); Process proc = rt.exec(command); StreamConsumer error = new StreamConsumer(proc.getErrorStream()); StreamConsumer output = new StreamConsumer(proc.getInputStream()); error.start(); output.start(); int returnValue = proc.waitFor(); return returnValue; } catch (Exception t) { LOG.error(t, t); } finally { if ( hasTicket ) releaseRestrictedOp( command ); } return -1; } public static class CommandOutput { public int returnValue; public String output; public String error; public CommandOutput(int returnValue, String output, String error) { this.returnValue = returnValue; this.output = output; this.error = error; } public String toString() { StringBuilder sb = new StringBuilder(); sb.append('[').append(returnValue).append(']').append('\n'); if (output != null) { sb.append(output); } if (error != null) { sb.append(error); } return sb.toString(); } /** * True if command returned non-zero return code * @return */ public boolean failed() { return this.returnValue != 0; } } public static CommandOutput runWithRawOutput(String[] command) throws Exception { //System.out.println(Joiner.on(" ").skipNulls().join(command)); Runtime rt = Runtime.getRuntime(); Process proc = rt.exec(command); StreamConsumer error = new StreamConsumer(proc.getErrorStream()); StreamConsumer output = new StreamConsumer(proc.getInputStream()); error.start(); output.start(); int returnValue = proc.waitFor(); output.join(); error.join(); return new CommandOutput(returnValue, output.getReturnValue(), error.getReturnValue()); } public static void shutdownWithError(String errorMessage) { LOG.fatal(errorMessage); throw new IllegalStateException(errorMessage); } public static void setEucaReadWriteOnly(String filePath) throws EucalyptusCloudException { File file = new File(filePath); try { file.setReadable(false, false); file.setWritable(false, false); file.setExecutable(false, false); file.setReadable(true, true); file.setWritable(true, true); file.setExecutable(true, true); } catch(SecurityException ex) { LOG.error(ex); throw new EucalyptusCloudException(ex); } } }