Как выбрать элемент файловой системы по устройству: пара инодов в perl или лучшее решение - PullRequest
1 голос
/ 22 апреля 2011

У меня довольно сложный Perl-скрипт, который просматривает файловую систему и сохраняет список обновленных владельцев, затем просматривает этот список и применяет изменения.Я делаю это для того, чтобы обновить измененные UID.Поскольку у меня есть несколько ситуаций, когда я меняю UID пользователя a и пользователя b, я не могу просто сказать «все, что сейчас 1, должно быть 2, а все, что 2, должно быть 1», так как также возможно, что этот скрипт могбудет прервана, и система останется в полностью отключенном, практически невосстановимом состоянии за пределами «восстановления из резервной копии и запуска заново».Что было бы довольно отстойно.

Чтобы избежать этой проблемы, я делаю описанный выше подход с двумя шагами, создавая структуру, такую ​​как $ changes -> {path} -> \% c, где c имеет строку атрибутов newuid,olduid, newgid и olduid.Затем я замораживаю хеш, и как только он записывается на диск, я снова читаю хеш и начинаю вносить изменения.Таким образом, если меня прервали, я могу проверить, существует ли замороженный хэш или нет, и просто начать применять изменения снова, если он есть.

Недостаток в том, что иногда изменяющийся пользователь имеет буквально миллионыфайлы, часто с очень длинными путями.Это означает, что я храню много действительно длинных строк в виде хэш-ключей, и иногда у меня заканчивается память.Итак, я предложил два варианта.Единственное, что имеет отношение к этому вопросу, - вместо этого хранить элементы как пары device: inode.Это было бы намного более экономно, и однозначно идентифицировало бы элементы файловой системы.Недостаток в том, что я не нашел особенно эффективного способа получить относительный к устройству путь от inode или просто применить изменения stat (), которые я хочу, к inode.Да, я мог бы сделать другую находку, и для каждого файла выполнить поиск по моему сохраненному списку устройств и inode, чтобы увидеть, нужно ли изменение.Но если есть системный вызов, доступный по perl - переносимый через HP-UX, AIX и Linux - из которого я могу прямо сказать: «на этом устройстве внесите эти изменения в этот inode», было бы заметно лучшеперспективы производительности.

Я использую это на нескольких тысячах систем, некоторые из которых имеют файловые системы в диапазоне петабайт и имеют триллионы файлов.Таким образом, хотя производительность может не сильно отличаться от моего домашнего ПК, на самом деле это несколько значимо в этом сценарии.:) Именно эта потребность в производительности, кстати, поэтому я действительно не хочу делать другой вариант - который будет обходить проблему с памятью, просто привязывая хеш к файлу на диске.И именно поэтому я предпочел бы сделать больше работы, чтобы избежать повторного обхода всей файловой системы.

Альтернативные предложения, которые могут уменьшить потребление памяти, также приветствуются.:) Мое требование заключается в том, что мне нужно записать как старые, так и новые значения UID / GID, чтобы я мог сохранить изменения / проверить изменения / обновить файлы, восстановленные из резервных копий, сделанных до даты очистки.Я подумал о том, чтобы сделать / path / to / file похожим на $ {changes} -> {root} -> {path} -> {to} -> {file}, но это гораздо больше работы для прохождения, и я неЯ знаю, что это действительно сэкономит мне достаточно памяти для решения моей проблемы.Свертывание всего этого в -> {device} -> {inode} делает его в основном размером только с двумя целыми числами, а не с N символами, что составляет субстандартное для любого пути, длиннее, скажем, 2 символов.:)

Ответы [ 2 ]

1 голос
/ 22 апреля 2011

Упрощенная идея

Когда я упоминал о потоке, я не имел в виду неконтролируемый . Журнал базы данных (например) также записывается в потоковом режиме для сравнения.

Также обратите внимание, что утверждение о том, что вы не можете позволить себе сортировать даже один подкаталог , прямо противоречит использованию Perl-хеша для хранения той же информации (я не буду винить вас, если вы не не имеет фона CS).

Итак, вот очень простая иллюстрация того, что вы могли бы сделать. Обратите внимание, что каждый шаг на пути является потоковым, повторяемым и регистрируется.

# export SOME_FIND_OPTIONS=...?
find $SOME_FIND_OPTIONS -print0 | ./generate_script.pl > chownscript.sh

