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