001 /** 002 * Licensed to the Apache Software Foundation (ASF) under one 003 * or more contributor license agreements. See the NOTICE file 004 * distributed with this work for additional information 005 * regarding copyright ownership. The ASF licenses this file 006 * to you under the Apache License, Version 2.0 (the 007 * "License"); you may not use this file except in compliance 008 * with the License. You may obtain a copy of the License at 009 * 010 * http://www.apache.org/licenses/LICENSE-2.0 011 * 012 * Unless required by applicable law or agreed to in writing, software 013 * distributed under the License is distributed on an "AS IS" BASIS, 014 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 015 * See the License for the specific language governing permissions and 016 * limitations under the License. 017 */ 018 package org.apache.hadoop.fs; 019 020 import java.io.IOException; 021 import java.io.PrintStream; 022 import java.util.ArrayList; 023 import java.util.Arrays; 024 import java.util.LinkedList; 025 026 import org.apache.commons.logging.Log; 027 import org.apache.commons.logging.LogFactory; 028 import org.apache.hadoop.classification.InterfaceAudience; 029 import org.apache.hadoop.conf.Configuration; 030 import org.apache.hadoop.conf.Configured; 031 import org.apache.hadoop.fs.shell.Command; 032 import org.apache.hadoop.fs.shell.CommandFactory; 033 import org.apache.hadoop.fs.shell.FsCommand; 034 import org.apache.hadoop.util.Tool; 035 import org.apache.hadoop.util.ToolRunner; 036 037 /** Provide command line access to a FileSystem. */ 038 @InterfaceAudience.Private 039 public class FsShell extends Configured implements Tool { 040 041 static final Log LOG = LogFactory.getLog(FsShell.class); 042 043 private FileSystem fs; 044 private Trash trash; 045 protected CommandFactory commandFactory; 046 047 private final String usagePrefix = 048 "Usage: hadoop fs [generic options]"; 049 050 /** 051 * Default ctor with no configuration. Be sure to invoke 052 * {@link #setConf(Configuration)} with a valid configuration prior 053 * to running commands. 054 */ 055 public FsShell() { 056 this(null); 057 } 058 059 /** 060 * Construct a FsShell with the given configuration. Commands can be 061 * executed via {@link #run(String[])} 062 * @param conf the hadoop configuration 063 */ 064 public FsShell(Configuration conf) { 065 super(conf); 066 } 067 068 protected FileSystem getFS() throws IOException { 069 if (fs == null) { 070 fs = FileSystem.get(getConf()); 071 } 072 return fs; 073 } 074 075 protected Trash getTrash() throws IOException { 076 if (this.trash == null) { 077 this.trash = new Trash(getConf()); 078 } 079 return this.trash; 080 } 081 082 protected void init() throws IOException { 083 getConf().setQuietMode(true); 084 if (commandFactory == null) { 085 commandFactory = new CommandFactory(getConf()); 086 commandFactory.addObject(new Help(), "-help"); 087 commandFactory.addObject(new Usage(), "-usage"); 088 registerCommands(commandFactory); 089 } 090 } 091 092 protected void registerCommands(CommandFactory factory) { 093 // TODO: DFSAdmin subclasses FsShell so need to protect the command 094 // registration. This class should morph into a base class for 095 // commands, and then this method can be abstract 096 if (this.getClass().equals(FsShell.class)) { 097 factory.registerCommands(FsCommand.class); 098 } 099 } 100 101 /** 102 * Returns the Trash object associated with this shell. 103 * @return Path to the trash 104 * @throws IOException upon error 105 */ 106 public Path getCurrentTrashDir() throws IOException { 107 return getTrash().getCurrentTrashDir(); 108 } 109 110 // NOTE: Usage/Help are inner classes to allow access to outer methods 111 // that access commandFactory 112 113 /** 114 * Display help for commands with their short usage and long description 115 */ 116 protected class Usage extends FsCommand { 117 public static final String NAME = "usage"; 118 public static final String USAGE = "[cmd ...]"; 119 public static final String DESCRIPTION = 120 "Displays the usage for given command or all commands if none\n" + 121 "is specified."; 122 123 @Override 124 protected void processRawArguments(LinkedList<String> args) { 125 if (args.isEmpty()) { 126 printUsage(System.out); 127 } else { 128 for (String arg : args) printUsage(System.out, arg); 129 } 130 } 131 } 132 133 /** 134 * Displays short usage of commands sans the long description 135 */ 136 protected class Help extends FsCommand { 137 public static final String NAME = "help"; 138 public static final String USAGE = "[cmd ...]"; 139 public static final String DESCRIPTION = 140 "Displays help for given command or all commands if none\n" + 141 "is specified."; 142 143 @Override 144 protected void processRawArguments(LinkedList<String> args) { 145 if (args.isEmpty()) { 146 printHelp(System.out); 147 } else { 148 for (String arg : args) printHelp(System.out, arg); 149 } 150 } 151 } 152 153 /* 154 * The following are helper methods for getInfo(). They are defined 155 * outside of the scope of the Help/Usage class because the run() method 156 * needs to invoke them too. 157 */ 158 159 // print all usages 160 private void printUsage(PrintStream out) { 161 printInfo(out, null, false); 162 } 163 164 // print one usage 165 private void printUsage(PrintStream out, String cmd) { 166 printInfo(out, cmd, false); 167 } 168 169 // print all helps 170 private void printHelp(PrintStream out) { 171 printInfo(out, null, true); 172 } 173 174 // print one help 175 private void printHelp(PrintStream out, String cmd) { 176 printInfo(out, cmd, true); 177 } 178 179 private void printInfo(PrintStream out, String cmd, boolean showHelp) { 180 if (cmd != null) { 181 // display help or usage for one command 182 Command instance = commandFactory.getInstance("-" + cmd); 183 if (instance == null) { 184 throw new UnknownCommandException(cmd); 185 } 186 if (showHelp) { 187 printInstanceHelp(out, instance); 188 } else { 189 printInstanceUsage(out, instance); 190 } 191 } else { 192 // display help or usage for all commands 193 out.println(usagePrefix); 194 195 // display list of short usages 196 ArrayList<Command> instances = new ArrayList<Command>(); 197 for (String name : commandFactory.getNames()) { 198 Command instance = commandFactory.getInstance(name); 199 if (!instance.isDeprecated()) { 200 System.out.println("\t[" + instance.getUsage() + "]"); 201 instances.add(instance); 202 } 203 } 204 // display long descriptions for each command 205 if (showHelp) { 206 for (Command instance : instances) { 207 out.println(); 208 printInstanceHelp(out, instance); 209 } 210 } 211 out.println(); 212 ToolRunner.printGenericCommandUsage(out); 213 } 214 } 215 216 private void printInstanceUsage(PrintStream out, Command instance) { 217 out.println(usagePrefix + " " + instance.getUsage()); 218 } 219 220 // TODO: will eventually auto-wrap the text, but this matches the expected 221 // output for the hdfs tests... 222 private void printInstanceHelp(PrintStream out, Command instance) { 223 boolean firstLine = true; 224 for (String line : instance.getDescription().split("\n")) { 225 String prefix; 226 if (firstLine) { 227 prefix = instance.getUsage() + ":\t"; 228 firstLine = false; 229 } else { 230 prefix = "\t\t"; 231 } 232 System.out.println(prefix + line); 233 } 234 } 235 236 /** 237 * run 238 */ 239 public int run(String argv[]) throws Exception { 240 // initialize FsShell 241 init(); 242 243 int exitCode = -1; 244 if (argv.length < 1) { 245 printUsage(System.err); 246 } else { 247 String cmd = argv[0]; 248 Command instance = null; 249 try { 250 instance = commandFactory.getInstance(cmd); 251 if (instance == null) { 252 throw new UnknownCommandException(); 253 } 254 exitCode = instance.run(Arrays.copyOfRange(argv, 1, argv.length)); 255 } catch (IllegalArgumentException e) { 256 displayError(cmd, e.getLocalizedMessage()); 257 if (instance != null) { 258 printInstanceUsage(System.err, instance); 259 } 260 } catch (Exception e) { 261 // instance.run catches IOE, so something is REALLY wrong if here 262 LOG.debug("Error", e); 263 displayError(cmd, "Fatal internal error"); 264 e.printStackTrace(System.err); 265 } 266 } 267 return exitCode; 268 } 269 270 private void displayError(String cmd, String message) { 271 for (String line : message.split("\n")) { 272 System.err.println(cmd + ": " + line); 273 if (cmd.charAt(0) != '-') { 274 Command instance = null; 275 instance = commandFactory.getInstance("-" + cmd); 276 if (instance != null) { 277 System.err.println("Did you mean -" + cmd + "? This command " + 278 "begins with a dash."); 279 } 280 } 281 } 282 } 283 284 /** 285 * Performs any necessary cleanup 286 * @throws IOException upon error 287 */ 288 public void close() throws IOException { 289 if (fs != null) { 290 fs.close(); 291 fs = null; 292 } 293 } 294 295 /** 296 * main() has some simple utility methods 297 * @param argv the command and its arguments 298 * @throws Exception upon error 299 */ 300 public static void main(String argv[]) throws Exception { 301 FsShell shell = newShellInstance(); 302 int res; 303 try { 304 res = ToolRunner.run(shell, argv); 305 } finally { 306 shell.close(); 307 } 308 System.exit(res); 309 } 310 311 // TODO: this should be abstract in a base class 312 protected static FsShell newShellInstance() { 313 return new FsShell(); 314 } 315 316 /** 317 * The default ctor signals that the command being executed does not exist, 318 * while other ctor signals that a specific command does not exist. The 319 * latter is used by commands that process other commands, ex. -usage/-help 320 */ 321 @SuppressWarnings("serial") 322 static class UnknownCommandException extends IllegalArgumentException { 323 private final String cmd; 324 UnknownCommandException() { this(null); } 325 UnknownCommandException(String cmd) { this.cmd = cmd; } 326 327 @Override 328 public String getMessage() { 329 return ((cmd != null) ? "`"+cmd+"': " : "") + "Unknown command"; 330 } 331 } 332 }