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