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 @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 }