# and then
sh -e ./chownscript.sh

Пример generate_script.pl (очевидно, адаптируйте его под свои нужды:)

#!/usr/bin/perl
use strict;
use warnings;

$/="\0";
while (<>)
{
    my ($dev,$ino,$mode,$nlink,$uid,$gid,$rdev,$size,$atime,$mtime,$ctime,$blksize,$blocks) = stat;

    # demo purpose, silly translation:
    my ($newuid, $newgid) = ($uid+1000, $gid+1000);

    print "./chmod.pl $uid:$gid $newuid:$newgid '$_'\n"
}

У вас может быть системно-зависимая реализация chmod.pl (это помогает уменьшить сложность и, следовательно,: риск):

#!/usr/bin/perl
use strict;
use warnings;

my $oldown = shift;
my $newown = shift;
my $path   = shift;

($oldown and $newown and $path) or die "usage: $0 <uid:gid> <newuid:newgid> <path>";

my ($dev,$ino,$mode,$nlink,$uid,$gid,$rdev,$size,$atime,$mtime,$ctime,$blksize,$blocks) = stat $path;

die "file not found: $path" unless $ino;
die "precondition failed" unless ($oldown eq "$uid:$gid");

($uid, $gid) = split /:/, $newown;

chown $uid, $gid, $path or die "unable to chown: $path"

Это позволит вам перезапускаться, когда что-то происходит на полпути, и даже позволит вам вручную выбирать исключения, если это необходимо. Вы можете сохранить сценарии, чтобы иметь подотчетность. Я сделал разумный удар по обеспечению безопасной работы скриптов. Тем не менее, это, очевидно, только отправная точка. Самое главное, я не имею дело с пересечениями файловой системы, символическими ссылками, сокетами, узлами устройств, где вы можете обратить на них внимание.


оригинальный ответ следует:

Идеи

Да, если проблема в производительности, сделайте это в C

Не ведите постоянное ведение журнала для всей файловой системы (кстати, зачем вам нужно хранить их в одном хеше? Потоковый вывод - ваш друг)

Вместо этого регистрируйте выполненные запуски для каждого каталога. Вы можете легко разбить отображение по шагам:

 user A: 1  -> 99
 user B: 2  -> 1
 user A: 99 -> 2

Ownify - что я использую ( код )

До тех пор, пока вы можете зарезервировать диапазон для временных uid / guids, таких как 99, не будет никакого риска необходимости перезапуска (не больше, чем * , чем выполнять эту транснумерацию в действующей файловой системе, в любом случае). ).

Вы могли бы начать с этого приятного небольшого фрагмента кода C (который, по общему признанию, не очень оптимистично):

// vim: se ts=4 sw=4 et ar aw 
//
// make: g++ -D_FILE_OFFSET_BITS=64 ownify.cpp -o ownify 
//
// Ownify: ownify -h
//

#include <stdio.h>

#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>

/* old habits die hard. can't stick to pure C ... */
#include <string>
#include <iostream>

#define do_stat(a,b)        lstat(a,b)
#define do_chown(a,b,c)     lchown(a,b,c)

//////////////////////////////////////////////////////////
// logic declarations
//
void ownify(struct stat& file)
{
//  if (S_ISLNK(file.st_mode))
//      return;
    switch (file.st_uid)
    {
#if defined(PASS1)
        case 1:  file.st_uid = 99; break;
        case 99: fputs(err, "Unexpected existing owned file!"); exit(255);
#elif defined(PASS2)
        case 2:  file.st_uid = 1;  break;
#elif defined(PASS3)
        case 99: file.st_uid = 1;  break;
#endif
    }
    switch (file.st_gid) // optionally map groups as well
    {
#if defined(PASS1)
#elif defined(PASS2)
#elif defined(PASS3)
#endif
    }
}

/////////////////////////////////////////////////////////
// driver
//
static unsigned int changed = 0, skipped = 0, failed  = 0;
static bool dryrun = false;

