Useless JNI
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#include "jni.h" | |
#include <android/log.h> | |
#include <stddef.h> | |
#include <fcntl.h> // splice ? | |
#include <sys/sendfile.h> // sendfile ? | |
#include <sys/stat.h> // fstat ? | |
#include <unistd.h> // seek ? | |
#include <stdio.h> // sprintf | |
#define LOG_TAG "fdshare-native" | |
#define MIN(a,b) \ | |
({ __typeof__ (a) _a = (a); \ | |
__typeof__ (b) _b = (b); \ | |
_a < _b ? _a : _b; }) | |
#define PIPE_SIZE 4096 | |
static int throwIOException(JNIEnv *env, int errnum, const char *message) | |
{ | |
__android_log_print(ANDROID_LOG_ERROR, LOG_TAG, "%s errno %s(%d)", message, strerror(errno), errno); | |
if (errnum != 0) { | |
const char *s = strerror(errnum); | |
if (strcmp(s, "Unknown error") != 0) | |
message = s; | |
} | |
jclass exClass; | |
const char *className = "java/io/IOException"; | |
exClass = (*env) -> FindClass(env, className); | |
return (*env) -> ThrowNew(env, exClass, message); | |
} | |
// the source is a pipe or socket - splice it directly | |
static int doPipeCopy(jint fd1, jint fd2, jlong start, ssize_t num) { | |
if (start != 0) { | |
// it is improbable, but someone can consider himself clever enough to offer us a pipe with mandatory offset... | |
// let's just flush the beginning to /dev/null | |
int nullFd; | |
if ((nullFd = open("/dev/null", O_WRONLY, S_IRWXU|S_IRWXG)) == -1) | |
return -1; | |
int skipResult = doPipeCopy(fd1, nullFd, 0, (ssize_t) start); | |
int errsv = errno; | |
close(nullFd); | |
errno = errsv; | |
if (skipResult < 0) | |
return skipResult; | |
} | |
ssize_t copied = 0; | |
ssize_t remaining = num == -1 ? PIPE_SIZE : num; | |
while(num == -1 || (remaining -= copied)) { | |
errno = 0; | |
copied = splice(fd1, NULL, fd2, NULL, MIN(remaining, PIPE_SIZE), SPLICE_F_MORE | SPLICE_F_MOVE); | |
if (copied < 1 && errno) { | |
return -1; | |
} else if (copied == 0) { | |
break; | |
} | |
} | |
return copied; | |
} | |
// splice a file via page cache | |
static int doZeroCopy(jint fd1, jint fd2, ssize_t num) { | |
ssize_t copied = 0; | |
ssize_t remaining = num == -1 ? PIPE_SIZE : num; | |
while(num == -1 || (remaining -= copied)) { | |
errno = 0; | |
if ((copied = sendfile(fd1, fd2, NULL, MIN(remaining, PIPE_SIZE))) < 1 && errno) | |
break; | |
} | |
return copied; | |
} | |
JNIEXPORT jstring Java_com_example_FdUtil_getFdPathInternal(JNIEnv *env, jint descriptor) | |
{ | |
// The filesystem name may not fit in PATH_MAX, but all workarounds | |
// (as well as resulting strings) are prone to OutOfMemoryError. | |
// The proper solution would, probably, include writing a specialized | |
// CharSequence. Too much pain, too little gain. | |
char buf[PATH_MAX + 1] = { 0 }; | |
char procFile[25]; | |
sprintf(procFile, "/proc/self/fd/%d", descriptor); | |
if (readlink(procFile, buf, sizeof(buf)) == -1) { | |
// the descriptor is no more, became inaccessible etc. | |
throwIOException(env, errno, "readlink() failed"); | |
return NULL; | |
} | |
if (buf[PATH_MAX] != 0) { | |
// the name is over PATH_MAX bytes long, the caller is at fault | |
// for dealing with such tricky descriptors | |
throwIOException(env, errno, "The path is too long"); | |
return NULL; | |
} | |
if (buf[0] != '/') { | |
// the name is not in filesystem namespace, e.g. a socket, | |
// pipe or something like that | |
//throwIOException(env, errno, "The descriptor does not belong to file with name"); | |
return NULL; | |
} | |
// doing stat on file does not give any guarantees, that it | |
// will remain valid, and on Android it likely to be | |
// inaccessible to us anyway let's just hope | |
return (*env) -> NewStringUTF(env, buf); | |
} | |
JNIEXPORT void Java_net_sf_fdshare_FdUtil_spliceInternal(JNIEnv *env, jint fd1, jint fd2, jlong start, jlong num) | |
{ | |
struct stat fStat; | |
int sourceFd; | |
if (fstat(sourceFd, &fStat)) | |
return; | |
if (S_ISREG(fStat.st_mode)) | |
{ | |
if (lseek(fd1, (off_t) start, SEEK_SET) != start) { | |
throwIOException(env, errno, "seek() failed"); | |
return; | |
} | |
ssize_t realNum = num < 0 ? fStat.st_size : MIN((ssize_t) num, fStat.st_size); | |
if (doZeroCopy(fd1, fd2, realNum) == -1) | |
throwIOException(env, errno, "failed to copy a file"); | |
} | |
else if (S_ISFIFO(fStat.st_mode) || S_ISSOCK(fStat.st_mode)) | |
{ | |
if (doPipeCopy(fd1, fd2, start, (ssize_t) num) == -1) | |
throwIOException(env, errno, "failed to copy from pipe"); | |
} | |
else throwIOException(env, errno, "unsupported file type"); | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment