[devel] I: rpm in p8 had problems reading *.rpm files

Ivan Zakharyaschev imz на altlinux.org
Пт Июл 12 17:48:07 MSK 2019


On Thu, 11 Jul 2019, Ivan Zakharyaschev wrote:

> Из отдела тестирования сообщили, что в Workstation 8.2 i586 наблюдаются 
> проблемы при чтении пакетов из репозитория p8, смонтированного по NFS.
> 
> # /usr/lib/rpm/rpmq -q -i --list --verbose --package -q --qf "[* %{CHANGELOGNAME}\n %{CHANGELOGTEXT}\n]" /mnt/repo-nhcR/pub/distributions/ALTLinux/p8/branch/files/i586/RPMS/sssd-ad-1.16.4-alt1.M80P.1.i586.rpm
> ошибка: ошибка запроса /mnt/repo-nhcR/pub/distributions/ALTLinux/p8/branch/files/i586/RPMS/sssd-ad-1.16.4-alt1.M80P.1.i586.rpm
> 
> С обновлённым rpm-4.0.4-alt101.M80P.5.5 такой проблемы нет.
> 
> Я примерно понял, какой код там выполняется, а какой с обновлённым rpm, 
> когда ошибки нет. (Они идут разным путём.)

Вчера я понял более точно.

> Когда ошибка случается, вывод strace -fy -s1000 перед этим такой:
> 
> open("/mnt/repo-nhcR/pub/distributions/ALTLinux/p8/branch/files/i586/RPMS/sssd-ad-1.16.4-alt1.M80P.1.i586.rpm", 
> O_RDONLY) = 
> 3</mnt/repo-nhcR/pub/distributions/ALTLinux/p8/branch/files/i586/RPMS/sssd-ad-1.16.4-alt1.M80P.1.i586.rpm>
> fcntl64(3</mnt/repo-nhcR/pub/distributions/ALTLinux/p8/branch/files/i586/RPMS/sssd-ad-1.16.4-alt1.M80P.1.i586.rpm>, 
> F_SETFD, FD_CLOEXEC) = 0
> gettimeofday({tv_sec=1562834023, tv_usec=224425}, NULL) = 0
> fstat64(3</mnt/repo-nhcR/pub/distributions/ALTLinux/p8/branch/files/i586/RPMS/sssd-ad-1.16.4-alt1.M80P.1.i586.rpm>, 
> {st_mode=S_IFREG|0644, st_size=122129, ...}) = 0
> _newselect(4, 
> [3</mnt/repo-nhcR/pub/distributions/ALTLinux/p8/branch/files/i586/RPMS/sssd-ad-1.16.4-alt1.M80P.1.i586.rpm>], 
> NULL, NULL, {tv_sec=1, tv_usec=0}) = 1 (in [3], left {tv_sec=0, 
> tv_usec=999999})
> gettimeofday({tv_sec=1562834023, tv_usec=224634}, NULL) = 0
> read(3</mnt/repo-nhcR/pub/distributions/ALTLinux/p8/branch/files/i586/RPMS/sssd-ad-1.16.4-alt1.M80P.1.i586.rpm>, 
> "\355\253\356\333\3\0\0\0\0\1sssd-ad-1.16.4-alt1.M80P.1\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\1\0\5\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0", 
> 96) = 96
> gettimeofday({tv_sec=1562834023, tv_usec=224766}, NULL) = 0
> _newselect(4, 
> [3</mnt/repo-nhcR/pub/distributions/ALTLinux/p8/branch/files/i586/RPMS/sssd-ad-1.16.4-alt1.M80P.1.i586.rpm>], 
> NULL, NULL, {tv_sec=1, tv_usec=0}) = 1 (in [3], left {tv_sec=0, 
> tv_usec=999999})
> gettimeofday({tv_sec=1562834023, tv_usec=224870}, NULL) = 0
> read(3</mnt/repo-nhcR/pub/distributions/ALTLinux/p8/branch/files/i586/RPMS/sssd-ad-1.16.4-alt1.M80P.1.i586.rpm>, 
> "\216\255\350\1\0\0\0\0\0\0\0\5\0\0\0\230", 16) = 16
> gettimeofday({tv_sec=1562834023, tv_usec=224953}, NULL) = 0
> _newselect(4, 
> [3</mnt/repo-nhcR/pub/distributions/ALTLinux/p8/branch/files/i586/RPMS/sssd-ad-1.16.4-alt1.M80P.1.i586.rpm>], 
> NULL, NULL, {tv_sec=1, tv_usec=0}) = 1 (in [3], left {tv_sec=0, 
> tv_usec=999999})
> gettimeofday({tv_sec=1562834023, tv_usec=225069}, NULL) = 0
> read(3</mnt/repo-nhcR/pub/distributions/ALTLinux/p8/branch/files/i586/RPMS/sssd-ad-1.16.4-alt1.M80P.1.i586.rpm>, 
> "\0\0\0>\0\0\0\7\0\0\0\210\0\0\0\20\0\0\1\r\0\0\0\6\0\0\0\0\0\0\0\1\0\0\3\350\0\0\0\4\0\0\0,\0\0\0\1\0\0\3\354\0\0\0\7\0\0\0000\0\0\0\20\0\0\3\355\0\0\0\7\0\0\0@\0\0\0H7ce3371ed3cb8e8bcea5a341a029ac364173f7ab\0\0\0\0\0\1\333\271\271v\nq\244\1\271&VY\242\257RH\260\220\210F\4\0\21\2\0\6\5\2]\33\225b\0\n\t\20\334\236\225\302#\21\24\263GF\0\237`,L\375\226t\370s\35\206\202\"\265k\364s:\206\v\31\0\240\256VMa\356w*M\352\36\374!Z^J\277\16\356\256?\0\0\0>\0\0\0\7\377\377\377\260\0\0\0\20", 
> 232) = 232
> gettimeofday({tv_sec=1562834023, tv_usec=225146}, NULL) = 0
> fstat64(3</mnt/repo-nhcR/pub/distributions/ALTLinux/p8/branch/files/i586/RPMS/sssd-ad-1.16.4-alt1.M80P.1.i586.rpm>, 
> {st_mode=S_IFREG|0644, st_size=122129, ...}) = 0
> gettimeofday({tv_sec=1562834023, tv_usec=225226}, NULL) = 0
> close(3</mnt/repo-nhcR/pub/distributions/ALTLinux/p8/branch/files/i586/RPMS/sssd-ad-1.16.4-alt1.M80P.1.i586.rpm>) 
> = 0
> 
> Когда ошибки нет, аналогичный кусок у меня (правда, на x86_64) такой:

