How to create named pipe (mkfifo) in Android?
Asked Answered
M

5

15

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?
Misjudge answered 29/4, 2010 at 19:46 Comment(0)
V
18

Roosmaa's 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;
}
Vardar answered 3/5, 2010 at 18:28 Comment(9)
this might be hard to replicate in java - just saying :)Linolinocut
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.Vardar
Right, target is C. This approach seem to require separate thread per connected client.Misjudge
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.Vardar
Thanks! Can you explain why you use pSockLen?Beau
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.)Vardar
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!Beau
This approach works on Android, I just tried it. You can use LocalSocketAddress and LocalSocket classes in Java to access a socket opened with the above code.Bristow
There some troubles with sockets since android 5.1. See my question: #31068940Gresham
A
8

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.

Across answered 3/5, 2010 at 12:42 Comment(0)
J
1

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)

Joaquinajoash answered 7/5, 2010 at 9:58 Comment(2)
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.Misjudge
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.Landowska
D
1

I would like to append to the accepted answer:

1) I am able to use this method to connect a socket between two native modules of an Android app.

2) write() should be in a loop since it may not write the full amount requested in the first go. For example, it should read something like:

void *p = buffer;
count = 0;
while ((count += write(clientSock, buffer, num_bytes - count)) < num_bytes)
{
    if (count < 0)
    {
        close(clientSock);
        errCode = count;
        break;
    }
    p += count;
}

The error handling shown above is insufficient, since several error codes simply indicate to try again. See the documentation for write.

Duclos answered 10/6, 2015 at 14:22 Comment(1)
You can use TEMP_FAILURE_RETRY() to retry on EINTR.Vardar
S
-1

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

Sainthood answered 8/5, 2010 at 8:59 Comment(2)
can PipedInputStream and PipedOutputStream be used across multiple applications like in c++ a namedPipe can be used by 2 different applications for reading and writting.Unfailing
Nope. It's in-process only.Sainthood

© 2022 - 2024 — McMap. All rights reserved.