summaryrefslogtreecommitdiffstats
path: root/src/main/java/com/gitblit/utils/JnaUtils.java
blob: 2b807196178a740c689cd85f7b057aace648f3f5 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
/*
 * 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;
	}

}