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 */ 018package org.apache.hadoop.fs; 019 020import java.io.IOException; 021import java.io.PrintStream; 022import java.util.ArrayList; 023import java.util.Arrays; 024import java.util.LinkedList; 025 026import org.apache.commons.lang.WordUtils; 027import org.apache.commons.logging.Log; 028import org.apache.commons.logging.LogFactory; 029import org.apache.hadoop.classification.InterfaceAudience; 030import org.apache.hadoop.conf.Configuration; 031import org.apache.hadoop.conf.Configured; 032import org.apache.hadoop.fs.shell.Command; 033import org.apache.hadoop.fs.shell.CommandFactory; 034import org.apache.hadoop.fs.shell.FsCommand; 035import org.apache.hadoop.tools.TableListing; 036import org.apache.hadoop.tracing.TraceUtils; 037import org.apache.hadoop.util.StringUtils; 038import org.apache.hadoop.util.Tool; 039import org.apache.hadoop.util.ToolRunner; 040import org.apache.htrace.core.TraceScope; 041import org.apache.htrace.core.Tracer; 042 043/** Provide command line access to a FileSystem. */ 044@InterfaceAudience.Private 045public class FsShell extends Configured implements Tool { 046 047 static final Log LOG = LogFactory.getLog(FsShell.class); 048 049 private static final int MAX_LINE_WIDTH = 80; 050 051 private FileSystem fs; 052 private Trash trash; 053 protected CommandFactory commandFactory; 054 055 private final String usagePrefix = 056 "Usage: hadoop fs [generic options]"; 057 058 private Tracer tracer; 059 static final String SHELL_HTRACE_PREFIX = "fs.shell.htrace."; 060 061 /** 062 * Default ctor with no configuration. Be sure to invoke 063 * {@link #setConf(Configuration)} with a valid configuration prior 064 * to running commands. 065 */ 066 public FsShell() { 067 this(null); 068 } 069 070 /** 071 * Construct a FsShell with the given configuration. Commands can be 072 * executed via {@link #run(String[])} 073 * @param conf the hadoop configuration 074 */ 075 public FsShell(Configuration conf) { 076 super(conf); 077 } 078 079 protected FileSystem getFS() throws IOException { 080 if (fs == null) { 081 fs = FileSystem.get(getConf()); 082 } 083 return fs; 084 } 085 086 protected Trash getTrash() throws IOException { 087 if (this.trash == null) { 088 this.trash = new Trash(getConf()); 089 } 090 return this.trash; 091 } 092 093 protected void init() throws IOException { 094 getConf().setQuietMode(true); 095 if (commandFactory == null) { 096 commandFactory = new CommandFactory(getConf()); 097 commandFactory.addObject(new Help(), "-help"); 098 commandFactory.addObject(new Usage(), "-usage"); 099 registerCommands(commandFactory); 100 } 101 this.tracer = new Tracer.Builder("FsShell"). 102 conf(TraceUtils.wrapHadoopConf(SHELL_HTRACE_PREFIX, getConf())). 103 build(); 104 } 105 106 protected void registerCommands(CommandFactory factory) { 107 // TODO: DFSAdmin subclasses FsShell so need to protect the command 108 // registration. This class should morph into a base class for 109 // commands, and then this method can be abstract 110 if (this.getClass().equals(FsShell.class)) { 111 factory.registerCommands(FsCommand.class); 112 } 113 } 114 115 /** 116 * Returns the Trash object associated with this shell. 117 * @return Path to the trash 118 * @throws IOException upon error 119 */ 120 public Path getCurrentTrashDir() throws IOException { 121 return getTrash().getCurrentTrashDir(); 122 } 123 124 /** 125 * Returns the current trash location for the path specified 126 * @param path to be deleted 127 * @return path to the trash 128 * @throws IOException 129 */ 130 public Path getCurrentTrashDir(Path path) throws IOException { 131 return getTrash().getCurrentTrashDir(path); 132 } 133 134 // NOTE: Usage/Help are inner classes to allow access to outer methods 135 // that access commandFactory 136 137 /** 138 * Display help for commands with their short usage and long description. 139 */ 140 protected class Usage extends FsCommand { 141 public static final String NAME = "usage"; 142 public static final String USAGE = "[cmd ...]"; 143 public static final String DESCRIPTION = 144 "Displays the usage for given command or all commands if none " + 145 "is specified."; 146 147 @Override 148 protected void processRawArguments(LinkedList<String> args) { 149 if (args.isEmpty()) { 150 printUsage(System.out); 151 } else { 152 for (String arg : args) printUsage(System.out, arg); 153 } 154 } 155 } 156 157 /** 158 * Displays short usage of commands sans the long description 159 */ 160 protected class Help extends FsCommand { 161 public static final String NAME = "help"; 162 public static final String USAGE = "[cmd ...]"; 163 public static final String DESCRIPTION = 164 "Displays help for given command or all commands if none " + 165 "is specified."; 166 167 @Override 168 protected void processRawArguments(LinkedList<String> args) { 169 if (args.isEmpty()) { 170 printHelp(System.out); 171 } else { 172 for (String arg : args) printHelp(System.out, arg); 173 } 174 } 175 } 176 177 /* 178 * The following are helper methods for getInfo(). They are defined 179 * outside of the scope of the Help/Usage class because the run() method 180 * needs to invoke them too. 181 */ 182 183 // print all usages 184 private void printUsage(PrintStream out) { 185 printInfo(out, null, false); 186 } 187 188 // print one usage 189 private void printUsage(PrintStream out, String cmd) { 190 printInfo(out, cmd, false); 191 } 192 193 // print all helps 194 private void printHelp(PrintStream out) { 195 printInfo(out, null, true); 196 } 197 198 // print one help 199 private void printHelp(PrintStream out, String cmd) { 200 printInfo(out, cmd, true); 201 } 202 203 private void printInfo(PrintStream out, String cmd, boolean showHelp) { 204 if (cmd != null) { 205 // display help or usage for one command 206 Command instance = commandFactory.getInstance("-" + cmd); 207 if (instance == null) { 208 throw new UnknownCommandException(cmd); 209 } 210 if (showHelp) { 211 printInstanceHelp(out, instance); 212 } else { 213 printInstanceUsage(out, instance); 214 } 215 } else { 216 // display help or usage for all commands 217 out.println(usagePrefix); 218 219 // display list of short usages 220 ArrayList<Command> instances = new ArrayList<Command>(); 221 for (String name : commandFactory.getNames()) { 222 Command instance = commandFactory.getInstance(name); 223 if (!instance.isDeprecated()) { 224 out.println("\t[" + instance.getUsage() + "]"); 225 instances.add(instance); 226 } 227 } 228 // display long descriptions for each command 229 if (showHelp) { 230 for (Command instance : instances) { 231 out.println(); 232 printInstanceHelp(out, instance); 233 } 234 } 235 out.println(); 236 ToolRunner.printGenericCommandUsage(out); 237 } 238 } 239 240 private void printInstanceUsage(PrintStream out, Command instance) { 241 out.println(usagePrefix + " " + instance.getUsage()); 242 } 243 244 private void printInstanceHelp(PrintStream out, Command instance) { 245 out.println(instance.getUsage() + " :"); 246 TableListing listing = null; 247 final String prefix = " "; 248 for (String line : instance.getDescription().split("\n")) { 249 if (line.matches("^[ \t]*[-<].*$")) { 250 String[] segments = line.split(":"); 251 if (segments.length == 2) { 252 if (listing == null) { 253 listing = createOptionTableListing(); 254 } 255 listing.addRow(segments[0].trim(), segments[1].trim()); 256 continue; 257 } 258 } 259 260 // Normal literal description. 261 if (listing != null) { 262 for (String listingLine : listing.toString().split("\n")) { 263 out.println(prefix + listingLine); 264 } 265 listing = null; 266 } 267 268 for (String descLine : WordUtils.wrap( 269 line, MAX_LINE_WIDTH, "\n", true).split("\n")) { 270 out.println(prefix + descLine); 271 } 272 } 273 274 if (listing != null) { 275 for (String listingLine : listing.toString().split("\n")) { 276 out.println(prefix + listingLine); 277 } 278 } 279 } 280 281 // Creates a two-row table, the first row is for the command line option, 282 // the second row is for the option description. 283 private TableListing createOptionTableListing() { 284 return new TableListing.Builder().addField("").addField("", true) 285 .wrapWidth(MAX_LINE_WIDTH).build(); 286 } 287 288 /** 289 * run 290 */ 291 @Override 292 public int run(String argv[]) throws Exception { 293 // initialize FsShell 294 init(); 295 int exitCode = -1; 296 if (argv.length < 1) { 297 printUsage(System.err); 298 } else { 299 String cmd = argv[0]; 300 Command instance = null; 301 try { 302 instance = commandFactory.getInstance(cmd); 303 if (instance == null) { 304 throw new UnknownCommandException(); 305 } 306 TraceScope scope = tracer.newScope(instance.getCommandName()); 307 if (scope.getSpan() != null) { 308 String args = StringUtils.join(" ", argv); 309 if (args.length() > 2048) { 310 args = args.substring(0, 2048); 311 } 312 scope.getSpan().addKVAnnotation("args", args); 313 } 314 try { 315 exitCode = instance.run(Arrays.copyOfRange(argv, 1, argv.length)); 316 } finally { 317 scope.close(); 318 } 319 } catch (IllegalArgumentException e) { 320 if (e.getMessage() == null) { 321 displayError(cmd, "Null exception message"); 322 e.printStackTrace(System.err); 323 } else { 324 displayError(cmd, e.getLocalizedMessage()); 325 } 326 printUsage(System.err); 327 if (instance != null) { 328 printInstanceUsage(System.err, instance); 329 } 330 } catch (Exception e) { 331 // instance.run catches IOE, so something is REALLY wrong if here 332 LOG.debug("Error", e); 333 displayError(cmd, "Fatal internal error"); 334 e.printStackTrace(System.err); 335 } 336 } 337 tracer.close(); 338 return exitCode; 339 } 340 341 private void displayError(String cmd, String message) { 342 for (String line : message.split("\n")) { 343 System.err.println(cmd + ": " + line); 344 if (cmd.charAt(0) != '-') { 345 Command instance = null; 346 instance = commandFactory.getInstance("-" + cmd); 347 if (instance != null) { 348 System.err.println("Did you mean -" + cmd + "? This command " + 349 "begins with a dash."); 350 } 351 } 352 } 353 } 354 355 /** 356 * Performs any necessary cleanup 357 * @throws IOException upon error 358 */ 359 public void close() throws IOException { 360 if (fs != null) { 361 fs.close(); 362 fs = null; 363 } 364 } 365 366 /** 367 * main() has some simple utility methods 368 * @param argv the command and its arguments 369 * @throws Exception upon error 370 */ 371 public static void main(String argv[]) throws Exception { 372 FsShell shell = newShellInstance(); 373 Configuration conf = new Configuration(); 374 conf.setQuietMode(false); 375 shell.setConf(conf); 376 int res; 377 try { 378 res = ToolRunner.run(shell, argv); 379 } finally { 380 shell.close(); 381 } 382 System.exit(res); 383 } 384 385 // TODO: this should be abstract in a base class 386 protected static FsShell newShellInstance() { 387 return new FsShell(); 388 } 389 390 /** 391 * The default ctor signals that the command being executed does not exist, 392 * while other ctor signals that a specific command does not exist. The 393 * latter is used by commands that process other commands, ex. -usage/-help 394 */ 395 @SuppressWarnings("serial") 396 static class UnknownCommandException extends IllegalArgumentException { 397 private final String cmd; 398 UnknownCommandException() { this(null); } 399 UnknownCommandException(String cmd) { this.cmd = cmd; } 400 401 @Override 402 public String getMessage() { 403 return ((cmd != null) ? "`"+cmd+"': " : "") + "Unknown command"; 404 } 405 } 406}