> ...
> 
> Обращает на себя внимание наличие при работе старого rpm вызовов 
> gettimeofday(), а в обновлённом -- просто select(). Это свидетельствует, 

Просто на x86_64 не делается вызов gettimeofday(), оно реализуется внутри 
linux-vdso.so.1, так что зря я считал, что разный код в rpm выполняется.

> что в обновлённом rpm используется набор функций для работы с файлами 
> "ufdio", а в старом -- "fdio". Наверное, "fdio" глючный.

В любом случае в rpm ufdRead() внутри использует и вызывает fdRead().

Дело не в глючности fdRead(). Дело в том, что на той системе на nfs 
номер inode свежих пакетов в зеркале стал слишком большим для i586 без 
Large File Support. (А кое-где в rpm вызывается fstat() и хоть системный 
вызов нормально отрабатывает, glibc возвращает ошибку, потому что не может 
поместить номер inode в старую структуру.)

Так что коротко вывод такой же: нужно обновить rpm, а обновлённый rpm уже 
собран с Large File Support. Также вы можете в sources.list поменять 
временно file: на copy: и тогда rpm не будет обращаться к файлам на nfs.

Подробности использования fstat() в rpm в этом случае такие:

1. lib/package.c

static rpmRC readPackageHeaders(FD_t fd,
		/*@null@*/ /*@out@*/ struct rpmlead * leadPtr,
		/*@null@*/ /*@out@*/ Header * sigs,
		/*@null@*/ /*@out@*/ Header * hdrPtr)
	/*@modifies fd, *leadPtr, *sigs, *hdrPtr @*/
{
    struct stat sb;
    int readahead_off = 0;
    /* if fd points to a socket, pipe, etc, sb.st_size is *always* zero */
    if (fstat(Fileno(fd), &sb) == 0 && S_ISREG(sb.st_mode)) {
	if (sb.st_size < sizeof(struct rpmlead))
	    return 1;
	/* Typical header size is 4-16K, and default readahead is 128K.
	 * When scanning a large number of packages (with e.g. rpmquery),
	 * readahead might cause negative effects on the buffer cache. */
	if (sb.st_size > /* page size */ 4096)
	    if (posix_fadvise(Fileno(fd), 0, 0, POSIX_FADV_RANDOM) == 0)
		readahead_off = 1;
    }
    rpmRC rc = do_readPackageHeaders(fd, leadPtr, sigs, hdrPtr);
    /* re-enable readahead for cpio */
    if (readahead_off)
	posix_fadvise(Fileno(fd), 0, 0, POSIX_FADV_NORMAL);
    return rc;
}

