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 }