Версия для печати

logo70-tran

У NFS клиента в 2.6.x ядре появилась неприятная особенность. Он никак не сигнализирует о невозможности записать данные по причине превышения файловой квоты. А наоборот всем своим поведением говорит о том, что всё хорошо. Все операции, включая fsync() и close() заканчиваются успешно. Более того, даже ls после этого показывает якобы всё записано(размер файла равен записанному, что превышает квоту). Но через несколько секунд размер файла «укорачивается» до того размера, что поместилось в квоту (что в общем-то и есть правда).

Вот небольшая тестовая программа test_nfswrite.c, которая пишет 1024 байтные блоки. Тестовый файл /mount/m7/tmp/test должен быть на NFS сервере (не важно с каким ядром). А клиент, который пишет с ядром 2.6.x.

Компилить надо так: gcc test_nfswrite.c -o test_nfswrite

Запускать так: ./test_nfswrite 500

где 500 — это количество блоков по 1024 байта. Если указать большое количество блоков, которое не влезает в квоту, то вы должны наблюдать ошибки.

#include
#include
#include
#include
#include
#include
#include
#include
int main(int arc, char * argv[]) {
    int fd;
    int rc;
    int i;
    int size = 2049;
    char *buf;
    int it;

    it = atoi(argv[1]);

    buf = (char*)malloc(size * sizeof(char));
    memset(buf, 'a', size - 1);
    buf[size - 1] = '\0';

/*   fd = open("/mount/m7/tmp/test", O_WRONLY|O_SYNC); */
   fd = open("/mount/m7/tmp/test", O_WRONLY);

    for(i = 0; i < it; i++) {
        rc = write(fd, buf, strlen(buf));
        printf("write: %d, errno = %s\n", rc, strerror(errno));
    }
    rc = fsync(fd);
    printf("fsync: %d, errno = %s\n", rc, strerror(errno));
    rc = close(fd);
    printf("close: %d\n", rc);
    free(buf);
    return 0;
}

Комментарии:

  1. Если мы открываем файл fd = open («/mount/m7/tmp/test», O_WRONLY); пишем туда в цикле по 1024 байта (к примеру) rc = write(fd, buf, strlen(buf)); и переполняем дисковую квоту, то вызывая синхронизацию rc = fsync (fd); и закрытие файл rc = close (fd); не видно никаких признаков ошибки.
  2. Но если открыть файл по-другому, с флагом O_SYNC fd = open («/mount/m7/tmp/test», O_WRONLY | O_SYNC);, то ошибка появится. И при попытке записи и при синхронизации: write: -1, errno = Input/output error, fsync: -1, errno = Disk quota exceeded.

Данное исследование проводилось в связи с тем, что MTA Zmailer имеет mailbox.c LDA (доставщик сообщений в ящик пользователя), который подвержен этой проблеме. При использовании NFS хранилища для ящиков и ядер 2.6.x на клиенте, доставка сообщений в переполненный ящик проводит к разрушению структуры ящика.

Небольшой патч включает O_SYNC режим записи и исправляет проблему:

--- ./zmailer.orig/transports/mailbox/mailbox.c 2006-06-26 20:28:03.000000000 +0400
+++ ./zmailer/transports/mailbox/mailbox.c      2009-06-22 16:49:18.000000000 +0400
@@ -1753,7 +1753,8 @@
                  "To open a file with euid=%d egid=%d ismbox=%d file='%s'\n",
                  (int)geteuid(), (int)getegid(), ismbox, file);

-       fdmail = open(file, O_RDWR|O_APPEND);
+/* +++ */
+       fdmail = open(file, O_RDWR|O_APPEND|O_SYNC);

        if (fdmail < 0) {
          char fmtbuf[512];
@@ -1997,6 +1998,11 @@
       int rc = write(sffileno(sfp), p, len);
       int e  = errno;
+/* +++ */
+      if ((rc < 0) && (fsync(sffileno(sfp)) < 0)) {
+        if (errno == EDQUOT) e  = errno;
+      }
+
 if (verboselog)
   fprintf(verboselog, " mbox_sfwrite(ptr, len=%d) rc=%d errno=%d\n",
          (int)len, rc, e);

Больше спасибо Mike Fandorin <fandorin (at) rol.ru> за исследование проблемы.

Cross-posted to ZMailer mailing list.

UPD: Похоже в 2.6.30 наконец-то исправили эту проблему.  fsync() теперь работает в синхронном режиме не зависимо от опций открытия файла:

ext3: Use WRITE_SYNC for commits which are caused by fsync()
Theodore Ts'o [Sat, 28 Mar 2009 02:14:27 +0000 (22:14 -0400)]

If a commit is triggered by fsync(), set a flag indicating the journal
blocks associated with the transaction should be flushed out using
WRITE_SYNC.
Signed-off-by: "Theodore Ts'o"
Acked-by: Jan Kara

Оставить комментарий


Антиспам-картинка