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  public int run(String argv[]) throws Exception {
240    // initialize FsShell
241    init();
242
243    int exitCode = -1;
244    if (argv.length < 1) {
245      printUsage(System.err);
246    } else {
247      String cmd = argv[0];
248      Command instance = null;
249      try {
250        instance = commandFactory.getInstance(cmd);
251        if (instance == null) {
252          throw new UnknownCommandException();
253        }
254        exitCode = instance.run(Arrays.copyOfRange(argv, 1, argv.length));
255      } catch (IllegalArgumentException e) {
256        displayError(cmd, e.getLocalizedMessage());
257        if (instance != null) {
258          printInstanceUsage(System.err, instance);
259        }
260      } catch (Exception e) {
261        // instance.run catches IOE, so something is REALLY wrong if here
262        LOG.debug("Error", e);
263        displayError(cmd, "Fatal internal error");
264        e.printStackTrace(System.err);
265      }
266    }
267    return exitCode;
268  }
269  
270  private void displayError(String cmd, String message) {
271    for (String line : message.split("\n")) {
272      System.err.println(cmd + ": " + line);
273      if (cmd.charAt(0) != '-') {
274        Command instance = null;
275        instance = commandFactory.getInstance("-" + cmd);
276        if (instance != null) {
277          System.err.println("Did you mean -" + cmd + "?  This command " +
278              "begins with a dash.");
279        }
280      }
281    }
282  }
283  
284  /**
285   *  Performs any necessary cleanup
286   * @throws IOException upon error
287   */
288  public void close() throws IOException {
289    if (fs != null) {
290      fs.close();
291      fs = null;
292    }
293  }
294
295  /**
296   * main() has some simple utility methods
297   * @param argv the command and its arguments
298   * @throws Exception upon error
299   */
300  public static void main(String argv[]) throws Exception {
301    FsShell shell = newShellInstance();
302    Configuration conf = new Configuration();
303    conf.setQuietMode(false);
304    shell.setConf(conf);
305    int res;
306    try {
307      res = ToolRunner.run(shell, argv);
308    } finally {
309      shell.close();
310    }
311    System.exit(res);
312  }
313
314  // TODO: this should be abstract in a base class
315  protected static FsShell newShellInstance() {
316    return new FsShell();
317  }
318  
319  /**
320   * The default ctor signals that the command being executed does not exist,
321   * while other ctor signals that a specific command does not exist.  The
322   * latter is used by commands that process other commands, ex. -usage/-help
323   */
324  @SuppressWarnings("serial")
325  static class UnknownCommandException extends IllegalArgumentException {
326    private final String cmd;    
327    UnknownCommandException() { this(null); }
328    UnknownCommandException(String cmd) { this.cmd = cmd; }
329    
330    @Override
331    public String getMessage() {
332      return ((cmd != null) ? "`"+cmd+"': " : "") + "Unknown command";
333    }
334  }
335}