views:

765

answers:

4

I am having trouble in creating named pipe in Android and the example below illustrates my dilemma:

res = mkfifo("/sdcard/fifo9000", S_IRWXO);
if (res != 0)
{
    LOG("Error while creating a pipe (return:%d, errno:%d)", res, errno);
}

The code always prints:

Error while creating a pipe (return:-1, errno:1)

I can't figure out exactly why this fails. The application has android.permission.WRITE_EXTERNAL_STORAGE permissions. I can create normal files with exactly the same name in the same location, but pipe creation fails. The pipe in question should be accessible from multiple applications.

  1. I suspect that noone can create pipes in /sdcard. Where would it be the best location to do so?
  2. What mode mast should I set (2nd parameter)?
  3. Does application need any extra permissions?
+1  A: 

The default filesystem of /sdcard is FAT32, which doesn't support named pipes.

On a non-rooted device the only possible place you could try to create those pipes would be the application data directory /data/data/com.example/ . Note: You shouldn't hardcode that value, use the Context.getApplicationInfo().dataDir .

But be aware that whenever the user is using Apps2SD or whenever Google implements that support officially you need to make sure to let the user know that the app can't be stored on vfat files system.

Roosmaa
+4  A: 

The previous answer is correct -- mkfifo() just calls mknod() to create a special file, and FAT32 doesn't support that.

As an alternative you may want to consider using Linux's "abstract namespace" UNIX-domain sockets. They should be roughly equivalent to a named pipe. You can access them by name, but they're not part of the filesystem, so you don't have to deal with various permission issues. Note the socket is bi-directional.

Since it's a socket, you may need INTERNET permission. Not sure about that.

Here's a quick bit of client/server sample code:

#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <stddef.h>
#include <sys/socket.h>
#include <sys/un.h>

/*
 * Create a UNIX-domain socket address in the Linux "abstract namespace".
 *
 * The socket code doesn't require null termination on the filename, but
 * we do it anyway so string functions work.
 */
int makeAddr(const char* name, struct sockaddr_un* pAddr, socklen_t* pSockLen)
{
    int nameLen = strlen(name);
    if (nameLen >= (int) sizeof(pAddr->sun_path) -1)  /* too long? */
        return -1;
    pAddr->sun_path[0] = '\0';  /* abstract namespace */
    strcpy(pAddr->sun_path+1, name);
    pAddr->sun_family = AF_LOCAL;
    *pSockLen = 1 + nameLen + offsetof(struct sockaddr_un, sun_path);
    return 0;
}

int main(int argc, char** argv)
{
    static const char* message = "hello, world!";
    struct sockaddr_un sockAddr;
    socklen_t sockLen;
    int result = 1;

    if (argc != 2 || (argv[1][0] != 'c' && argv[1][0] != 's')) {
        printf("Usage: {c|s}\n");
        return 2;
    }

    if (makeAddr("com.whoever.xfer", &sockAddr, &sockLen) < 0)
        return 1;
    int fd = socket(AF_LOCAL, SOCK_STREAM, PF_UNIX);
    if (fd < 0) {
        perror("client socket()");
        return 1;
    }

    if (argv[1][0] == 'c') {
        printf("CLIENT %s\n", sockAddr.sun_path+1);

        if (connect(fd, (const struct sockaddr*) &sockAddr, sockLen) < 0) {
            perror("client connect()");
            goto bail;
        }
        if (write(fd, message, strlen(message)+1) < 0) {
            perror("client write()");
            goto bail;
        }
    } else if (argv[1][0] == 's') {
        printf("SERVER %s\n", sockAddr.sun_path+1);
        if (bind(fd, (const struct sockaddr*) &sockAddr, sockLen) < 0) {
            perror("server bind()");
            goto bail;
        }
        if (listen(fd, 5) < 0) {
            perror("server listen()");
            goto bail;
        }
        int clientSock = accept(fd, NULL, NULL);
        if (clientSock < 0) {
            perror("server accept");
            goto bail;
        }
        char buf[64];
        int count = read(clientSock, buf, sizeof(buf));
        close(clientSock);
        if (count < 0) {
            perror("server read");
            goto bail;
        }
        printf("GOT: '%s'\n", buf);
    }
    result = 0;

bail:
    close(fd);
    return result;
}
fadden
this might be hard to replicate in java - just saying :)
KevinDTimm
True, but the original code snippet isn't Java either, so I figured that wasn't a deal-breaker. The socket approach is certainly more JNI-intensive than a named pipe in the filesystem.
fadden
Right, target is C. This approach seem to require separate thread per connected client.
Ignas Limanauskas
If you're doing it in C it shouldn't be much different from a fifo, and if you have multiple connected clients it should be easier for the server to manage (one fd listening for connections, new fd for each client, use select() or poll() to watch for traffic on all of them). It's like a network socket but without the pesky network. :-) Spawning multiple threads and dropping them into a blocking read() also work, but adds context-switch overhead and gets busy if you have a lot of clients.
fadden
Thanks! Can you explain why you use pSockLen?
Berkus
Berkus: The socket calls want the string length, not a null-terminated string, so you need to compute that and pass it in. (It might work just fine with null termination and an excessive length, but I believe the above is the pedantically-correct approach.)
fadden
Yeah, just figured, that since abstract namespace socket names are not null-terminated, you either have to zero-fill whole sun_path variable or limit the sockaddr size, otherwise the socket names won't match!
Berkus
+1  A: 

there's also /sqlite_stmt_journals (we use it for testing, I don't know how long this directory will survive OS updates)

If you need IPC, the best practices are to use the Binders

If you only need inter-thread communication, you can use unnamed pipes through JNI (this works fine)

Stéphane
Yes, this is what I ended up using. The key here is to get native code talk to native code. Going over Java would make it more complicated (JNI and the likes) and potentially slower. Binder interface is available over the C++, but full Android source is needed, which is a big obstacle.
Ignas Limanauskas
We use JNI on top of unnamed pipes because we have Java code asynchronously sending data to our native threads ; but if you just have to make your native threads talk to other native threads within the same process, you don't need JNI for that. I should add that pipes are faster and lighter than sockets.
Stéphane
A: 

If you're coding this in Java, you should just use PipedInputStream and PipedOutputStream.

Jesse Wilson