blob: 2b807196178a740c689cd85f7b057aace648f3f5 [file] [log] [blame]
/*
* 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;
}
}