/*
|
* Copyright 2013 gitblit.com.
|
*
|
* 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.gitblit.utils;
|
|
import java.io.BufferedReader;
|
import java.io.File;
|
import java.io.IOException;
|
import java.io.InputStreamReader;
|
import java.util.regex.Matcher;
|
import java.util.regex.Pattern;
|
|
import org.slf4j.Logger;
|
import org.slf4j.LoggerFactory;
|
|
import com.sun.jna.Library;
|
import com.sun.jna.Native;
|
|
/**
|
* Collection of static methods to access native OS library functionality.
|
*
|
* @author Florian Zschocke
|
*/
|
public class JnaUtils {
|
public static final int S_ISUID = 0004000; // set user id on execution
|
public static final int S_ISGID = 0002000; // set group id on execution
|
public static final int S_ISVTX = 0001000; // sticky bit, save swapped text even after use
|
|
public static final int S_IRWXU = 0000700; // RWX mask for owner
|
public static final int S_IRUSR = 0000400; // read permission for owner
|
public static final int S_IWUSR = 0000200; // write permission for owner
|
public static final int S_IXUSR = 0000100; // execute/search permission for owner
|
public static final int S_IRWXG = 0000070; // RWX mask for group
|
public static final int S_IRGRP = 0000040; // read permission for group
|
public static final int S_IWGRP = 0000020; // write permission for group
|
public static final int S_IXGRP = 0000010; // execute/search permission for group
|
public static final int S_IRWXO = 0000007; // RWX mask for other
|
public static final int S_IROTH = 0000004; // read permission for other
|
public static final int S_IWOTH = 0000002; // write permission for other
|
public static final int S_IXOTH = 0000001; // execute/search permission for other
|
|
public static final int S_IFMT = 0170000; // type of file mask
|
public static final int S_IFIFO = 0010000; // named pipe (fifo)
|
public static final int S_IFCHR = 0020000; // character special device
|
public static final int S_IFDIR = 0040000; // directory
|
public static final int S_IFBLK = 0060000; // block special device
|
public static final int S_IFREG = 0100000; // regular file
|
public static final int S_IFLNK = 0120000; // symbolic link
|
public static final int S_IFSOCK = 0140000; // socket
|
|
|
private static final Logger LOGGER = LoggerFactory.getLogger(JGitUtils.class);
|
|
private static UnixCLibrary unixlibc = null;
|
|
|
/**
|
* Utility method to check if the JVM is running on a Windows OS.
|
*
|
* @return true, if the system property 'os.name' starts with 'Windows'.
|
*/
|
public static boolean isWindows()
|
{
|
return System.getProperty("os.name").toLowerCase().startsWith("windows");
|
}
|
|
|
private interface UnixCLibrary extends Library {
|
public int chmod(String path, int mode);
|
public int getgid();
|
public int getegid();
|
}
|
|
|
public static int getgid()
|
{
|
if (isWindows()) {
|
throw new UnsupportedOperationException("The method JnaUtils.getgid is not supported under Windows.");
|
}
|
|
return getUnixCLibrary().getgid();
|
}
|
|
|
public static int getegid()
|
{
|
if (isWindows()) {
|
throw new UnsupportedOperationException("The method JnaUtils.getegid is not supported under Windows.");
|
}
|
|
return getUnixCLibrary().getegid();
|
}
|
|
|
/**
|
* Set the permission bits of a file.
|
*
|
* The permission bits are set to the provided mode. This method is only
|
* implemented for OSes of the Unix family and makes use of the 'chmod'
|
* function of the native C library. See 'man 2 chmod' for more information.
|
*
|
* @param path
|
* File/directory to set the permission bits for.
|
* @param mode
|
* A mode created from or'd permission bit masks S_I*
|
* @return Upon successful completion, a value of 0 returned. Otherwise, a value of -1 is returned.
|
*/
|
public static int setFilemode(File file, int mode)
|
{
|
return setFilemode(file.getAbsolutePath(), mode);
|
}
|
|
/**
|
* Set the permission bits of a file.
|
*
|
* The permission bits are set to the provided mode. This method is only
|
* implemented for OSes of the Unix family and makes use of the 'chmod'
|
* function of the native C library. See 'man 2 chmod' for more information.
|
*
|
* @param path
|
* Path to a file/directory to set the permission bits for.
|
* @param mode
|
* A mode created from or'd permission bit masks S_I*
|
* @return Upon successful completion, a value of 0 returned. Otherwise, a value of -1 is returned.
|
*/
|
public static int setFilemode(String path, int mode)
|
{
|
if (isWindows()) {
|
throw new UnsupportedOperationException("The method JnaUtils.getFilemode is not supported under Windows.");
|
}
|
|
return getUnixCLibrary().chmod(path, mode);
|
}
|
|
|
|
/**
|
* Get the file mode bits of a file.
|
*
|
* This method is only implemented for OSes of the Unix family. It returns the file mode
|
* information as available in the st_mode member of the resulting struct stat when calling
|
* 'lstat' on a file.
|
*
|
* @param path
|
* File/directory to get the file mode from.
|
* @return Upon successful completion, the file mode bits are returned. Otherwise, a value of -1 is returned.
|
*/
|
public static int getFilemode(File path)
|
{
|
return getFilemode(path.getAbsolutePath());
|
}
|
|
/**
|
* Get the file mode bits of a file.
|
*
|
* This method is only implemented for OSes of the Unix family. It returns the file mode
|
* information as available in the st_mode member of the resulting struct stat when calling
|
* 'lstat' on a file.
|
*
|
* @param path
|
* Path to a file/directory to get the file mode from.
|
* @return Upon successful completion, the file mode bits are returned. Otherwise, a value of -1 is returned.
|
*/
|
public static int getFilemode(String path)
|
{
|
if (isWindows()) {
|
throw new UnsupportedOperationException("The method JnaUtils.getFilemode is not supported under Windows.");
|
}
|
|
Filestat stat = getFilestat(path);
|
if ( stat == null ) return -1;
|
return stat.mode;
|
}
|
|
|
/**
|
* Status information of a file.
|
*/
|
public static class Filestat
|
{
|
public int mode; // file mode, permissions, type
|
public int uid; // user Id of owner
|
public int gid; // group Id of owner
|
|
Filestat(int mode, int uid, int gid) {
|
this.mode = mode; this.uid = uid; this.gid = gid;
|
}
|
}
|
|
|
/**
|
* Get Unix file status information for a file.
|
*
|
* This method is only implemented for OSes of the Unix family. It returns file status
|
* information for a file. Currently this is the file mode, the user id and group id of the owner.
|
*
|
* @param path
|
* File/directory to get the file status from.
|
* @return Upon successful completion, a Filestat object containing the file information is returned.
|
* Otherwise, null is returned.
|
*/
|
public static Filestat getFilestat(File path)
|
{
|
return getFilestat(path.getAbsolutePath());
|
}
|
|
|
/**
|
* Get Unix file status information for a file.
|
*
|
* This method is only implemented for OSes of the Unix family. It returns file status
|
* information for a file. Currently this is the file mode, the user id and group id of the owner.
|
*
|
* @param path
|
* Path to a file/directory to get the file status from.
|
* @return Upon successful completion, a Filestat object containing the file information is returned.
|
* Otherwise, null is returned.
|
*/
|
public static Filestat getFilestat(String path)
|
{
|
if (isWindows()) {
|
throw new UnsupportedOperationException("The method JnaUtils.getFilestat is not supported under Windows.");
|
}
|
|
|
int mode = 0;
|
|
// Use a Runtime, because implementing stat() via JNA is just too much trouble.
|
// This could be done with the 'stat' command, too. But that may have a shell specific implementation, so we use 'ls' instead.
|
String lsLine = runProcessLs(path);
|
if (lsLine == null) {
|
LOGGER.debug("Could not get file information for path " + path);
|
return null;
|
}
|
|
Pattern p = Pattern.compile("^(([-bcdlspCDMnP?])([-r][-w][-xSs])([-r][-w][-xSs])([-r][-w][-xTt]))[@+.]? +[0-9]+ +([0-9]+) +([0-9]+) ");
|
Matcher m = p.matcher(lsLine);
|
if ( !m.lookingAt() ) {
|
LOGGER.debug("Could not parse valid file mode information for path " + path);
|
return null;
|
}
|
|
// Parse mode string to mode bits
|
String group = m.group(2);
|
switch (group.charAt(0)) {
|
case 'p' :
|
mode |= 0010000; break;
|
case 'c':
|
mode |= 0020000; break;
|
case 'd':
|
mode |= 0040000; break;
|
case 'b':
|
mode |= 0060000; break;
|
case '-':
|
mode |= 0100000; break;
|
case 'l':
|
mode |= 0120000; break;
|
case 's':
|
mode |= 0140000; break;
|
}
|
|
for ( int i = 0; i < 3; i++) {
|
group = m.group(3 + i);
|
switch (group.charAt(0)) {
|
case 'r':
|
mode |= (0400 >> i*3); break;
|
case '-':
|
break;
|
}
|
|
switch (group.charAt(1)) {
|
case 'w':
|
mode |= (0200 >> i*3); break;
|
case '-':
|
break;
|
}
|
|
switch (group.charAt(2)) {
|
case 'x':
|
mode |= (0100 >> i*3); break;
|
case 'S':
|
mode |= (04000 >> i); break;
|
case 's':
|
mode |= (0100 >> i*3);
|
mode |= (04000 >> i); break;
|
case 'T':
|
mode |= 01000; break;
|
case 't':
|
mode |= (0100 >> i*3);
|
mode |= 01000; break;
|
case '-':
|
break;
|
}
|
}
|
|
return new Filestat(mode, Integer.parseInt(m.group(6)), Integer.parseInt(m.group(7)));
|
}
|
|
|
/**
|
* Run the unix command 'ls -ldn' on a single file and return the resulting output line.
|
*
|
* @param path
|
* Path to a single file or directory.
|
* @return The first line of output from the 'ls' command. Null, if an error occurred and no line could be read.
|
*/
|
private static String runProcessLs(String path)
|
{
|
String cmd = "ls -ldn " + path;
|
Runtime rt = Runtime.getRuntime();
|
Process pr = null;
|
InputStreamReader ir = null;
|
BufferedReader br = null;
|
String output = null;
|
|
try {
|
pr = rt.exec(cmd);
|
ir = new InputStreamReader(pr.getInputStream());
|
br = new BufferedReader(ir);
|
|
output = br.readLine();
|
|
while (br.readLine() != null) ; // Swallow remaining output
|
}
|
catch (IOException e) {
|
LOGGER.debug("Exception while running unix command '" + cmd + "': " + e);
|
}
|
finally {
|
if (pr != null) try { pr.waitFor(); } catch (Exception ignored) {}
|
|
if (br != null) try { br.close(); } catch (Exception ignored) {}
|
if (ir != null) try { ir.close(); } catch (Exception ignored) {}
|
|
if (pr != null) try { pr.getOutputStream().close(); } catch (Exception ignored) {}
|
if (pr != null) try { pr.getInputStream().close(); } catch (Exception ignored) {}
|
if (pr != null) try { pr.getErrorStream().close(); } catch (Exception ignored) {}
|
}
|
|
return output;
|
}
|
|
|
private static UnixCLibrary getUnixCLibrary()
|
{
|
if (unixlibc == null) {
|
unixlibc = (UnixCLibrary) Native.loadLibrary("c", UnixCLibrary.class);
|
if (unixlibc == null) throw new RuntimeException("Could not initialize native C library.");
|
}
|
return unixlibc;
|
}
|
|
}
|