/* * Copyright (C) 2012 The CyanogenMod Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.cyanogenmod.filemanager.util; import com.cyanogenmod.filemanager.model.BlockDevice; import com.cyanogenmod.filemanager.model.CharacterDevice; import com.cyanogenmod.filemanager.model.Directory; import com.cyanogenmod.filemanager.model.DiskUsage; import com.cyanogenmod.filemanager.model.DomainSocket; import com.cyanogenmod.filemanager.model.FileSystemObject; import com.cyanogenmod.filemanager.model.Group; import com.cyanogenmod.filemanager.model.GroupPermission; import com.cyanogenmod.filemanager.model.MountPoint; import com.cyanogenmod.filemanager.model.NamedPipe; import com.cyanogenmod.filemanager.model.OthersPermission; import com.cyanogenmod.filemanager.model.Permission; import com.cyanogenmod.filemanager.model.Permissions; import com.cyanogenmod.filemanager.model.RegularFile; import com.cyanogenmod.filemanager.model.Symlink; import com.cyanogenmod.filemanager.model.User; import com.cyanogenmod.filemanager.model.UserPermission; import java.io.File; import java.text.ParseException; import java.util.Date; /** * A helper class with useful methods for deal with parse of results. */ public final class ParseHelper { // The structure of a terse stat output // http://mailman.lug.org.uk/pipermail/nottingham/2007-January/009303.html private static enum TERSE_STAT_STRUCT { FILENAME, SIZE, BLOCKS, RAW_MODE, UID, GID, DEVICE, INODE, HARD_LINKS, MAJOR_DEVICE_TYPE, MINOR_DEVICE_TYPE, ACCESS, MODIFY, CHANGE, IOBLOCK } private static int TERSE_STAT_STRUCT_LENGTH = TERSE_STAT_STRUCT.values().length; // The structure of raw mode in hex format (defined with octal values) // http://unix.stackexchange.com/questions/39716/what-is-raw-mode-in-hex-from-stat-output private static enum RMIHF { S_IFMT (0170000), //bit mask for the file type bit fields S_IFSOCK (0140000), //socket S_IFLNK (0120000), //symbolic link S_IFREG (0100000), //regular file S_IFBLK (0060000), //block device S_IFDIR (0040000), //directory S_IFCHR (0020000), //character device S_IFIFO (0010000), //FIFO S_ISUID (0004000), //set UID bit S_ISGID (0002000), //set-group-ID bit (see below) S_ISVTX (0001000), //sticky bit (see below) S_IRWXU (0000700), //mask for file owner permissions S_IRUSR (0000400), //owner has read permission S_IWUSR (0000200), //owner has write permission S_IXUSR (0000100), //owner has execute permission S_IRWXG (0000070), //mask for group permissions S_IRGRP (0000040), //group has read permission S_IWGRP (0000020), //group has write permission S_IXGRP (0000010), //group has execute permission S_IRWXO (0000007), //mask for permissions for others (not in group) S_IROTH (0000004), //others have read permission S_IWOTH (0000002), //others have write permission S_IXOTH (0000001); //others have execute permission final int mValue; RMIHF(int value) { this.mValue = value; } } /** * Constructor of <code>ParseHelper</code>. */ private ParseHelper() { super(); } /** * Method that parses the output of a terse stat command.<br/> * <br/> * The stat terse format is described as:<br/> * <br/> * <code/> * terse format = "%n %s %b %f %u %g %D %i %h %t %T %X %Y %Z %o": * filename * size(bytes) * blocks * Raw_mode(HEX) * Uid * Gid * Device(HEX) * Inode * hard_links * major_device_type(HEX) * minor_device_type(HEX) * Access(Epoch seconds) * Modify(Epoch seconds) * Change(Epoch seconds) * IOblock * </code> * * @param output Line with the output of a line of a stat command * @return FileSystemObject The file system object reference * @throws ParseException If the permissions can't be parsed * @{link "http://www.gnu.org/software/coreutils/manual/html_node/stat-invocation.html"} */ public static FileSystemObject parseStatOutput(final String output) throws ParseException { try { // Split the terse line String[] data = output.split(" "); //$NON-NLS-1$ boolean valid = true; try { getTerseStatInt(data, TERSE_STAT_STRUCT.IOBLOCK); } catch (Exception e) { valid = false; } if (valid && output.startsWith("stat:")) { //$NON-NLS-1$ throw new ParseException( String.format("Stat failed: %s", output), 0); //$NON-NLS-1$ } if (valid && data.length < TERSE_STAT_STRUCT.values().length) { throw new ParseException( String.format("Not enought data: %s", output), 0); //$NON-NLS-1$ } // Parse the line String raw = getTerseRawPermissions(data); char type = raw.charAt(0); Permissions permissions = parsePermission(raw); Date lastAccessedTime = getTerseStatDate(data, TERSE_STAT_STRUCT.ACCESS); Date lastModifiedTime = getTerseStatDate(data, TERSE_STAT_STRUCT.MODIFY); Date lastChangedTime = getTerseStatDate(data, TERSE_STAT_STRUCT.CHANGE); int uid = getTerseStatInt(data, TERSE_STAT_STRUCT.UID); User user = new User(uid, AIDHelper.getNullSafeName(uid)); int gid = getTerseStatInt(data, TERSE_STAT_STRUCT.GID); Group group = new Group(gid, AIDHelper.getNullSafeName(gid)); long size = getTerseStatLong(data, TERSE_STAT_STRUCT.SIZE); File file = new File(getTerseStatName(data)); String name = file.getName(); if (name.trim().length() == 0) { name = FileHelper.ROOT_DIRECTORY; } String parentDir = FileHelper.getParentDir(file); // Create the file system object FileSystemObject fso = createObject( parentDir, type, name, null, user, group, permissions, size, lastAccessedTime, lastModifiedTime, lastChangedTime); // Check if its a symlink if (type == Symlink.UNIX_ID) { // Extract the ref info Symlink symlink = (Symlink)fso; File refFile = file.getCanonicalFile(); char refType = refFile.isDirectory() ? Directory.UNIX_ID : RegularFile.UNIX_ID; String refName = refFile.getName(); String refParentDir = FileHelper.getParentDir(refFile); Date refLastModifiedTime = new Date(refFile.lastModified()); long refSize = refFile.length(); // Create the ref file system object FileSystemObject refFso = createObject( refParentDir, refType, refName, null, null, null, null, refSize, null, refLastModifiedTime, null); // Update the symlink ref symlink.setLink(refParentDir); symlink.setLinkRef(refFso); } // Parsed return fso; } catch (Exception ex) { // Notify the exception when parsing the data throw new ParseException(ex.getMessage(), 0); } } /** * Method that parses and extracts the permissions from a unix string format. * * @param permissions The raw permissions * @return Permissions An object with all the permissions * @throws ParseException If the permissions can't be parsed * @{link "http://en.wikipedia.org/wiki/File_system_permissions"} */ public static Permissions parsePermission(String permissions) throws ParseException { if (permissions.length() != 10) { throw new ParseException("permission length() != 10", 0); //$NON-NLS-1$ } UserPermission up = new UserPermission( permissions.charAt(1) == Permission.READ, permissions.charAt(2) == Permission.WRITE, permissions.charAt(3) == Permission.EXECUTE || permissions.charAt(3) == UserPermission.SETUID_E, permissions.charAt(3) == UserPermission.SETUID_E || permissions.charAt(3) == UserPermission.SETUID); GroupPermission gp = new GroupPermission( permissions.charAt(4) == Permission.READ, permissions.charAt(5) == Permission.WRITE, permissions.charAt(6) == Permission.EXECUTE || permissions.charAt(6) == GroupPermission.SETGID_E, permissions.charAt(6) == GroupPermission.SETGID_E || permissions.charAt(6) == GroupPermission.SETGID); OthersPermission op = new OthersPermission( permissions.charAt(7) == Permission.READ, permissions.charAt(8) == Permission.WRITE, permissions.charAt(9) == Permission.EXECUTE || permissions.charAt(9) == OthersPermission.STICKY_E, permissions.charAt(9) == OthersPermission.STICKY_E || permissions.charAt(9) == OthersPermission.STICKY); return new Permissions(up, gp, op); } /** * Method that parse a disk usage line. * * @param src The disk usage line * @return DiskUsage The disk usage information * @throws ParseException If the line can't be parsed */ public static DiskUsage toDiskUsage(final String src) throws ParseException { // Filesystem Size Used Free Blksize // /dev 414M 48K 414M 4096 // /mnt/asec 414M 0K 414M 4096 // /mnt/secure/asec: Permission denied try { final int fields = 5; //Permission denied or invalid statistics if (src.indexOf(":") != -1) { //$NON-NLS-1$ throw new ParseException(String.format("Non allowed: %s", src), 0); //$NON-NLS-1$ } //Extract all the info String line = src; String[] data = new String[fields]; for (int i = 0; i < fields; i++) { int pos = line.indexOf(" "); //$NON-NLS-1$ data[i] = line.substring(0, pos != -1 ? pos : line.length()); if (pos != -1) { line = line.substring(pos).trim(); } } //Return the disk usage return new DiskUsage(data[0], toBytes(data[1]), toBytes(data[2]), toBytes(data[3])); } catch (Exception e) { throw new ParseException(e.getMessage(), 0); } } /** * Method that parse a {@link "/proc/mounts"} line. * * @param src The mount point line * @return MountPoint The mount point information * @throws ParseException If the line can't be parsed */ public static MountPoint toMountPoint(final String src) throws ParseException { // rootfs / rootfs ro,relatime 0 0 // tmpfs /dev tmpfs rw,nosuid,relatime,mode=755 0 0 // devpts /dev/pts devpts rw,relatime,mode=600 0 0 // /dev/block/vold/179:25 /mnt/emmc vfat rw,dirsync,nosuid,nodev,noexec,relatime,uid=1000, gid=1015,fmask=0702,dmask=0702,allow_utime=0020,codepage=cp437,iocharset=iso8859-1, shortname=mixed,utf8,errors=remount-ro 0 0 try { //Extract all the info String line = src; int pos = line.lastIndexOf(" "); //$NON-NLS-1$ int pass = Integer.parseInt(line.substring(pos + 1)); line = line.substring(0, pos).trim(); pos = line.lastIndexOf(" "); //$NON-NLS-1$ int dump = Integer.parseInt(line.substring(pos + 1)); line = line.substring(0, pos).trim(); pos = line.indexOf(" "); //$NON-NLS-1$ String device = line.substring(0, pos).trim(); line = line.substring(pos).trim(); pos = line.lastIndexOf(" "); //$NON-NLS-1$ String options = line.substring(pos + 1).trim(); line = line.substring(0, pos).trim(); pos = line.lastIndexOf(" "); //$NON-NLS-1$ String type = line.substring(pos + 1).trim(); String mountPoint = line.substring(0, pos).trim(); //Return the mount point return new MountPoint(mountPoint, device, type, options, dump, pass); } catch (Exception e) { throw new ParseException(e.getMessage(), 0); } } /** * Method that creates the appropriate file system object. * * @param parentDir The parent directory * @param type The raw char type of the file system object * @param name The name of the object * @param link The real file that this symlink is point to * @param user The user proprietary of the object * @param group The group proprietary of the object * @param permissions The permissions of the object * @param size The size in bytes of the object * @param lastAccessedTime The last time that the object was accessed * @param lastModifiedTime The last time that the object was modified * @param lastChangedTime The last time that the object was changed * @return FileSystemObject The file system object reference * @throws ParseException If type couldn't be translate into a reference * file system object */ private static FileSystemObject createObject( String parentDir, char type, String name, String link, User user, Group group, Permissions permissions, long size, Date lastAccessedTime, Date lastModifiedTime, Date lastChangedTime) throws ParseException { String parent = (parentDir == null && name.compareTo(FileHelper.ROOT_DIRECTORY) != 0) ? FileHelper.ROOT_DIRECTORY : parentDir; if (type == RegularFile.UNIX_ID) { return new RegularFile( name, parent, user, group, permissions, size, lastAccessedTime, lastModifiedTime, lastChangedTime); } if (type == Directory.UNIX_ID) { return new Directory(name, parent, user, group, permissions, lastAccessedTime, lastModifiedTime, lastChangedTime); } if (type == Symlink.UNIX_ID) { return new Symlink(name, link, parent, user, group, permissions, lastAccessedTime, lastModifiedTime, lastChangedTime); } if (type == BlockDevice.UNIX_ID) { return new BlockDevice(name, parent, user, group, permissions, lastAccessedTime, lastModifiedTime, lastChangedTime); } if (type == CharacterDevice.UNIX_ID) { return new CharacterDevice(name, parent, user, group, permissions, lastAccessedTime, lastModifiedTime, lastChangedTime); } if (type == NamedPipe.UNIX_ID) { return new NamedPipe(name, parent, user, group, permissions, lastAccessedTime, lastModifiedTime, lastChangedTime); } if (type == DomainSocket.UNIX_ID) { return new DomainSocket(name, parent, user, group, permissions, lastAccessedTime, lastModifiedTime, lastChangedTime); } throw new ParseException("no file system object", 0); //$NON-NLS-1$ } /** * Method that converts to bytes the string representation * of a size (10M, 1G, 0K, ...). * * @param size The size as a string representation * @return long The size in bytes */ private static long toBytes(String size) { double bytes = Double.parseDouble(size.substring(0, size.length() - 1)); String unit = size.substring(size.length() - 1); if (unit.compareToIgnoreCase("G") == 0) { //$NON-NLS-1$ return (long)(bytes * 1024 * 1024 * 1024); } if (unit.compareToIgnoreCase("M") == 0) { //$NON-NLS-1$ return (long)(bytes * 1024 * 1024); } if (unit.compareToIgnoreCase("K") == 0) { //$NON-NLS-1$ return (long)(bytes * 1024); } //Don't touch return (long)bytes; } /** * Method that extract a date from a terse stat ouput. * * @param stat The terse stat data * @param e The position of the date * @return Date The date */ private static Date getTerseStatDate(String[] stat, TERSE_STAT_STRUCT e) { int cc = stat.length; return new Date( Long.parseLong(stat[cc - (TERSE_STAT_STRUCT_LENGTH - e.ordinal())]) * 1000L); } /** * Method that extract a integer value from a terse stat ouput. * * @param stat The terse stat data * @param e The position of the date * @return int The integer value */ private static int getTerseStatInt(String[] stat, TERSE_STAT_STRUCT e) { int cc = stat.length; return Integer.parseInt(stat[cc - (TERSE_STAT_STRUCT_LENGTH - e.ordinal())]); } /** * Method that extract a long value from a terse stat ouput. * * @param stat The terse stat data * @param e The position of the date * @return long The long value */ private static long getTerseStatLong(String[] stat, TERSE_STAT_STRUCT e) { int cc = stat.length; return Long.parseLong(stat[cc - (TERSE_STAT_STRUCT_LENGTH - e.ordinal())]); } /** * Method that returns the name of file * * @param stat The terse stat data * @return String The name of file */ private static String getTerseStatName(String[] stat) { int cc = stat.length; int to = cc - (TERSE_STAT_STRUCT_LENGTH - TERSE_STAT_STRUCT.SIZE.ordinal()); StringBuilder sb = new StringBuilder(); for (int i = 0; i < to; i++) { sb.append(stat[i]); if (i < to-1) { sb.append(" "); //$NON-NLS-1$ } } return sb.toString(); } /** * Method that retrieve the raw string with the permissions. * * @param stat The terse stat data * @return String The raw string */ private static String getTerseRawPermissions(String[] stat) { int cc = stat.length; int rawInt = Integer.parseInt( stat[cc - (TERSE_STAT_STRUCT_LENGTH - TERSE_STAT_STRUCT.RAW_MODE.ordinal())],16); // Extract the type char t = RegularFile.UNIX_ID; if (RMIHF.S_IFSOCK.mValue == (rawInt & RMIHF.S_IFSOCK.mValue)) { t = DomainSocket.UNIX_ID; } else if (RMIHF.S_IFLNK.mValue == (rawInt & RMIHF.S_IFLNK.mValue)) { t = Symlink.UNIX_ID; } else if (RMIHF.S_IFREG.mValue == (rawInt & RMIHF.S_IFREG.mValue)) { t = RegularFile.UNIX_ID; } else if (RMIHF.S_IFBLK.mValue == (rawInt & RMIHF.S_IFBLK.mValue)) { t = BlockDevice.UNIX_ID; } else if (RMIHF.S_IFDIR.mValue == (rawInt & RMIHF.S_IFDIR.mValue)) { t = Directory.UNIX_ID; } else if (RMIHF.S_IFCHR.mValue == (rawInt & RMIHF.S_IFCHR.mValue)) { t = CharacterDevice.UNIX_ID; } else if (RMIHF.S_IFIFO.mValue == (rawInt & RMIHF.S_IFIFO.mValue)) { t = NamedPipe.UNIX_ID; } // Extract User/Group/Others boolean us = RMIHF.S_ISUID.mValue == (rawInt & RMIHF.S_ISUID.mValue); boolean ur = RMIHF.S_IRUSR.mValue == (rawInt & RMIHF.S_IRUSR.mValue); boolean uw = RMIHF.S_IWUSR.mValue == (rawInt & RMIHF.S_IWUSR.mValue); boolean ux = RMIHF.S_IXUSR.mValue == (rawInt & RMIHF.S_IXUSR.mValue); boolean gs = RMIHF.S_ISGID.mValue == (rawInt & RMIHF.S_ISGID.mValue); boolean gr = RMIHF.S_IRGRP.mValue == (rawInt & RMIHF.S_IRGRP.mValue); boolean gw = RMIHF.S_IWGRP.mValue == (rawInt & RMIHF.S_IWGRP.mValue); boolean gx = RMIHF.S_IXGRP.mValue == (rawInt & RMIHF.S_IXGRP.mValue); boolean os = RMIHF.S_ISVTX.mValue == (rawInt & RMIHF.S_ISVTX.mValue); boolean or = RMIHF.S_IROTH.mValue == (rawInt & RMIHF.S_IROTH.mValue); boolean ow = RMIHF.S_IWOTH.mValue == (rawInt & RMIHF.S_IWOTH.mValue); boolean ox = RMIHF.S_IXOTH.mValue == (rawInt & RMIHF.S_IXOTH.mValue); // Build the raw string StringBuilder sb = new StringBuilder(); sb.append(t); sb.append(ur ? Permission.READ : Permission.UNASIGNED); sb.append(uw ? Permission.WRITE : Permission.UNASIGNED); sb.append(us ? (ux ? UserPermission.SETUID_E : UserPermission.SETUID) : (ux ? Permission.EXECUTE : Permission.UNASIGNED)); sb.append(gr ? Permission.READ : Permission.UNASIGNED); sb.append(gw ? Permission.WRITE : Permission.UNASIGNED); sb.append(gs ? (gx ? GroupPermission.SETGID_E : GroupPermission.SETGID) : (gx ? Permission.EXECUTE : Permission.UNASIGNED)); sb.append(or ? Permission.READ : Permission.UNASIGNED); sb.append(ow ? Permission.WRITE : Permission.UNASIGNED); sb.append(os ? (ox ? OthersPermission.STICKY_E : OthersPermission.STICKY) : (ox ? Permission.EXECUTE : Permission.UNASIGNED)); return sb.toString(); } }