使用しているファイルの一覧を取得する

プロセスが使用しているファイルの一覧を取得する。
c - on iOS/iPhone: "Too many open files": need to list open files (like lsof) - Stack Overflow」によると、fcntlでfdを舐めてパスを取れば良いっぽい。

#include <sys/param.h>
#include <sys/fcntl.h>

void
show_openfiles()
{
    int flags;
    int fd;
    char buf[MAXPATHLEN + 1] ;
    
    for (fd = 0; fd < OPEN_MAX; fd++) {
        errno = 0;
        flags = fcntl(fd, F_GETPATH, buf);
        if (flags != -1) {
            printf("File Descriptor %d in use for: %s\n", fd, buf) ;
        }
    }
}

なんか適当にファイルを開いた状態で読んでみる。

    id filehandle = [NSFileHandle fileHandleForReadingAtPath:@"/etc/services"];
    show_openfiles();
...

出力

File Descriptor 0 in use for: /dev/null
File Descriptor 1 in use for: /dev/ttys000
File Descriptor 2 in use for: /dev/ttys000
File Descriptor 3 in use for: /private/etc/services

0〜2は標準入力、標準出力、標準エラー出力。/dev/ttys...になっているのはソケットの模様。


sysctlでKERN_FILEを取れないかやってみた。

#include <sys/sysctl.h>
#include <sys/file.h>

/* file types */
typedef enum {
    DTYPE_VNODE = 1,	/* file */
    DTYPE_SOCKET,	/* communications endpoint */
    DTYPE_PSXSHM,	/* POSIX Shared memory */
    DTYPE_PSXSEM,	/* POSIX Semaphores */
    DTYPE_KQUEUE,	/* kqueue */
    DTYPE_PIPE,         /* pipe */
    DTYPE_FSEVENTS,	/* fsevents */
    DTYPE_MAX
} file_type_t;
#define DTYPE_NAME { \
    "*UNKNOWN*", \
    "DTYPE_VNODE", \
    "DTYPE_SOCKET", \
    "DTYPE_PSXSHM", \
    "DTYPE_PSXSEM", \
    "DTYPE_KQUEUE", \
    "DTYPE_PIPE", \
    "DTYPE_FSEVENTS" \
}

LIST_HEAD(filelist, extern_file);

void
show_kernfiles(void)
{
    int count[DTYPE_MAX];
    for (int i = 0; i < DTYPE_MAX; i++) count[i] = 0;
    const char *dtype_name[] = DTYPE_NAME;

    size_t size;
    if (sysctlbyname("kern.file", NULL, &size, NULL, 0) == -1) {
        perror("sysctl: kern.file");
        return;
    }
    char *buffer = malloc(size);
    if (sysctlbyname("kern.file", buffer, &size, NULL, 0) == -1) {
        perror("sysctl: kern.file");
        return;
    }

    char *ptr = buffer;

    struct filelist filehead = *(struct filelist *)ptr;
    ptr += sizeof(struct filelist);

    struct extern_file *fp;
    for (fp = filehead.lh_first; fp != 0; fp = fp->f_list.le_next){
        fp = (struct extern_file *)ptr;
        printf("fp = %p\n", fp);
        printf("f_list.le_next = %p\n", fp->f_list.le_next);
        printf("f_flag = %x\n", fp->f_flag);
        printf("f_type = %d\n", fp->f_type);
        printf("f_count = %d\n", fp->f_count);
        printf("f_msgcount = %d\n", fp->f_msgcount);
        printf("f_cred = %p\n", fp->f_cred);
        printf("f_ops = %p\n", fp->f_ops);
        printf("f_offset = %lld\n", fp->f_offset);
        printf("f_data = %p\n", fp->f_data);
        if (fp->f_type >= DTYPE_VNODE && fp->f_type < DTYPE_MAX) {
            count[fp->f_type]++;;
        } else {
            printf("unknown type = %d\n", fp->f_type);
            count[0]++;;
        }
        ptr += sizeof(struct extern_file);
    }
    free(buffer);
    printf("\nSummary:\n");
    for (int i = 0; i < DTYPE_MAX; i++) {
        printf("\t%s:\t%d\n", dtype_name[i], count[i]);
    }
}

file_type_tは定義が見つからなかったからxnuのソースコードの新しそうな奴から持って来た。
取得したデータの中身はsys/sysctl.hによると「struct: file entries」らしいのだが、具体的には何か分からないし、データの中身を見ると36バイト周期っぽかったので多分struct extern_fileと思う。
struct extern_fileはLIST_ENTRYでリンク構造になっているのでLIST_HEADで先頭のポインタの型を作ってやる。
但し、リンクの中身はカーネル空間のアドレス(0x80000000以降)で見れないので、そっちは使わずに構造体のサイズ分をポインタを進めている。


出力:

fp = 0xe07de04
f_list.le_next = 0x9880a900
f_flag = 1
f_type = 1
f_count = 41
f_msgcount = 0
f_cred = 0x80b52f80
f_ops = 0x802c4168
f_offset = 0
f_data = 0x81e875c8
...
fp = 0xe080df8
f_list.le_next = 0x0
f_flag = 1
f_type = 1
f_count = 28
f_msgcount = 0
f_cred = 0x80b52f80
f_ops = 0x802c4168
f_offset = 0
f_data = 0x81e875c8

Summary:
	*UNKNOWN*:	0
	DTYPE_VNODE:	144
	DTYPE_SOCKET:	145
	DTYPE_PSXSHM:	0
	DTYPE_PSXSEM:	0
	DTYPE_KQUEUE:	36
	DTYPE_PIPE:	16
	DTYPE_FSEVENTS:	1

VNODEというのがファイルのようで、実際にf_dataの中身にアクセス出来ればパス等も取れそうだけど、檻の中からカーネルタスクのメモリに自由にアクセスする方法がないので詳細は見られないっぽい。

kern.vnodeも読んでみたけど、こっちはEINVALになって内容は読めなかった。