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.logging.Log; 027import org.apache.commons.logging.LogFactory; 028import org.apache.hadoop.classification.InterfaceAudience; 029import org.apache.hadoop.conf.Configuration; 030import org.apache.hadoop.conf.Configured; 031import org.apache.hadoop.fs.shell.Command; 032import org.apache.hadoop.fs.shell.CommandFactory; 033import org.apache.hadoop.fs.shell.FsCommand; 034import org.apache.hadoop.util.Tool; 035import org.apache.hadoop.util.ToolRunner; 036 037/** Provide command line access to a FileSystem. */ 038@InterfaceAudience.Private 039public 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 @Override 240 public int run(String argv[]) throws Exception { 241 // initialize FsShell 242 init(); 243 244 int exitCode = -1; 245 if (argv.length < 1) { 246 printUsage(System.err); 247 } else { 248 String cmd = argv[0]; 249 Command instance = null; 250 try { 251 instance = commandFactory.getInstance(cmd); 252 if (instance == null) { 253 throw new UnknownCommandException(); 254 } 255 exitCode = instance.run(Arrays.copyOfRange(argv, 1, argv.length)); 256 } catch (IllegalArgumentException e) { 257 displayError(cmd, e.getLocalizedMessage()); 258 if (instance != null) { 259 printInstanceUsage(System.err, instance); 260 } 261 } catch (Exception e) { 262 // instance.run catches IOE, so something is REALLY wrong if here 263 LOG.debug("Error", e); 264 displayError(cmd, "Fatal internal error"); 265 e.printStackTrace(System.err); 266 } 267 } 268 return exitCode; 269 } 270 271 private void displayError(String cmd, String message) { 272 for (String line : message.split("\n")) { 273 System.err.println(cmd + ": " + line); 274 if (cmd.charAt(0) != '-') { 275 Command instance = null; 276 instance = commandFactory.getInstance("-" + cmd); 277 if (instance != null) { 278 System.err.println("Did you mean -" + cmd + "? This command " + 279 "begins with a dash."); 280 } 281 } 282 } 283 } 284 285 /** 286 * Performs any necessary cleanup 287 * @throws IOException upon error 288 */ 289 public void close() throws IOException { 290 if (fs != null) { 291 fs.close(); 292 fs = null; 293 } 294 } 295 296 /** 297 * main() has some simple utility methods 298 * @param argv the command and its arguments 299 * @throws Exception upon error 300 */ 301 public static void main(String argv[]) throws Exception { 302 FsShell shell = newShellInstance(); 303 int res; 304 try { 305 res = ToolRunner.run(shell, argv); 306 } finally { 307 shell.close(); 308 } 309 System.exit(res); 310 } 311 312 // TODO: this should be abstract in a base class 313 protected static FsShell newShellInstance() { 314 return new FsShell(); 315 } 316 317 /** 318 * The default ctor signals that the command being executed does not exist, 319 * while other ctor signals that a specific command does not exist. The 320 * latter is used by commands that process other commands, ex. -usage/-help 321 */ 322 @SuppressWarnings("serial") 323 static class UnknownCommandException extends IllegalArgumentException { 324 private final String cmd; 325 UnknownCommandException() { this(null); } 326 UnknownCommandException(String cmd) { this.cmd = cmd; } 327 328 @Override 329 public String getMessage() { 330 return ((cmd != null) ? "`"+cmd+"': " : "") + "Unknown command"; 331 } 332 } 333}