/** * $Id: $ * $Date: $ * */ package org.xmlsh.util; import java.io.File; import java.io.IOException; import java.io.InputStream; import java.nio.ByteBuffer; import java.nio.CharBuffer; import java.nio.charset.Charset; import java.nio.charset.CharsetDecoder; import java.nio.file.FileStore; import java.nio.file.FileSystem; import java.nio.file.FileSystems; import java.nio.file.Files; import java.nio.file.LinkOption; import java.nio.file.Path; import java.nio.file.StandardOpenOption; import java.nio.file.attribute.AclFileAttributeView; import java.nio.file.attribute.BasicFileAttributeView; import java.nio.file.attribute.BasicFileAttributes; import java.nio.file.attribute.DosFileAttributeView; import java.nio.file.attribute.DosFileAttributes; import java.nio.file.attribute.FileAttributeView; import java.nio.file.attribute.FileStoreAttributeView; import java.nio.file.attribute.FileTime; import java.nio.file.attribute.PosixFileAttributeView; import java.nio.file.attribute.PosixFileAttributes; import java.nio.file.attribute.PosixFilePermission; import static java.nio.file.attribute.PosixFilePermission.*; import static org.xmlsh.util.Util.enumSetOf; import java.nio.file.attribute.UserDefinedFileAttributeView; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.Comparator; import java.util.EnumSet; import java.util.HashMap; import java.util.Map; import java.util.Set; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.xmlsh.sh.shell.ShellConstants; public class FileUtils { static volatile Map<java.nio.file.FileStore,Collection<Class<? extends FileAttributeView> > > sSupportedAttributes = new HashMap<>(); static LinkOption[] _pathFollowLinks = new LinkOption[] { LinkOption.NOFOLLOW_LINKS } ; static LinkOption[] _pathNoFollowLinks = new LinkOption[0] ; public static Set<PosixFilePermission> _allRead = enumSetOf( OWNER_READ , GROUP_READ , OTHERS_READ ); public static Set<PosixFilePermission> _allWrite = enumSetOf( OWNER_WRITE , GROUP_WRITE , OTHERS_WRITE ); public static Set<PosixFilePermission> _allExec = enumSetOf( OWNER_EXECUTE , GROUP_EXECUTE , OTHERS_EXECUTE ); static Logger mLogger = LogManager.getLogger(); public static String getNullFilePath() { if(Util.isWindows()) return "NUL" ; else return "/dev/null"; } public static File getNullFile() { return new File( getNullFilePath() ); } public static boolean isNullFile( File file ) { return isNullFilePath(file.getName()) ; } public static boolean isNullFilePath(String file) { return Util.isBlank(file) || file.equals("/dev/null") || (Util.isWindows() && file.equalsIgnoreCase("NUL")); } @SuppressWarnings("unchecked") public static <T extends InputStream> T getInputStream(InputStream stream, Class<T> cls ) { if( stream == null ) return null ; if( cls.isInstance( stream ) ) return (T) stream ; if( stream instanceof SynchronizedInputStream ) return getInputStream( ((SynchronizedInputStream)stream).getStream() , cls ); return null; } public boolean hasConsole() { return System.console() != null ; } /* * Compare paths strictly by name not pathwise * Intended only for simple names not full paths */ public static Comparator<Path> alphaPathComparator() { return new Comparator<Path>(){ @Override public int compare(Path o1, Path o2) { // Default use Path compareT if( o1 == o2 ) return 0; return o1.getFileName().toString().compareTo(o2.getFileName().toString()); } }; } public static Set<PosixFilePermission> getPosixFilePermissions(Path path, LinkOption followLink ) { if( supportsAttributeView(path, PosixFileAttributeView.class)){ try { return Files.getPosixFilePermissions(path, (followLink)); } catch (IOException e) { mLogger.catching(e); } } return emulatePosixFilePermissions(path,followLink); } public static <A extends BasicFileAttributes, V extends BasicFileAttributeView> A getFileAttributes( Path path , Class<A> attrClass , Class<V> viewClass , LinkOption... followLinks) { // Returns null instead of exception if( supportsAttributeView(path, viewClass)){ try { A attrs = Files.readAttributes(path, attrClass, followLinks); if( attrs != null ) return attrs; } catch (IOException e) { mLogger.catching(e); } } return null; } public static LinkOption[] pathLinkOptions(boolean followLinks) { if( followLinks ) return _pathFollowLinks ; else return _pathNoFollowLinks ; } public static PosixFileAttributes getPosixFileAttributes(Path path, LinkOption... followLinks ) { return getFileAttributes(path,PosixFileAttributes.class,PosixFileAttributeView.class,followLinks); } public static BasicFileAttributes getBasicFileAttributes(Path path, LinkOption... followLinks ) { BasicFileAttributes basic = getFileAttributes(path,BasicFileAttributes.class,BasicFileAttributeView.class,followLinks); mLogger.entry(path,followLinks); if( basic == null) { // fake it out -- bug on overlay fs mLogger.debug("No basic atribues - simulating with File methods"); File file = path.toFile(); basic = new BasicFileAttributes() { public FileTime creationTime() { return FileTime.fromMillis(file.lastModified()); } public Object fileKey() { return file.hashCode() ; } public boolean isDirectory() { return file.isDirectory() ; } public boolean isOther() { return ! file.isDirectory() && ! file.isFile() ; } public boolean isRegularFile() { return file.isFile() ; } public boolean isSymbolicLink() { return false ; } public FileTime lastAccessTime() { return creationTime(); } public FileTime lastModifiedTime() { return creationTime(); } public long size() { return file.length() ; } }; } return mLogger.exit(basic) ; } public static DosFileAttributes getDosFileAttributes(Path path, LinkOption... followLinks ) { DosFileAttributes dos = getFileAttributes(path,DosFileAttributes.class,DosFileAttributeView.class,followLinks); return dos ; } public static UnifiedFileAttributes getUnifiedFileAttributes(Path path, LinkOption... followLinks) { return new UnifiedFileAttributes(path, followLinks ); } public static UnifiedFileAttributes getUnifiedFileAttributes(Path path, BasicFileAttributes attrs, LinkOption followLinks) { return new UnifiedFileAttributes(path, attrs , followLinks ); } protected static Set<PosixFilePermission> emulatePosixFilePermissions(Path path, LinkOption... followLinks ) { EnumSet<PosixFilePermission> perms = EnumSet .noneOf(PosixFilePermission.class); if (Files.isReadable(path)) perms.addAll(_allRead); if (Files.isWritable(path)) perms.addAll(_allWrite); if (Files.isExecutable(path)) perms.addAll(_allExec); return perms ; } protected static Set<PosixFilePermission> emulatePosixFilePermissions(DosFileAttributes dos , LinkOption... followLinks ) { Set<PosixFilePermission> perms = EnumSet .noneOf(PosixFilePermission.class); if (!dos.isReadOnly()) perms.addAll(_allWrite); perms.addAll( _allRead ); return Collections.unmodifiableSet(perms); } public static String getSystemTextEncoding() { return System.getProperty("file.encoding"); } public static boolean hasDirectory(String name) { /* Dont use Paths ... it bombs on bad names Path p = Paths.get(name); return p.getNameCount() > 1 ; */ return name.contains( File.separator) || ( Util.isWindows() && name.contains("/")); } public static String convertPath(String name, boolean bSystem) { assert( name!= null); if( bSystem && File.separatorChar != '/') return name.replace('/', File.separatorChar); else return name.replace(File.separatorChar, '/'); } /* * Reverse the conversion of toJavaPath */ public static String fromJavaPath( String path ) { if( path == null ) return null; if( File.separatorChar != '/') return path.replace('/' , File.separatorChar); else return path; } /** * Convert a Path or name in DOS format to Java format * This means converting \ to / */ public static String toJavaPath( Path path ) { mLogger.entry(path); if( path == null ) return ""; return toJavaPath( path.toString()); } public static String toJavaPath( String path ) { if( path == null ) return null; if( File.separatorChar != '/') return path.replace(File.separatorChar, '/'); else return path; } // Return the number of chars that include the root part of a path // Include windows drive: // Assumes java path format and dont try to convert path to a NIO Path public static int rootPathLength(String path) { if( Util.isBlank(path)) return 0; if( ! isFilesystemCaseSensitive() ) path = path.toLowerCase(); FileSystem fs = FileSystems.getDefault(); for( Path root : fs.getRootDirectories() ){ String sr = toJavaPath(root.toString()); if( ! isFilesystemCaseSensitive() ) sr = sr.toLowerCase(); if( path.startsWith(sr)) return sr.length(); } return 0; } static FileSystem getFileSystem( Path path ){ return FileSystems.getFileSystem(path.toUri()); } /* * Special function that would return basename without extension if this is path-like * but otherwise still does something useful - dont use if you know the string is really a path */ public static String basePathLikeName(String path) { path = getPathLikeName(path); int startpos = 0 ; int dotpos = path.indexOf(ShellConstants.kDOT_CHAR, startpos); if( dotpos < 0 ) dotpos = path.length(); return path.substring(startpos,dotpos); } // Take a path like string and return just the name.ext component public static String getPathLikeName(String path) { if( Util.isBlank(path)) return "" ; int startpos = 0; // get rid of any windowy drive paths and leading /s int rlen = FileUtils.rootPathLength(path); if( rlen > 0 ) startpos = rlen; int slashpos = path.lastIndexOf('/'); int slashpos2 = (File.separatorChar != '/' ) ? path.lastIndexOf( File.separatorChar ) : -1 ; slashpos = Math.max(slashpos, slashpos2); if( slashpos > startpos ) startpos = slashpos + 1 ; if( startpos >= rlen ) return path.substring(startpos ); return ""; } public static boolean supportsAttributeView( Path path , Class<? extends FileAttributeView> view ) { try { return supportsAttributeView( Files.getFileStore(path) , view ); } catch (IOException e) { mLogger.trace("Catching:",e); } return false ; } public static boolean supportsAttributeView( File file , Class<? extends FileAttributeView> view ){ Path path = asValidPath(file); if( path == null ) return false ; try { return supportsAttributeView( Files.getFileStore(path) , view ); } catch (IOException e) { mLogger.trace("Catching:",e); } return false ; } public static Path asValidPath( File file ){ if( file == null ) return null ; try { return file.toPath(); } catch(java.nio.file.InvalidPathException e ){ mLogger.trace("Invalid path: " , e ); return null ; } } public static <V extends FileAttributeView> V getAttributeView( Path path , Class<V> view ) throws IOException { return Files.getFileAttributeView(path, view ); } public static boolean supportsAttributeView( FileStore store , Class<? extends FileAttributeView> view ) { if( store == null ) return false; Collection<Class<? extends FileAttributeView> > set = sSupportedAttributes.get(store); if( set == null ){ set = sSupportedAttributes.get(store); if( set == null ){ set = new ArrayList< Class<? extends FileAttributeView> >(); if( store.supportsFileAttributeView(FileAttributeView.class)) set.add(FileAttributeView.class); if( store.supportsFileAttributeView(BasicFileAttributeView.class)) set.add(BasicFileAttributeView.class); if( store.supportsFileAttributeView(AclFileAttributeView.class)) set.add(AclFileAttributeView.class); if( store.supportsFileAttributeView(UserDefinedFileAttributeView.class)) set.add(UserDefinedFileAttributeView.class); if( store.supportsFileAttributeView(PosixFileAttributeView.class)) set.add(PosixFileAttributeView.class); if( store.supportsFileAttributeView(DosFileAttributeView.class)) set.add(DosFileAttributeView.class); } synchronized( sSupportedAttributes ){ sSupportedAttributes.put( store , set ); } } return set.contains(view); } /* * Return OS localized extension or "" * -- tolower if on case insensitive filesystems * foo.bar => .bar * /foobaar/xyz => "" * .foobar => "" * /foo/bar/.bar =>"" */ public static String getExt(String name) { mLogger.entry(name); name = getPathLikeName( name ); // Try the hard way. int dotpos = name.lastIndexOf(ShellConstants.kDOT_CHAR); if( dotpos > 0 && dotpos < name.length() ) // ".xyz" not an extension return mLogger.exit(name.substring(dotpos )); return mLogger.exit(""); } /* IsHidden by name only - do Not check file attributes */ public static boolean isHiddenName(Path path) { if( path == null ) return true ; return path.getFileName().toString().startsWith("."); } public static boolean isSystem(Path path) { DosFileAttributes view = getDosFileAttributes(path); if( view == null ) return false ; return view.isSystem(); } /* * Our own version of a FileTreeWalker that is sortable and doesnt follow links */ public static <V extends IPathTreeVisitor> void walkPathTree( Path start , boolean recursive , V visitor, PathMatchOptions options ) throws IOException { (new PathTreeWalker( start , recursive , options )).walk(visitor); } public static boolean isFilesystemCaseSensitive() { boolean bIsWindows = Util.isWindows(); boolean caseSensitive = !bIsWindows; return caseSensitive ; } /* * Guess the file type for purposes of script or cmd execution * if bScripty then any texty like thing will do */ public static boolean isXScript( Path path , boolean bScripty , String encoding ){ String ext = getExt(path.toString()); if( Util.isEqual( ext, ShellConstants.XSH_EXTENSION ) ) return true ; if( Util.isBlank(ext) ){ String line = getTextFileMagic( path , encoding ); if( line != null ){ if( bScripty ) return true ; if( line.startsWith("#!") ){ line=basePathLikeName(line.substring(2)); if( Util.isEqual(line,"xmlsh",true) ) return true ; } } return false ; } return false ; } public static String getTextFileMagic(Path path ,String encoding ){ mLogger.entry(path,encoding); try ( InputStream is = Files.newInputStream(path, StandardOpenOption.READ ) ){ byte data[] = new byte[1024]; long len = is.read(data); if( len <= 0 ) return null ; java.nio.ByteBuffer bb = ByteBuffer.wrap(data,0,(int)len); CharsetDecoder decoder= Charset.forName(encoding).newDecoder(); CharBuffer ret = decoder.decode(bb); if( ret.length() <= 0) return null ; // Look for some alpaha or reserved word int good = 0; int max = Math.min( 100 , ret.length() ); int p = 0; for( char c : ret.array() ){ p++; if( good > max ) return mLogger.exit("text"); if( !Character.isDefined(c) ) return mLogger.exit(null) ; switch( c ){ case '\0' : return mLogger.exit(null) ; case '\n' : case '\r' : if( good > 2 ) return ret.subSequence(0, p).toString(); case '\t' : case '\b' : case '\f' : case '#' : case '!' : case '-' : case '_' : case '(' : case ')' : case '{' : case '}' : case '|' : case '"' : case '\'': case '[' : case ']' : case '=' : case ';' : good++; continue; } switch( Character.getType(c)){ case Character.CONNECTOR_PUNCTUATION : case Character.CURRENCY_SYMBOL : case Character. DIRECTIONALITY_PARAGRAPH_SEPARATOR : case Character.LINE_SEPARATOR : case Character.INITIAL_QUOTE_PUNCTUATION : case Character.ENCLOSING_MARK : case Character.OTHER_PUNCTUATION : good++; continue; } if( Character.isJavaIdentifierPart(c)|| Character.isJavaIdentifierStart(c)|| Character.isLetterOrDigit(c) || Character.isUnicodeIdentifierStart(c) || Character.isUnicodeIdentifierPart(c) ) good++; else if( ! Character.isWhitespace(c)){ if( Character.isISOControl(c) ) return good > 20 ? ret.subSequence(0, p).toString() : null ; good = 0; } } if( good > 0 ) return mLogger.exit("text"); } catch (Exception e) { return mLogger.exit(null) ; } return mLogger.exit(null ) ; } public static void changeFilePermissions(Path path, Set<PosixFilePermission> change , LinkOption... links ) { changeFilePermissions( path , getUnifiedFileAttributes(path, links) , change , links ); } public static void changeFilePermissions(Path path, UnifiedFileAttributes orig, Set<PosixFilePermission> change , LinkOption... links) { mLogger.entry(path,change); if( supportsAttributeView(path, PosixFileAttributeView.class)){ mLogger.debug("using PosixFileAttributeView" ); try { Files.setPosixFilePermissions(path, change); } catch (IOException e) { mLogger.catching(e); } } if( supportsAttributeView(path, DosFileAttributeView.class)){ mLogger.debug("supports DosFileAttributeView" ); try { DosFileAttributeView dosView = getAttributeView( path , DosFileAttributeView.class ); if( dosView != null ){ mLogger.debug("using DosFileAttributeView"); DosFileAttributes origView = orig.getDos(); boolean anyWrite = Util.setContainsAny( change , _allWrite ); boolean origRO = origView.isReadOnly(); if( !anyWrite != origRO ) dosView.setReadOnly(!anyWrite); } else { mLogger.error("using File" ); boolean anyWrite = Util.setContainsAny( change , _allWrite ); boolean anyRead = Util.setContainsAny( change , _allRead ); File f = path.toFile(); if( !anyWrite && ! orig.isReadOnly() ) f.setReadOnly( ); if( anyWrite != orig.canWrite() ) f.setWritable( anyWrite ); if( anyRead != f.canRead()) f.setReadable(anyRead); boolean anyExec = Util.setContainsAny( change , _allExec); if( anyExec != orig.canExecute() ) f.setExecutable( anyExec ); } } catch (IOException e) { mLogger.catching(e); } } return ; } public static Path resolveLink(Path p) { if( p == null ) return null ; p = p.normalize(); try { p=p.toRealPath(_pathFollowLinks); if( Files.isSymbolicLink(p )) return Files.readSymbolicLink(p ); } catch( IOException e){ mLogger.catching(e); } return p; } public static Path getParent(Path p) { if( p != null ){ File pf = p.toFile(); if( pf != null ){ pf = pf.getParentFile(); if( pf != null ) return pf.toPath(); } } return null; } } /* * Copyright (C) 2008-2012 David A. Lee. * * The contents of this file are subject to the "Simplified BSD License" (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.opensource.org/licenses/bsd-license.php * Software distributed under the License is distributed on an "AS IS" basis, * WITHOUT WARRANTY OF ANY KIND, either express or implied. * See the License for the specific language governing rights and limitations under the License. * * The Original Code is: all this file. * * The Initial Developer of the Original Code is David A. Lee * * Portions created by (your name) are Copyright (C) (your legal entity). All Rights Reserved. * * Contributor(s): David A. Lee * */