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.logging.Log;
027    import org.apache.commons.logging.LogFactory;
028    import org.apache.hadoop.classification.InterfaceAudience;
029    import org.apache.hadoop.conf.Configuration;
030    import org.apache.hadoop.conf.Configured;
031    import org.apache.hadoop.fs.shell.Command;
032    import org.apache.hadoop.fs.shell.CommandFactory;
033    import org.apache.hadoop.fs.shell.FsCommand;
034    import org.apache.hadoop.util.Tool;
035    import org.apache.hadoop.util.ToolRunner;
036    
037    /** Provide command line access to a FileSystem. */
038    @InterfaceAudience.Private
039    public 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        Configuration conf = new Configuration();
304        conf.setQuietMode(false);
305        shell.setConf(conf);
306        int res;
307        try {
308          res = ToolRunner.run(shell, argv);
309        } finally {
310          shell.close();
311        }
312        System.exit(res);
313      }
314    
315      // TODO: this should be abstract in a base class
316      protected static FsShell newShellInstance() {
317        return new FsShell();
318      }
319      
320      /**
321       * The default ctor signals that the command being executed does not exist,
322       * while other ctor signals that a specific command does not exist.  The
323       * latter is used by commands that process other commands, ex. -usage/-help
324       */
325      @SuppressWarnings("serial")
326      static class UnknownCommandException extends IllegalArgumentException {
327        private final String cmd;    
328        UnknownCommandException() { this(null); }
329        UnknownCommandException(String cmd) { this.cmd = cmd; }
330        
331        @Override
332        public String getMessage() {
333          return ((cmd != null) ? "`"+cmd+"': " : "") + "Unknown command";
334        }
335      }
336    }