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.lang.WordUtils;
027import org.apache.commons.logging.Log;
028import org.apache.commons.logging.LogFactory;
029import org.apache.hadoop.classification.InterfaceAudience;
030import org.apache.hadoop.conf.Configuration;
031import org.apache.hadoop.conf.Configured;
032import org.apache.hadoop.fs.shell.Command;
033import org.apache.hadoop.fs.shell.CommandFactory;
034import org.apache.hadoop.fs.shell.FsCommand;
035import org.apache.hadoop.tools.TableListing;
036import org.apache.hadoop.util.Tool;
037import org.apache.hadoop.util.ToolRunner;
038
039/** Provide command line access to a FileSystem. */
040@InterfaceAudience.Private
041public 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}