Linux/InodeConv

Материал из Etersoft wiki
Перейти к навигацииПерейти к поиску

Задача

Описание задачи и альтернативный метод решения представлен здесь: http://wiki.etersoft.ru/Linux/ZeroInode

Squashing Inode for Glibc

Для решения данной проблемы предлагается использование сжатия inode. Похожий подход представлен здесь: https://bugzilla.redhat.com/show_bug.cgi?id=872629. В нашем случае произведены следующие изменения

--- a/ports/sysdeps/unix/sysv/linux/generic/wordsize-32/getdents.c
+++ b/ports/sysdeps/unix/sysv/linux/generic/wordsize-32/getdents.c
@@ -26,10 +26,18 @@
 #include <unistd.h>
 #include <sys/param.h>
 #include <sys/types.h>
 
 #include <sysdep.h>
 #include <sys/syscall.h>
 
+static ino_t
+squash_dir_inode (ino_t d_ino32, uint64_t d_ino64)
+{
+  d_ino32 ^= d_ino64 >> (sizeof (uint64_t) - sizeof (ino_t)) * 8;
+  return d_ino32;
+}
+
 /* Pack the dirent64 struct down into 32-bit offset/inode fields, and
    ensure that no overflow occurs.  */
 ssize_t
@@ -87,10 +95,14 @@ __getdents (int fd, char *buf, size_t nbytes)
 
       outp->u.d_ino = d_ino;
       outp->u.d_off = d_off;
