Учитывая, что у меня открыт файл, есть ли способ определить, есть ли файл у других процессов? - PullRequest
3 голосов
/ 08 января 2020

У меня есть дескриптор файла (полученный с помощью open (2)). В какой-то момент времени я могу определить, что файл не был связан (fstat (2), проверьте st_nlinks). Однако прежде чем я смогу закрыть свой дескриптор файла, я хотел бы убедиться, что ни у одного другого процесса файл еще не открыт (для записи по крайней мере).

inotify может (и делает) дать мне несколько приятных событий для работы с например, IN_ATTRIB, когда меняются st_nlinks, и IN_CLOSE_WRITE, когда другой процесс закрывает файл (и если он был открыт для записи, это, однако, не означает, что NO ONE все еще не открыл его для записи).

В основном я отслеживаю файл журнала, и нужно убедиться, что я получаю всю информацию, которая была записана, даже после переименования, но как только файл был переименован и / или удален, и все авторы закрылись, нет Еще один момент, который я хочу оставить открытым для меня файловым дескриптором.

(Избегать рас при открытии файла и проверке того, что дескриптор inotify ссылается на тот же файл, выходит за рамки этого вопроса.)

1 Ответ

1 голос
/ 13 января 2020

Не защищен от ошибок, но если вы можете отслеживать переименования с помощью inotify, то они должны быть "достаточно хорошими", очевидно, с некоторыми условиями гонки (согласно стандарту при работе с чем-либо inotify). Улучшение этого требует stat () для каждого не удаленного файла, на который есть ссылка из / pro c, и, на мой взгляд, не может существенно улучшить решение.

Первый, шаг, учитывая pid и fd (как строки, потому что так мы получаем их из readdir (3), получим режим, для которого он открыт:

static
int getflags(const char* pidn, const char* fdn)
{
    char fnamebfr[525];
    char bfr[1 << 13];
    sprintf(fnamebfr, "/proc/%s/fdinfo/%s", pidn, fdn);
    int fd = open(fnamebfr, O_RDONLY);
    if (fd < 0) {
        if (errno != ENOENT)
            perror(fnamebfr);
        return 0;
    }
    int r = read(fd, bfr, sizeof(bfr));
    if (r < 0) {
        perror(fnamebfr);
        r = 0;
    } else if (r == sizeof(bfr)) {
        r = 0;
        fprintf(stderr, "bfr in %s is too small.\n", __FUNCTION__);
    } else {
        bfr[r] = 0;
        r = 0;
        char *fb = strstr(bfr, flagsstr);
        if (!fb) {
            fprintf(stderr, "Error locating '%s' in %s, content:\n%s\n", flagsstr, fnamebfr, bfr);
        } else {
            char *nptr;
            fb += strlen(flagsstr);
            r = strtol(fb, &nptr, 8);
            if (*nptr != '\n')
                fprintf(stderr, "Parse warning for strtol, endp=\n%s\nbfr=%s\n", nptr, bfr);
        }
    }

    close(fd);

    return r;
}

Примечание: если файл был закрыт между ходьбой / proc / / fd / мы вернем 0 здесь (O_RDONLY), если дескриптор файла был использован повторно ... тогда мы вернем совершенно неправильные флаги.

Теперь мы можем ходить / proc / искать O_WRONLY или O_RDWR в этих флагах :

static int fileopenforwrite(const char* path)
{
    size_t tlen = strlen(path);
    DIR *proc, *fd;
    struct dirent *procent, *fdent;
    int _fd;
    proc = opendir("/proc");

    if (!proc) {
        perror("/proc");
        return -1;
    }

    while ((procent = readdir(proc))) {
        char *endptr;
        char fdpath[MAXNAMLEN + 10];

        strtol(procent->d_name, &endptr, 10);
        if (*endptr)
            continue; /* not a pid */
        sprintf(fdpath, "/proc/%s/fd", procent->d_name);
        _fd = open(fdpath, O_RDONLY);
        if (_fd < 0) {
            if (errno != ENOENT) /* process terminated + waited */
                perror(fdpath);
            fd = fdopendir(_fd);
            if (!fd) {
                perror(fdpath);
                close(_fd);
                continue;
            }
            while ((fdent = readdir(fd))) {
                if (fdent->d_type == DT_DIR)
                    continue; /* skip . and .. */
                char readbuf[MAXNAMLEN + 11];
                ssize_t r = readlinkat(_fd, fdent->d_name, readbuf, sizeof(readbuf));
                if (r < 0) {
                    perror(fdpath);
                } else if (r >= (int)sizeof(readbuf)) {
                    fprintf(stderr, "Bufferoverflow in %s (%s).", __FUNCTION__, __FILE__);
                } else {
                    readbuf[r] = 0;
                    if (strncmp(path, readbuf, tlen) == 0 &&
                            (!readbuf[tlen] || strcmp(" (deleted)", readbuf + tlen) == 0))
                    {
                        /* We have an FD to the file, now we want to know if it's
                         * open for writing */
                        int f = getflags(procent->d_name, fdent->d_name);
                        if (f & (O_RDONLY | O_RDWR)) {
                            closedir(fd);
                            closedir(proc);
                            return 0;
                        }
                    }
                }
            }
            closedir(fd); /* implicitly closes _fd */
        }
    }
    closedir(proc);

    return 1;
}

Это вернет 0 при да, 1 при ложном, -1 при невозможности открыть /proc.

Если вы используете это, очевидно, очистите его до требования.

...