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