Wednesday, January 23, 2013

workaround for fd leaking programs



A hack to close the existing fd if the caller has already opened a file with the same name.  Use with caution.

Compile and LD_PRELOAD this.

PS: http://troydhanson.github.com/uthash/ is awesome.

#include <stdio.h>
#include <strings.h>
#include <unistd.h>
#include <dlfcn.h>

#include "uthash.h"

/*-
 * Copyright (c) 2013 Brad Forschinger
 * All rights reserved.
 */


/* last entry must be NULL */
static char *target_files[] = { "/var/krb5/rcache/HTTP_8080", NULL };

struct fd_info_s {
    int fd;
    char path[FILENAME_MAX];
    UT_hash_handle hh;
};

struct fd_info_s *fd_to_path = NULL, *path_to_fd = NULL;

/* see if we've already got an fd for the path.  if so, close it first. */
int open(const char *path, int oflag, mode_t mode)
{
    static int (*real_open) (const char *, int, mode_t);
    struct fd_info_s *fd_info = NULL;
    char **target_file;

    if (real_open == NULL) {
        fprintf(stderr, "open: init\n");
        real_open = dlsym(RTLD_NEXT, "open");
        if (real_open == NULL) {
            return -1;
        }
    }

    /* look for a match */
    target_file = &target_files[0];
    while (*target_file != NULL) {

        /* match? */
        if (strcmp(*target_file, path) == 0) {
            int fd;

            HASH_FIND_STR(path_to_fd, path, fd_info);   /* have we opened this already? */
            if (fd_info) {

                fd = fd_info->fd;
                fd_info = NULL;
                fprintf(stderr, "open: closing %d before opening %s\n", fd, *target_file);
                close(fd);      /* our hooked close, we'll remove the hash entries there */
            }

            fd = real_open(path, oflag, mode);
            if (fd != -1) {
                /* create fd => path */
                fd_info = malloc(sizeof(struct fd_info_s));
                fd_info->fd = fd;
                strcpy(fd_info->path, path);
                HASH_ADD_INT(fd_to_path, fd, fd_info);

                /* create path => fd */
                fd_info = malloc(sizeof(struct fd_info_s));
                fd_info->fd = fd;
                strcpy(fd_info->path, path);
                HASH_ADD_STR(path_to_fd, path, fd_info);
            }
            return fd;
        }

        /* next. */
        *target_file++;
    }

    return real_open(path, oflag, mode);
}

/* stop tracking the fd */
int close(int filedes)
{
    static int (*real_close) (int);
    struct fd_info_s *fd_info = NULL;
    char path[FILENAME_MAX];

    if (real_close == NULL) {
        fprintf(stderr, "close: init\n");
        real_close = dlsym(RTLD_NEXT, "close");
        if (real_close == NULL) {
            return -1;
        }
    }

    HASH_FIND_INT(fd_to_path, &filedes, fd_info);
    if (fd_info) {
        /* del fd => path */
        strcpy(path, fd_info->path);
        HASH_DEL(fd_to_path, fd_info);
        free(fd_info);

        /* del path => fd */
        HASH_FIND_STR(path_to_fd, path, fd_info);
        HASH_DEL(path_to_fd, fd_info);
        free(fd_info);

        fprintf(stderr, "close: was tracking %d (%s)\n", filedes, path);
    }

    return real_close(filedes);
}

No comments: