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 }