/* * $Id$ * * Copyright (C) 2003-2015 JNode.org * * This library is free software; you can redistribute it and/or modify it * under the terms of the GNU Lesser General Public License as published * by the Free Software Foundation; either version 2.1 of the License, or * (at your option) any later version. * * This library is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public * License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this library; If not, write to the Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ package org.jnode.command.file; import java.io.File; import java.io.IOException; import java.io.PrintWriter; import java.util.Stack; import javax.naming.NameNotFoundException; import org.apache.log4j.Logger; import org.jnode.command.util.AbstractDirectoryWalker; import org.jnode.driver.Device; import org.jnode.driver.block.FSBlockDeviceAPI; import org.jnode.fs.service.FileSystemService; import org.jnode.naming.InitialNaming; import org.jnode.shell.AbstractCommand; import org.jnode.shell.syntax.Argument; import org.jnode.shell.syntax.FileArgument; import org.jnode.shell.syntax.FlagArgument; import org.jnode.shell.syntax.IntegerArgument; import org.jnode.shell.syntax.StringArgument; import org.jnode.util.NumberUtils; import org.jnode.vm.VmExit; /** * @author Alexander Kerner * @author Mario Zsilak */ public class DuCommand extends AbstractCommand { private static final String HELP_SUPER = "With no arguments, `du' reports the disk space for the current directory. Normally the disk space is " + "printed in units of 1024 bytes, but this can be overridden"; private static final String HELP_DIR = "directory to start printing sizes recursively"; private static final String HELP_ALL = "Show counts for all files, not just directories."; private static final String HELP_BLOCK_SIZE_1 = "Print sizes in bytes, overriding the default block size"; private static final String HELP_TOTAL = "Print a grand total of all arguments after all arguments have been processed. This can be used to " + "find out the total disk usage of a given set of files or directories."; private static final String HELP_DEREF_ARGS = "Dereference symbolic links that are command line arguments. Does not affect other symbolic links. " + "This is helpful for finding out the disk usage of directories, such as `/usr/tmp', " + "which are often symbolic links. (not implemented"; private static final String HELP_HUMAN_READABLE_1024 = "Append a size letter such as `M' for megabytes to each size. Powers of 1024 are used, not 1000; " + "`M' stands for 1,048,576 bytes. Use the `-H' or `--si' option if you prefer powers of 1000."; private static final String HELP_HUMAN_READABLE_1000 = "Append a size letter such as `M' for megabytes to each size. (SI is the International System of Units, " + "which defines these letters as prefixes.) Powers of 1000 are used, not 1024; " + "`M' stands for 1,000,000 bytes. Use the `-h' or `--human-readable' option if you prefer " + "powers of 1024."; private static final String HELP_BLOCK_SIZE_1024 = "Print sizes in 1024-byte blocks, overriding the default block size"; private static final String HELP_COUNT_LINKS = "Count the size of all files, even if they have appeared already (as a hard link). (not implemented"; private static final String HELP_DEREF = "Dereference symbolic links (show the disk space used by the file or directory that the link points to " + "instead of the space used by the link). (not implemented"; private static final String HELP_MAX_DEPTH = "Show the total for each directory (and file if -all) that is at most MAX_DEPTH levels down " + "from the root of the hierarchy. The root is at level 0, so `du --max-depth=0' is equivalent " + "to `du -s'. (not tested)"; private static final String HELP_BLOCK_SIZE_1024x1024 = "Print sizes in megabyte (that is, 1,048,576-byte) blocks."; private static final String HELP_SUM = "Display only a total for each argument."; private static final String HELP_SEPERATE_DIRS = "Report the size of each directory separately, not including the sizes of subdirectories."; private static final String HELP_ONE_FS = "Skip directories that are on different filesystems from the one that the argument " + "being processed is on. (not implemented)"; private static final String HELP_EXCLUDE = "When recursing, skip subdirectories or files matching PAT. For example, `du --exclude='*.o'' " + "excludes files whose names end in `.o'. (not tested)"; private static final String HELP_EXCLUDE_FROM = "Like `--exclude', except take the patterns to exclude from FILE, one per line. If FILE is `-', " + "take the patterns from standard input. (not implemented)"; private static final String HELP_BLOCK_SIZE_CUSTOM = "Print sizes in the user defined block size, overriding the default block size"; private static final String HELP_FS_BLOCK_SIZE = "Overrides the filesystem block size -- use it for testing"; private final FileArgument argDir = new FileArgument("directory", Argument.OPTIONAL | Argument.MULTIPLE | Argument.EXISTING, HELP_DIR); private final FlagArgument argAll = new FlagArgument("all", Argument.OPTIONAL, HELP_ALL); private final FlagArgument argBlockSize_1 = new FlagArgument("block-size-1", Argument.OPTIONAL, HELP_BLOCK_SIZE_1); private final FlagArgument argTotal = new FlagArgument("total", Argument.OPTIONAL, HELP_TOTAL); private final FlagArgument argDerefArgs = new FlagArgument("derefArgs", Argument.OPTIONAL, HELP_DEREF_ARGS); private final FlagArgument argHumanReadable_1024 = new FlagArgument("human-readable-1024", Argument.OPTIONAL, HELP_HUMAN_READABLE_1024); private final FlagArgument argHumanReadable_1000 = new FlagArgument("human-readable-1000", Argument.OPTIONAL, HELP_HUMAN_READABLE_1000); private final FlagArgument argBlockSize_1024 = new FlagArgument("block-size-1024", Argument.OPTIONAL, HELP_BLOCK_SIZE_1024); private final FlagArgument argCountLinks = new FlagArgument("count-links", Argument.OPTIONAL, HELP_COUNT_LINKS); private final FlagArgument argDereference = new FlagArgument("dereference", Argument.OPTIONAL, HELP_DEREF); private final IntegerArgument argMaxDepth = new IntegerArgument("max-depth", Argument.OPTIONAL, HELP_MAX_DEPTH); private final FlagArgument argBlockSize_1024x1024 = new FlagArgument("block-size-1024x1024", Argument.OPTIONAL, HELP_BLOCK_SIZE_1024x1024); private final FlagArgument argSum = new FlagArgument("summarize", Argument.OPTIONAL, HELP_SUM); private final FlagArgument argSeperateDirs = new FlagArgument("separate-dirs", Argument.OPTIONAL, HELP_SEPERATE_DIRS); private static final FlagArgument argOneFS = new FlagArgument("one-file-system", Argument.OPTIONAL, HELP_ONE_FS); private final StringArgument argExclude = new StringArgument("exclude", Argument.OPTIONAL, HELP_EXCLUDE); private static final StringArgument argExcludeFrom = new StringArgument("exclude-from", Argument.OPTIONAL, HELP_EXCLUDE_FROM); private final IntegerArgument argBlockSize_Custom = new IntegerArgument("block-size-custom", Argument.OPTIONAL, HELP_BLOCK_SIZE_CUSTOM); private final IntegerArgument argFilesystemBlockSize = new IntegerArgument("filesystem-block-size", Argument.OPTIONAL, HELP_FS_BLOCK_SIZE); private static final String ERR_PERMISSION = "Permission denied for '%s'%n"; private static final int DEFAULT_FILESYSTEM_BLOCK_SIZE = 1024; private static final int DEFAULT_DISPLAY_BLOCK_SIZE = 1024; private Logger logger = Logger.getLogger(getClass()); private int fsBlockSize; private int displayBlockSize; private PrintWriter out; private PrintWriter err; public static void main(String[] args) throws IOException { new DuCommand().execute(); } public DuCommand() { super(HELP_SUPER); registerArguments(argDir, argAll, argBlockSize_1, argTotal, argDerefArgs, argHumanReadable_1024, argHumanReadable_1000, argBlockSize_1024, argCountLinks, argDereference, argMaxDepth, argBlockSize_1024x1024, argSum, argSeperateDirs, argOneFS, argExclude, argExcludeFrom, argBlockSize_Custom, argFilesystemBlockSize); } public void execute() throws IOException { Walker walker = null; File[] startPoints = null; out = getOutput().getPrintWriter(); err = getError().getPrintWriter(); if (argAll.isSet() && argSum.isSet()) { err.println("Summarize and show all not possible at the some time!"); throw new VmExit(1); } if (argDerefArgs.isSet()) { logger.warn(argDerefArgs.getLabel() + " is currently not supported"); } if (argOneFS.isSet()) { logger.warn(argOneFS.getLabel() + " is currently not supported"); } if (argExcludeFrom.isSet()) { logger.warn(argExcludeFrom.getLabel() + " is currently not supported"); } if (argDereference.isSet()) { logger.warn(argDereference.getLabel() + " is currently not supported"); } if (argCountLinks.isSet()) { logger.warn(argCountLinks.getLabel() + " is currently not supported"); } startPoints = argDir.getValues(); if (startPoints.length == 0) { startPoints = new File[] {new File(System.getProperty("user.dir"))}; } if (argFilesystemBlockSize.isSet()) fsBlockSize = argFilesystemBlockSize.getValue(); else { fsBlockSize = getFsBlockSize(startPoints[0]); } if (argBlockSize_Custom.isSet()) { displayBlockSize = argBlockSize_Custom.getValue(); } else if (argBlockSize_1024x1024.isSet()) { displayBlockSize = 1024 * 1024; } else if (argBlockSize_1024.isSet()) { displayBlockSize = 1024; } else if (argBlockSize_1.isSet()) { displayBlockSize = 1; } else { displayBlockSize = DEFAULT_DISPLAY_BLOCK_SIZE; } if (argSum.isSet() || argTotal.isSet()) { long total = 0; for (File start : startPoints) { walker = new Walker(argMaxDepth, argExclude); walker.walk(start); printSize(start.getAbsolutePath(), walker.getSize()); total += walker.getSize(); } if (argTotal.isSet()) { printSize("Total", total); } } else { new Walker(argMaxDepth, argExclude).walk(startPoints); } } private void printFileSize(final File filename, final long size) { if (argAll.isSet()) { out.println(sizeToString(size) + '\t' + filename); } } private void printDirSize(final File filename, final long dirSizeOnly, final long subDirSize) { if (!argSum.isSet()) { if (argSeperateDirs.isSet()) { out.println(sizeToString(dirSizeOnly) + '\t' + filename); } else { out.println(sizeToString(dirSizeOnly + subDirSize) + '\t' + filename); } } } private void printSize(final String filename, final long size) { out.println(sizeToString(size) + '\t' + filename); } private void log(String message) { // logger.debug(message); } /** * should be in NumberUtils I guess * * @param lenght in bytes of the file / directory * @return the number of blocks it uses up depending on the int * displayBlockSize */ private long calc(long bytes) { double factor = fsBlockSize / displayBlockSize; long ret = -1; if (fsBlockSize > displayBlockSize) { if (bytes % displayBlockSize == 0) { ret = bytes / displayBlockSize; } else { ret = (long) ((bytes / (fsBlockSize) + 1) * factor); } } else { if (bytes % displayBlockSize == 0) { ret = bytes / displayBlockSize; } else { ret = (long) ((bytes / (displayBlockSize) + 1)); } } return ret; } private String sizeToString(long size) { String retValue = null; if (argHumanReadable_1024.isSet()) { retValue = NumberUtils.toBinaryByte(size); } else if (argHumanReadable_1000.isSet()) { retValue = NumberUtils.toDecimalByte(size); } else { retValue = String.valueOf(size); } return retValue; } /** * taken from the DfCommand */ private int getFsBlockSize(File file) throws IOException { int retValue = DEFAULT_FILESYSTEM_BLOCK_SIZE; // default block size FileSystemService fss = null; Device device = null; String path = null; String mp = null; try { fss = InitialNaming.lookup(FileSystemService.NAME); path = file.getCanonicalPath(); mp = null; for (String mountPoint : fss.getMountPoints().keySet()) { if (path.startsWith(mountPoint)) { if (mp != null) { if (!mp.startsWith(mountPoint)) { continue; } } mp = mountPoint; } } if (mp != null) { device = fss.getMountPoints().get(mp).getDevice(); if (device instanceof FSBlockDeviceAPI) { retValue = ((FSBlockDeviceAPI) device).getSectorSize(); } else { logger.warn("No FSBlockDeviceAPI device for device: " + device); logger.info("Using default block-size: " + DEFAULT_FILESYSTEM_BLOCK_SIZE); logger.info("override with --fs-block-size"); } } else { logger.warn("No mount point found for " + path); for (String mountPoint : fss.getMountPoints().keySet()) { logger.warn("mountpoints on system: " + mountPoint); } logger.info("Using default block-size: " + DEFAULT_FILESYSTEM_BLOCK_SIZE); logger.info("override with --fs-block-size"); } } catch (NameNotFoundException e) { logger.warn("FileSystemService lookup failed ...", e); logger.info("Using default block-size: " + DEFAULT_FILESYSTEM_BLOCK_SIZE); logger.info("override with --fs-block-size"); } return retValue; } private class Directory { private Directory parent = null; private File directory = null; private Stack<Directory> subDirs = null; private long size = 0; public Directory(Directory parent, File directory) { this.parent = parent; this.directory = directory; subDirs = new Stack<Directory>(); } public Directory addDirectory(File file) { Directory retValue = null; if (file.getParentFile().equals(directory)) { retValue = new Directory(this, file); subDirs.push(retValue); } else { logger.warn("addDirectory: tried to add " + file + " to " + directory); } return retValue; } public void addFile(File file) { if (!file.getParentFile().equals(directory)) { logger.warn("addFile: tried to add " + file + " to " + directory); } printFileSize(file, calc(file.length())); size += calc(file.length()); } public Directory getParent() { long dirSize = size + calc(directory.length()); // only the size for this directory + files // (in other words: excludes the size of subDirs) long subDirSize = 0; while (!subDirs.isEmpty()) { subDirSize += subDirs.pop().getSize(); } printDirSize(directory, dirSize, subDirSize); size = dirSize + subDirSize; return parent; } public long getSize() { return size; } @Override public int hashCode() { return directory.hashCode(); } public boolean equals(Object other) { boolean retValue = false; if (other instanceof Directory) { retValue = (other != null && ((Directory) other).directory.equals(this.directory)); } else if (other instanceof File) { retValue = (other != null && ((File) other).equals(this.directory)); } return retValue; } @Override public String toString() { return directory.toString(); } } private class Walker extends AbstractDirectoryWalker { long totalSize; protected Directory root = null; protected Directory currentDir = null; private Walker(IntegerArgument argMaxDepth, StringArgument argExclude) { super(); if (argMaxDepth.isSet()) { super.setMaxDepth(argMaxDepth.getValue().longValue()); } if (argExclude.isSet()) { super.addFilter(new RegexPatternFilter(argExclude.getValue(), true)); } } public long getSize() { return totalSize; } @Override /** * Set the "root" Directory to the Starting Dir */ protected void handleStartingDir(File file) throws IOException { log("starting dir: " + file); root = new Directory(null, file); } @Override /** * Calculate the "root" directory and reset the "current" directory .. we are done for now */ protected void lastAction(boolean wasCancelled) { root.getParent(); currentDir = null; totalSize = root.getSize(); } @Override /** * add the currently handled directory/file to the correct position in the hierarchy */ public void handleDir(File file) { log("handleDir: " + file); if (currentDir == null || currentDir.equals(file)) { currentDir = root; return; } while (!currentDir.equals(file.getParentFile())) { log("in while"); currentDir = currentDir.getParent(); } currentDir = currentDir.addDirectory(file); } @Override public void handleFile(File file) { log("handleFile: " + file); while (!currentDir.equals(file.getParentFile())) { currentDir = currentDir.getParent(); } currentDir.addFile(file); } @Override protected void handleRestrictedFile(File file) throws IOException { err.format(ERR_PERMISSION, file); } } }