void process(const char* const fname)
{
    struct stat s;
    if (0==do_stat(fname, &s))
    {
        struct stat n = s;
        ownify(n);

        if ((n.st_uid!=s.st_uid) || (n.st_gid!=s.st_gid))
        {
            if (dryrun || 0==do_chown(fname, n.st_uid, n.st_gid))
                printf("%u\tchanging owner %i:%i '%s'\t(was %i:%i)\n", 
                        ++changed,
                        n.st_uid, n.st_gid, 
                        fname, 
                        s.st_uid, s.st_gid);
            else 
            {
                failed++;
                int e = errno;
                fprintf(stderr, "'%s': cannot change owner %i:%i (%s)\n", 
                        fname, 
                        n.st_uid, n.st_gid, 
                        strerror(e));
            }
        }
        else
            skipped++;
    } else
    {
        int e = errno;
        fprintf(stderr, "'%s': cannot stat (%s)\n", fname, strerror(e));
        failed++;
    }
}

int main(int argc, char* argv[])
{
    switch(argc)
    {   
        case 0: //huh?
        case 1: break;
        case 2:
            dryrun = 0==strcmp(argv[1],"-n") || 
                0==strcmp(argv[1],"--dry-run");
            if (dryrun)
                break;
        default:
            std::cerr << "Illegal arguments" << std::endl;
            std::cout << 
                argv[0] << " (Ownify): efficient bulk adjust of owner user:group for many files\n\n"
                           "Goal: be flexible and a tiny bit fast\n\n" 
                           "Synopsis:\n"
                           "    find / -print0 | ./ownify -n 2>&1 | tee ownify.log\n\n"
                           "Input:\n"
                           "    reads a null-delimited stream of filespecifications from the\n"
                           "    standard input; links are _not_ dereferenced.\n\n"
                           "Options:\n"
                           "    -n/--dry-run    - test run (no changes)\n\n"
                           "Exit code:\n"
                           "    number of failed items" << std::endl;
            return 255;
    }

    std::string fname("/dev/null");
    while (std::getline(std::cin, fname, '\0'))
        process(fname.c_str());

    fprintf(stderr, "%s: completed with %u skipped, %u changed and %u failed%s\n", 
            argv[0], skipped, changed, failed, dryrun?" (DRYRUN)":"");

    return failed;
}

Обратите внимание, что для этого требуется немало мер безопасности

  • проверка на паранойю при первом прохождении (проверьте, нет ли полей с зарезервированным uid)
  • возможность изменять поведение do_stat и do_chown относительно ссылок
  • a --dry-run (чтобы посмотреть, что будет сделано ) -n

Программа с удовольствием расскажет вам, как ее использовать с ownify -h:

./ownify (Ownify): efficient bulk adjust of owner user:group for many files

Goal: be flexible and a tiny bit fast

Synopsis:
    find / -print0 | ./ownify -n 2>&1 | tee ownify.log

Input:
    reads a null-delimited stream of file specifications from the
    standard input; 

Options:
    -n/--dry-run    - test run (no changes)

Exit code:
    number of failed items
0 голосов
/ 22 апреля 2011

Несколько возможных решений, которые приходят на ум:

1) Не сохраняйте хэш в файле, просто отсортированный список в любом формате, который может быть разумно проанализирован последовательно.Сортируя список по имени файла, вы должны получить эквивалент запуска 10000 * снова, фактически не делая этого:

# UID, GID, MODE, Filename
0,0,600,/a/b/c/d/e
1,1,777,/a/b/c/f/g
...

Поскольку список отсортирован по имени файла, содержимое каждого каталога должно быть сгруппировано вместев файле.Вам не нужно использовать Perl для сортировки файла, sort будет хорошо работать в большинстве случаев.

Затем вы можете просто читать в файле построчно - или с любым разделителем, который не будет искажатьВаши имена файлов - и просто внесите любые изменения.Предполагая, что вы можете сказать, какие изменения необходимы для каждого файла одновременно, это не звучит так, как будто вам действительно нужны возможности произвольного доступа для хэша, поэтому это должно быть сделано.

Таким образом, процесс будет происходить втри шага:

  • Создание файла изменений
  • Сортировка файла изменений
  • Выполнение изменений для файла изменений

2) ЕслиВы не можете сказать, какие изменения нужны каждому файлу за раз, у вас может быть несколько строк для каждого файла, каждая из которых подробно описывает часть изменений.Каждая строка будет произведена в тот момент, когда вы определите необходимое изменение на первом шаге.Затем вы можете объединить их после сортировки.

3) Если вам do нужны возможности произвольного доступа, рассмотрите возможность использования подходящей встроенной базы данных, такой как BerkeleyDB или SQLite.Для большинства встроенных баз данных существуют модули Perl.Хотя это будет не так быстро.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...