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.tracing.TraceUtils;
037import org.apache.hadoop.util.StringUtils;
038import org.apache.hadoop.util.Tool;
039import org.apache.hadoop.util.ToolRunner;
040import org.apache.htrace.core.TraceScope;
041import org.apache.htrace.core.Tracer;
042
043/** Provide command line access to a FileSystem. */
044@InterfaceAudience.Private
045public class FsShell extends Configured implements Tool {
046  
047  static final Log LOG = LogFactory.getLog(FsShell.class);
048
049  private static final int MAX_LINE_WIDTH = 80;
050
051  private FileSystem fs;
052  private Trash trash;
053  protected CommandFactory commandFactory;
054
055  private final String usagePrefix =
056    "Usage: hadoop fs [generic options]";
057
058  private Tracer tracer;
059  static final String SHELL_HTRACE_PREFIX = "fs.shell.htrace.";
060
061  /**
062   * Default ctor with no configuration.  Be sure to invoke
063   * {@link #setConf(Configuration)} with a valid configuration prior
064   * to running commands.
065   */
066  public FsShell() {
067    this(null);
068  }
069
070  /**
071   * Construct a FsShell with the given configuration.  Commands can be
072   * executed via {@link #run(String[])}
073   * @param conf the hadoop configuration
074   */
075  public FsShell(Configuration conf) {
076    super(conf);
077  }
078  
079  protected FileSystem getFS() throws IOException {
080    if (fs == null) {
081      fs = FileSystem.get(getConf());
082    }
083    return fs;
084  }
085  
086  protected Trash getTrash() throws IOException {
087    if (this.trash == null) {
088      this.trash = new Trash(getConf());
089    }
090    return this.trash;
091  }
092  
093  protected void init() throws IOException {
094    getConf().setQuietMode(true);
095    if (commandFactory == null) {
096      commandFactory = new CommandFactory(getConf());
097      commandFactory.addObject(new Help(), "-help");
098      commandFactory.addObject(new Usage(), "-usage");
099      registerCommands(commandFactory);
100    }
101    this.tracer = new Tracer.Builder("FsShell").
102        conf(TraceUtils.wrapHadoopConf(SHELL_HTRACE_PREFIX, getConf())).
103        build();
104  }
105
106  protected void registerCommands(CommandFactory factory) {
107    // TODO: DFSAdmin subclasses FsShell so need to protect the command
108    // registration.  This class should morph into a base class for
109    // commands, and then this method can be abstract
110    if (this.getClass().equals(FsShell.class)) {
111      factory.registerCommands(FsCommand.class);
112    }
113  }
114  
115  /**
116   * Returns the Trash object associated with this shell.
117   * @return Path to the trash
118   * @throws IOException upon error
119   */
120  public Path getCurrentTrashDir() throws IOException {
121    return getTrash().getCurrentTrashDir();
122  }
123
124  /**
125   * Returns the current trash location for the path specified
126   * @param path to be deleted
127   * @return path to the trash
128   * @throws IOException
129   */
130  public Path getCurrentTrashDir(Path path) throws IOException {
131    return getTrash().getCurrentTrashDir(path);
132  }
133
134  // NOTE: Usage/Help are inner classes to allow access to outer methods
135  // that access commandFactory
136  
137  /**
138   *  Display help for commands with their short usage and long description.
139   */
140   protected class Usage extends FsCommand {
141    public static final String NAME = "usage";
142    public static final String USAGE = "[cmd ...]";
143    public static final String DESCRIPTION =
144      "Displays the usage 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        printUsage(System.out);
151      } else {
152        for (String arg : args) printUsage(System.out, arg);
153      }
154    }
155  } 
156
157  /**
158   * Displays short usage of commands sans the long description
159   */
160  protected class Help extends FsCommand {
161    public static final String NAME = "help";
162    public static final String USAGE = "[cmd ...]";
163    public static final String DESCRIPTION =
164      "Displays help for given command or all commands if none " +
165      "is specified.";
166    
167    @Override
168    protected void processRawArguments(LinkedList<String> args) {
169      if (args.isEmpty()) {
170        printHelp(System.out);
171      } else {
172        for (String arg : args) printHelp(System.out, arg);
173      }
174    }
175  }
176
177  /*
178   * The following are helper methods for getInfo().  They are defined
179   * outside of the scope of the Help/Usage class because the run() method
180   * needs to invoke them too. 
181   */
182
183  // print all usages
184  private void printUsage(PrintStream out) {
185    printInfo(out, null, false);
186  }
187  
188  // print one usage
189  private void printUsage(PrintStream out, String cmd) {
190    printInfo(out, cmd, false);
191  }
192
193  // print all helps
194  private void printHelp(PrintStream out) {
195    printInfo(out, null, true);
196  }
197
198  // print one help
199  private void printHelp(PrintStream out, String cmd) {
200    printInfo(out, cmd, true);
201  }
202
203  private void printInfo(PrintStream out, String cmd, boolean showHelp) {
204    if (cmd != null) {
205      // display help or usage for one command
206      Command instance = commandFactory.getInstance("-" + cmd);
207      if (instance == null) {
208        throw new UnknownCommandException(cmd);
209      }
210      if (showHelp) {
211        printInstanceHelp(out, instance);
212      } else {
213        printInstanceUsage(out, instance);
214      }
215    } else {
216      // display help or usage for all commands 
217      out.println(usagePrefix);
218      
219      // display list of short usages
220      ArrayList<Command> instances = new ArrayList<Command>();
221      for (String name : commandFactory.getNames()) {
222        Command instance = commandFactory.getInstance(name);
223        if (!instance.isDeprecated()) {
224          out.println("\t[" + instance.getUsage() + "]");
225          instances.add(instance);
226        }
227      }
228      // display long descriptions for each command
229      if (showHelp) {
230        for (Command instance : instances) {
231          out.println();
232          printInstanceHelp(out, instance);
233        }
234      }
235      out.println();
236      ToolRunner.printGenericCommandUsage(out);
237    }
238  }
239
240  private void printInstanceUsage(PrintStream out, Command instance) {
241    out.println(usagePrefix + " " + instance.getUsage());
242  }
243
244  private void printInstanceHelp(PrintStream out, Command instance) {
245    out.println(instance.getUsage() + " :");
246    TableListing listing = null;
247    final String prefix = "  ";
248    for (String line : instance.getDescription().split("\n")) {
249      if (line.matches("^[ \t]*[-<].*$")) {
250        String[] segments = line.split(":");
251        if (segments.length == 2) {
252          if (listing == null) {
253            listing = createOptionTableListing();
254          }
255          listing.addRow(segments[0].trim(), segments[1].trim());
256          continue;
257        }
258      }
259
260      // Normal literal description.
261      if (listing != null) {
262        for (String listingLine : listing.toString().split("\n")) {
263          out.println(prefix + listingLine);
264        }
265        listing = null;
266      }
267
268      for (String descLine : WordUtils.wrap(
269          line, MAX_LINE_WIDTH, "\n", true).split("\n")) {
270        out.println(prefix + descLine);
271      }
272    }
273
274    if (listing != null) {
275      for (String listingLine : listing.toString().split("\n")) {
276        out.println(prefix + listingLine);
277      }
278    }
279  }
280
281  // Creates a two-row table, the first row is for the command line option,
282  // the second row is for the option description.
283  private TableListing createOptionTableListing() {
284    return new TableListing.Builder().addField("").addField("", true)
285        .wrapWidth(MAX_LINE_WIDTH).build();
286  }
287
288  /**
289   * run
290   */
291  @Override
292  public int run(String argv[]) throws Exception {
293    // initialize FsShell
294    init();
295    int exitCode = -1;
296    if (argv.length < 1) {
297      printUsage(System.err);
298    } else {
299      String cmd = argv[0];
300      Command instance = null;
301      try {
302        instance = commandFactory.getInstance(cmd);
303        if (instance == null) {
304          throw new UnknownCommandException();
305        }
306        TraceScope scope = tracer.newScope(instance.getCommandName());
307        if (scope.getSpan() != null) {
308          String args = StringUtils.join(" ", argv);
309          if (args.length() > 2048) {
310            args = args.substring(0, 2048);
311          }
312          scope.getSpan().addKVAnnotation("args", args);
313        }
314        try {
315          exitCode = instance.run(Arrays.copyOfRange(argv, 1, argv.length));
316        } finally {
317          scope.close();
318        }
319      } catch (IllegalArgumentException e) {
320        if (e.getMessage() == null) {
321          displayError(cmd, "Null exception message");
322          e.printStackTrace(System.err);
323        } else {
324          displayError(cmd, e.getLocalizedMessage());
325        }
326        printUsage(System.err);
327        if (instance != null) {
328          printInstanceUsage(System.err, instance);
329        }
330      } catch (Exception e) {
331        // instance.run catches IOE, so something is REALLY wrong if here
332        LOG.debug("Error", e);
333        displayError(cmd, "Fatal internal error");
334        e.printStackTrace(System.err);
335      }
336    }
337    tracer.close();
338    return exitCode;
339  }
340  
341  private void displayError(String cmd, String message) {
342    for (String line : message.split("\n")) {
343      System.err.println(cmd + ": " + line);
344      if (cmd.charAt(0) != '-') {
345        Command instance = null;
346        instance = commandFactory.getInstance("-" + cmd);
347        if (instance != null) {
348          System.err.println("Did you mean -" + cmd + "?  This command " +
349              "begins with a dash.");
350        }
351      }
352    }
353  }
354  
355  /**
356   *  Performs any necessary cleanup
357   * @throws IOException upon error
358   */
359  public void close() throws IOException {
360    if (fs != null) {
361      fs.close();
362      fs = null;
363    }
364  }
365
366  /**
367   * main() has some simple utility methods
368   * @param argv the command and its arguments
369   * @throws Exception upon error
370   */
371  public static void main(String argv[]) throws Exception {
372    FsShell shell = newShellInstance();
373    Configuration conf = new Configuration();
374    conf.setQuietMode(false);
375    shell.setConf(conf);
376    int res;
377    try {
378      res = ToolRunner.run(shell, argv);
379    } finally {
380      shell.close();
381    }
382    System.exit(res);
383  }
384
385  // TODO: this should be abstract in a base class
386  protected static FsShell newShellInstance() {
387    return new FsShell();
388  }
389  
390  /**
391   * The default ctor signals that the command being executed does not exist,
392   * while other ctor signals that a specific command does not exist.  The
393   * latter is used by commands that process other commands, ex. -usage/-help
394   */
395  @SuppressWarnings("serial")
396  static class UnknownCommandException extends IllegalArgumentException {
397    private final String cmd;    
398    UnknownCommandException() { this(null); }
399    UnknownCommandException(String cmd) { this.cmd = cmd; }
400    
401    @Override
402    public String getMessage() {
403      return ((cmd != null) ? "`"+cmd+"': " : "") + "Unknown command";
404    }
405  }
406}