Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

(enhancement) Expose dup2() from libc, Daemon optionally redirect str… #12

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,15 @@

<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.6.1</version>
<configuration>
<source>1.6</source>
<target>1.6</target>
</configuration>
</plugin>
<plugin><!-- create uberjar for easy testing -->
<artifactId>maven-assembly-plugin</artifactId>
<executions>
Expand Down
2 changes: 2 additions & 0 deletions src/main/java/com/sun/akuma/CLibrary.java
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,8 @@ public interface CLibrary extends Library {
int unsetenv(String name);
void perror(String msg);
String strerror(int errno);
int dup(int fd);
int dup2(int newfd, int oldfd);

// this is listed in http://developer.apple.com/DOCUMENTATION/Darwin/Reference/ManPages/man3/sysctlbyname.3.html
// but not in http://www.gnu.org/software/libc/manual/html_node/System-Parameters.html#index-sysctl-3493
Expand Down
56 changes: 52 additions & 4 deletions src/main/java/com/sun/akuma/Daemon.java
Original file line number Diff line number Diff line change
Expand Up @@ -29,9 +29,8 @@
import com.sun.jna.StringArray;
import static com.sun.akuma.CLibrary.LIBC;

import java.io.FileWriter;
import java.io.IOException;
import java.io.File;
import java.io.*;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.logging.Level;
import java.util.logging.Logger;
Expand Down Expand Up @@ -185,7 +184,20 @@ public void init(String pidFile) throws Exception {
* when they don't work correctly.
*/
protected void closeDescriptors() throws IOException {
if(!Boolean.getBoolean(Daemon.class.getName()+".keepDescriptors")) {
final String cname = Daemon.class.getName();
if (Boolean.getBoolean(cname + ".redirectDescriptors")) {
// redirect (dup) System.out / System.err to user-specified files, or /dev/null
String stdoutPath = System.getProperty(cname + ".stdoutFile", "/dev/null");
String stderrPath = System.getProperty(cname + ".stderrFile", "/dev/null");
// attempt mkdirs as a nicety to caller
new File(stdoutPath).getParentFile().mkdirs();
new File(stderrPath).getParentFile().mkdirs();
FileOutputStream stdout = new FileOutputStream(stdoutPath);
FileOutputStream stderr = stdoutPath.equals(stderrPath) ? stdout : new FileOutputStream(stderrPath);
dupFD(stdout.getFD(), 1);
dupFD(stderr.getFD(), 2);
System.in.close();
} else if(!Boolean.getBoolean(cname +".keepDescriptors")) {
System.out.close();
System.err.close();
System.in.close();
Expand Down Expand Up @@ -238,6 +250,42 @@ public static String getCurrentExecutable() {
return System.getProperty("java.home")+"/bin/java";
}


// dup2() system call: close oldfd, make oldfd refer to same resource as newfd
// public access to be visible to testing
public static int dupFD(FileDescriptor newFD, int oldfd) throws IOException {
/*
We need to get the field 'private int fd' out of java.io.FileDescriptor
There are two possible ways without writing JNI
1) sun.misc.SharedSecrets
2) reflection

Both are used elsewhere in this codebase (NetworkServer.java), but
prefer 2) b/c dubiousness of using sun.* API's, but it might be
rejected by a SecurityManager, if installed.
*/
int newfd;
if (Boolean.getBoolean(Daemon.class.getName() + ".useSunMiscForDescriptors")) {
newfd = sun.misc.SharedSecrets.getJavaIOFileDescriptorAccess().get(newFD);
} else {
try {
Field fdField = FileDescriptor.class.getDeclaredField("fd");
fdField.setAccessible(true);
newfd = fdField.getInt(newFD);
} catch (Exception nsfe) { // NoSuchFieldException || IllegalAccessException
throw new RuntimeException("cannot reflect on java.io.FileDescriptor", nsfe);
}

}
int ret = LIBC.dup2(newfd, oldfd);
if (ret != oldfd) {
String msg = String.format("dup2 returned %d expected %d", ret, oldfd);
LIBC.perror(msg);
throw new IOException(msg);
}
return ret;
}

private static String resolveSymlink(File link) throws IOException {
String filename = link.getAbsolutePath();

Expand Down
66 changes: 66 additions & 0 deletions src/test/java/com/sun/akuma/test/Dup2Test.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
package com.sun.akuma.test;

import com.sun.akuma.Daemon;
import junit.framework.TestCase;

import java.io.*;
import java.lang.reflect.Field;
import java.nio.charset.Charset;
import java.util.Random;
import static com.sun.akuma.CLibrary.LIBC;


public class Dup2Test extends TestCase {

static final Random rand = new Random(System.currentTimeMillis());

// Attempting to write this test using stdout / stderr works within IntelliJ, but
// the surefire test runner writes to a cached reference to System.out during the test
// and corrupts the results. So make the same test using other files.
public void testDup2() throws Exception {
/* dup fileA into fileB, so that writes to A really go to B */
File fileA = File.createTempFile("dup2-testA", ".txt"),
fileB = File.createTempFile("dup2-testB", ".txt");
FileOutputStream outA = new FileOutputStream(fileA),
outB = new FileOutputStream(fileB);
String textA = String.valueOf(rand.nextLong()), textB = String.valueOf(rand.nextLong()),
textC = String.valueOf(rand.nextLong());
try {
outA.write(textA.getBytes(Charset.defaultCharset()));
outB.write(textB.getBytes(Charset.defaultCharset()));
int bfd = getFD(outB.getFD());
Daemon.dupFD(outA.getFD(), bfd);
outB.write(textC.getBytes(Charset.defaultCharset()));
} finally {
outA.close();
outB.close();
}
String contentA = readFileContents(fileA);
String contentB = readFileContents(fileB);
assertEquals("expect only textB in file B", textB, contentB);
assertEquals("expect textA + textC in file A", textA + textC, contentA);
}


private static int getFD(FileDescriptor fd) throws Exception {
Field fdField = FileDescriptor.class.getDeclaredField("fd");
fdField.setAccessible(true);
return fdField.getInt(fd);
}

private static String readFileContents(File f) throws IOException {
/* assuming this project wants to target back to java6, so do not use Files.readAllBytes() */
StringBuilder out = new StringBuilder();
Reader reader = new FileReader(f);
char[] buf = new char[100];
int r;
try {
while ((r = reader.read(buf)) > 0) {
out.append(buf, 0, r);
}
} finally {
reader.close();
}
return out.toString();
}
}