Тут просто в случае ошибки fstat() не будет вызван posix_fadvise(), что 
некритично.

2. rpmio/rpmio.c

static ssize_t ufdRead(void * cookie, /*@out@*/ char * buf, size_t count)
	/*@globals fileSystem, internalState @*/
	/*@modifies *buf, fileSystem, internalState @*/
{
    FD_t fd = c2f(cookie);
    int bytesRead;
    int total;

    *buf = '\0';	/* LCL: insistent bugger. */
    /* XXX preserve timedRead() behavior */
    if (fdGetIo(fd) == fdio) {
	struct stat sb;
	int fdno = fdFileno(fd);
	(void) fstat(fdno, &sb);
	if (S_ISREG(sb.st_mode))
	    return fdRead(fd, buf, count);
    }

    UFDONLY(fd);
    assert(fd->rd_timeoutsecs >= 0);

    for (total = 0; total < count; total += bytesRead) {

	int rc;

	bytesRead = 0;

	/* Is there data to read? */
	if (fd->bytesRemain == 0) return total;	/* XXX simulate EOF */
	rc = fdReadable(fd, fd->rd_timeoutsecs);

	switch (rc) {
	case -1:	/* error */
	case  0:	/* timeout */
	    return total;
	    /*@notreached@*/ /*@switchbreak@*/ break;
	default:	/* data to read */
	    /*@switchbreak@*/ break;
	}

	rc = fdRead(fd, buf + total, count - total);

Тут, конечно, глючная проверка S_ISREG(sb.st_mode) после того, как
(void) fstat(fdno, &sb); завершился с ошибкой. Но фактически это ни на что 
влияет в этом случае, потому что всё равно вызывается fdRead() на обоих 
путях.

3. lib/signature.c

static inline rpmRC checkSize(FD_t fd, int siglen, int pad, int datalen)
	/*@globals fileSystem @*/
	/*@modifies fileSystem @*/
{
    struct stat st;
    int delta;
    rpmRC rc;

    if (fstat(Fileno(fd), &st))
	return RPMRC_FAIL;

    if (!S_ISREG(st.st_mode)) {
	rpmMessage(RPMMESS_DEBUG,
	    _("file is not regular -- skipping size check\n"));
	return RPMRC_OK;
    }

Вот здесь checkSize() возвращает RPMRC_FAIL и из-за этого rpmq прекращает 
работу. (Хотя можно было бы пережить так же, как в случае нерегулярного 
файла.)

Особого повода что-либо в этом коде исправлять у нас больше нет, потому 
что уже включён Large File Support. На практике я не знаю, каие ещё могут 
быть случаи отказа fstat(), которые мы хотели бы пережить.


> Вероятно, выбор в 
> пользу "ufdio" поменялся в серии патчей Алексея Турбина at@ про Large File 
> Support.
> 
> Вряд ли пакеты из репозитория как-то плохо сформированы, да этот пример 
> вроде и не очень большого файла пакета.
> 
> Так что могу посоветовать всем пользователям p8 обновить сначала rpm, 
> чтобы избегать таких проблем.
> 
> -- 
> Best regards,
> Ivan


Подробная информация о списке рассылки Devel