-      if ((sizeof (outp->u.d_ino) != sizeof (inp->k.d_ino)
+      if (sizeof (outp->u.d_ino) != sizeof (inp->k.d_ino)
            && outp->u.d_ino != d_ino)
-          || (sizeof (outp->u.d_off) != sizeof (inp->k.d_off)
-              && outp->u.d_off != d_off))
+       {
+         /* Inode overflow. Squash it*/
+         outp->u.d_ino = squash_dir_inode (outp->u.d_ino, d_ino);
+       }
+      if (sizeof (outp->u.d_off) != sizeof (inp->k.d_off)
+              && outp->u.d_off != d_off)
         {
           /* Overflow.  If there was at least one entry before this one,
              return them without error, otherwise signal overflow.  */

--- a/sysdeps/unix/sysv/linux/xstatconv.c
+++ b/sysdeps/unix/sysv/linux/xstatconv.c
@@ -20,6 +20,7 @@
 #include <sys/stat.h>
 #include <kernel_stat.h>
 #include <kernel-features.h>
 
 #ifdef STAT_IS_KERNEL_STAT
 
@@ -178,6 +179,14 @@ __xstat64_conv (int vers, struct kernel_stat *kbuf, void *ubuf)
 #endif
 }
 
+static ino_t
+squash_inode (ino_t ino32, ino64_t ino64)
+{
+  ino32 ^= ino64 >> (sizeof (ino64_t) - sizeof (ino_t)) * 8;
+  return ino32;
+}
+
+
 int
 __xstat32_conv (int vers, struct stat64 *kbuf, struct stat *buf)
 {
@@ -202,8 +211,7 @@ __xstat32_conv (int vers, struct stat64 *kbuf, struct stat *buf)
            if (sizeof (buf->st_ino) != sizeof (kbuf->st_ino)
                && buf->st_ino != kbuf->st_ino)
              {
-               __set_errno (EOVERFLOW);
-               return -1;
+               buf->st_ino = squash_inode (buf->st_ino, kbuf->st_ino);
              }
          }
 #else
@@ -211,8 +219,7 @@ __xstat32_conv (int vers, struct stat64 *kbuf, struct stat *buf)
        if (sizeof (buf->st_ino) != sizeof (kbuf->st_ino)
            && buf->st_ino != kbuf->st_ino)
          {
-           __set_errno (EOVERFLOW);
-           return -1;
+               buf->st_ino = squash_inode (buf->st_ino, kbuf->st_ino);
          }
 #endif
        buf->st_mode = kbuf->st_mode;

Создание патча для Glibc

Для создания патча для glibc необходимо руководствоваться следующими документами:

Необходимо оформить патч по приведенным требованиям.

Описание баги в mail-list

Здесь составлено описание баги в mail-list для glibc (на русском).

В настоящее время наблюдается активное сетевое взаимодействие 64-битных и 32-битных систем. Проблема в том, что многие программы для проверки существования файла используют функцию stat (), которая возвращает ошибку в 32-битных системах при переполнении полей структуры stat. Данный патч позволяет решить проблему переполнения поля inode, сжимая его до 32бит.

Есть и другие варианты решения:

1. Перекомпиляция программ с флагом сборки -D_FILE_OFFSET_BITS=64. Но этот подход может потребовать большой переделки программы.

2. Обнулять inode. Этот вариант плох тем, что некоторых программах есть проверка существования файла по inode: if (st_ino >= 0) ...

Nowadays we can observe active cooperation between 64bit and 32bit systems. Due to this we have a compatibility problem - programs on 32bit systems have to operate with 64bit inodes.

Most of these programs use stat to check out file's presence and don's use inodes themselves. Sometimes we could resolve this problem by means of rebuilding them with -D_FILE_OFFSET_BITS=64 flag.

But many of them can't be rebuilt. So there is another way that is used in other products which have such a problem(e.g. fuse) - squashing inodes for 32bit systems.

Второе письмо

Цель второго письма - узнать что лучше: сжимать inode или обнулять его; понимают ли мейнтейнеры, как используются функции, возвращающие inode; знают ли они о том, что подобная функциональность(сжатие inode) уже реализована в ядре, считают ли они ее необходимой; зачем нужна дополнительная обертка вокруг stat64, если она дублирует код в ядре.

I understand that glibc has its own global purposes and its own view on the problem. Let me introduce user's view. First of all, I completely agree that programs should be compiled with _FILE_OFFSET_BITS defined to 64. Also I believe that one day all programs will use only proper structures for proper aims.
But unfortunately today isn't this day, probably tomorrow isn't too. Thus we have some legacy programs which couldn't be recompiled at all and programs which couldn't be recompiled with this flag due to their nature(mixing 32bit and 64bit things will cause undefined behavior). And these programs aren't work because hundreds of packages and libraries use stat only to make sure themselves that needed file exists. They fail every time when they want to checkout file's existence. I think it's more serious bug than all the bugs which may be caused by squashing. Also in this case(changing somehow inodes) we could take care of users by outputting some kind of dmesg that squashing s used. So the question is: could you tell me what is the better way - squash or zeroing?
Even if we wouldn't do something with inode in glibc, we, simple users, have to make our programs work. For your impartiality could I remind you that there are a lot of implementations of squashing inodes in the kernel scope, e g fuse:

/*
 * ino_t is 32-bits on 32-bit arch. We have to squash the 64-bit value down
 * so that it will fit.
 */
static ino_t fuse_squash_ino(u64 ino64)
{
        ino_t ino = (ino_t) ino64;
        if (sizeof(ino_t) < sizeof(u64))
                ino ^= ino64 >> (sizeof(u64) - sizeof(ino_t)) * 8;
        return ino;
}


Moreover, in some programs if inode is zero, they think that file doesn't exist, not the perfect way to use stat, doesn't it? So, zeroing inode is precisely safer way, but it also has its own minuses.

And there is one thing that I can't understand. Linux kernel has its own function, wrapper around stat64 for old systems, it's called cp_old_stat. This function has behavior similar to glibc's __xstat32_conv but it also outputs the warning message about old stat using. So why glibc doesn't use it?


Squshing inode for kernel

Подобное решение в ядро:

 #ifdef __ARCH_WANT_OLD_STAT

+/* 
+ * If inode too long - sqush it
+*/
+static unsigned long squash_ino(unsigned long long ino64)
+{
+       unsigned long ino;
+       ino ^= ino64 >> (sizeof(unsigned long long) - sizeof(unsigned long)) * 8;
+       return ino;
+}
+
 /*
  * For backward compatibility?  Maybe this should be moved
  * into arch/i386 instead?
@@ -136,7 +146,7 @@ static int cp_old_stat(struct kstat *stat, struct __old_kernel_stat __user * sta
        tmp.st_dev = old_encode_dev(stat->dev);
        tmp.st_ino = stat->ino;
        if (sizeof(tmp.st_ino) < sizeof(stat->ino) && tmp.st_ino != stat->ino)
-               tmp.st_ino = 0;
+               tmp.st_ino = squash_ino(stat->ino);
        tmp.st_mode = stat->mode;
        tmp.st_nlink = stat->nlink;
        if (tmp.st_nlink != stat->nlink)
@@ -207,6 +217,7 @@ SYSCALL_DEFINE2(fstat, unsigned int, fd, struct __old_kernel_stat __user *, stat
 #  define INIT_STRUCT_STAT_PADDING(st) memset(&st, 0, sizeof(st))
 #endif

+
 static int cp_new_stat(struct kstat *stat, struct stat __user *statbuf)
 {
        struct stat tmp;
@@ -222,7 +233,7 @@ static int cp_new_stat(struct kstat *stat, struct stat __user *statbuf)
        tmp.st_dev = encode_dev(stat->dev);
        tmp.st_ino = stat->ino;
        if (sizeof(tmp.st_ino) < sizeof(stat->ino) && tmp.st_ino != stat->ino)
-               tmp.st_ino = 0;
+               tmp.st_ino = squash_ino(stat->ino);
        tmp.st_mode = stat->mode;
        tmp.st_nlink = stat->nlink;
        if (tmp.st_nlink != stat->nlink)

Создание патча для ядра

Documentation
Linux Kernel patch submission checklist
Linux kernel coding style
HOWTO do Linux kernel development
Mailing lists
majordomo@vger.kernel.org Linux Filesystem Development list

Сжатие inode в подгружаемой библиотеке

При запуске программы можно задать список библиотек, подгружаемых до ее запуска. Таким образом можно создать библиотеку с реализацией альтернативной функцией stat, в которой будет сжиматься или обнуляться соответствующее поле.