From patchwork Fri Oct 2 17:06:15 2020 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Adhemerval Zanella Netto X-Patchwork-Id: 40648 Return-Path: X-Original-To: patchwork@sourceware.org Delivered-To: patchwork@sourceware.org Received: from server2.sourceware.org (localhost [IPv6:::1]) by sourceware.org (Postfix) with ESMTP id 532E8398E467; Fri, 2 Oct 2020 17:06:39 +0000 (GMT) DKIM-Filter: OpenDKIM Filter v2.11.0 sourceware.org 532E8398E467 DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=sourceware.org; s=default; t=1601658399; bh=+afOXFMgBIVbJ7bAdwvbWBkbhttPtUAm5ToxE5uIXXM=; h=To:Subject:Date:In-Reply-To:References:List-Id:List-Unsubscribe: List-Archive:List-Post:List-Help:List-Subscribe:From:Reply-To:Cc: From; b=hFcEVM45jb3u9vr2tno9om/sLwWLRhp0pIlEQRQcbhcNyp93tXqBvn1lAgeLrVcnJ 2jlfoW0zBtvgovW/XrrA/8Js+LDr8toxbzIuSxaSGwWCM3Cy4Wc8Qyh5Ot92deJwvG 1sNafS7i7gMc1j1kHhOyw/aiIuZKTMsfeT5HGmEY= X-Original-To: libc-alpha@sourceware.org Delivered-To: libc-alpha@sourceware.org Received: from mail-qt1-x841.google.com (mail-qt1-x841.google.com [IPv6:2607:f8b0:4864:20::841]) by sourceware.org (Postfix) with ESMTPS id A8834398E460 for ; Fri, 2 Oct 2020 17:06:35 +0000 (GMT) DMARC-Filter: OpenDMARC Filter v1.3.2 sourceware.org A8834398E460 Received: by mail-qt1-x841.google.com with SMTP id d1so1827974qtr.6 for ; Fri, 02 Oct 2020 10:06:35 -0700 (PDT) X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20161025; h=x-gm-message-state:from:to:cc:subject:date:message-id:in-reply-to :references:mime-version:content-transfer-encoding; bh=+afOXFMgBIVbJ7bAdwvbWBkbhttPtUAm5ToxE5uIXXM=; b=m5sUeVJE8wp2r/yCUay1eVJfgLFfdbn4tRTv6ygQ+Y5j/x2MqEtDSUJMswdIbHofN6 P4kab+OZGZDckuAGtC/jkJOwYtsHRPl7bNNHkoFPdQm7vNYIua0bt89i+ONaz4Tfm5h3 rkuUhBHOEP52ScMWUEKv2ZXarXPG2uUuPwaj4jzUizQ6BjOThnylZNTQIS+WRyATSfpO vhHB7RkJwOkc3iQi0Xk/qgLkodqPOJblKghPF/SxhXromo+F87Dxw5R1CgjGBzjV1B4S gVkbmBZsvciZ+zIMFahdK2wRqLf5XHkQy36XS2363RZkT87JbQOSjct3SaqUnTlvOC+r 4VPw== X-Gm-Message-State: AOAM532c/Z5CKKVgeUd8gyxmA0D9GADvge/oj7lt/sovAhIzhLKLq1v2 xQBu8ozoxEqQNRMQb30bM0kODCv+eiN5XA== X-Google-Smtp-Source: ABdhPJyOSeT8pmm12auPzqcTvLq2k7xh6Nas7x3my1nPOU8zmf7rVg+s2qh6UZ3bnXvFIhh/eehyMg== X-Received: by 2002:ac8:44aa:: with SMTP id a10mr3440826qto.116.1601658394862; Fri, 02 Oct 2020 10:06:34 -0700 (PDT) Received: from localhost.localdomain ([177.194.48.209]) by smtp.googlemail.com with ESMTPSA id d10sm1436891qkk.1.2020.10.02.10.06.33 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Fri, 02 Oct 2020 10:06:34 -0700 (PDT) To: libc-alpha@sourceware.org Subject: [PATCH v2 4/9] linux: Use getdents64 on non-LFS readdir Date: Fri, 2 Oct 2020 14:06:15 -0300 Message-Id: <20201002170620.1611673-5-adhemerval.zanella@linaro.org> X-Mailer: git-send-email 2.25.1 In-Reply-To: <20201002170620.1611673-1-adhemerval.zanella@linaro.org> References: <20201002170620.1611673-1-adhemerval.zanella@linaro.org> MIME-Version: 1.0 X-Spam-Status: No, score=-13.3 required=5.0 tests=BAYES_00, DKIM_SIGNED, DKIM_VALID, DKIM_VALID_AU, DKIM_VALID_EF, GIT_PATCH_0, KAM_SHORT, RCVD_IN_DNSWL_NONE, SPF_HELO_NONE, SPF_PASS, TXREP autolearn=ham autolearn_force=no version=3.4.2 X-Spam-Checker-Version: SpamAssassin 3.4.2 (2018-09-13) on server2.sourceware.org X-BeenThere: libc-alpha@sourceware.org X-Mailman-Version: 2.1.29 Precedence: list List-Id: Libc-alpha mailing list List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , X-Patchwork-Original-From: Adhemerval Zanella via Libc-alpha From: Adhemerval Zanella Netto Reply-To: Adhemerval Zanella Cc: James Clarke , John Paul Adrian Glaubitz Errors-To: libc-alpha-bounces@sourceware.org Sender: "Libc-alpha" It reserves some space on the allocated internal buffer to be used as a the returned dirent struct. The kernel obtained dirent64 struct are copied to the temporary buffer on each readdir call. The overflow test is moved once the dirent64 entry is copied to the temporary buffer, and a subsequent readdir will obtain the next entry. The idea is an overflow fails to return the entry on readdir, but a next readdir might still obtain the next entry. (for filesystem that does not have the concept of sequential d_off, such as ext4). Checked on x86_64-linux-gnu and i686-linux-gnu. --- sysdeps/unix/sysv/linux/opendir.c | 6 +- sysdeps/unix/sysv/linux/readdir.c | 25 +++---- sysdeps/unix/sysv/linux/readdir.h | 117 ++++++++++++++++++++++++++++++ 3 files changed, 131 insertions(+), 17 deletions(-) create mode 100644 sysdeps/unix/sysv/linux/readdir.h diff --git a/sysdeps/unix/sysv/linux/opendir.c b/sysdeps/unix/sysv/linux/opendir.c index 2198224588..24bd63d2ba 100644 --- a/sysdeps/unix/sysv/linux/opendir.c +++ b/sysdeps/unix/sysv/linux/opendir.c @@ -22,6 +22,7 @@ #include /* For MIN and MAX. */ #include +#include /* For return_buffer_size. */ enum { opendir_oflags = O_RDONLY|O_NDELAY|O_DIRECTORY|O_LARGEFILE|O_CLOEXEC @@ -103,8 +104,9 @@ __alloc_dir (int fd, bool close_fd, int flags, const struct stat64 *statp) enum { max_buffer_size = 1048576 }; const size_t allocation_size = 32768; - _Static_assert (allocation_size >= sizeof (struct dirent64), - "allocation_size < sizeof (struct dirent64)"); + _Static_assert (allocation_size >= sizeof (struct dirent64) + + return_buffer_size, + "opendir buffer size smaller than required"); /* Increase allocation if requested, but not if the value appears to be bogus. It will be between 32Kb and 1Mb. */ diff --git a/sysdeps/unix/sysv/linux/readdir.c b/sysdeps/unix/sysv/linux/readdir.c index ca2a8964e9..8eab0f4c9b 100644 --- a/sysdeps/unix/sysv/linux/readdir.c +++ b/sysdeps/unix/sysv/linux/readdir.c @@ -19,7 +19,7 @@ #include #if !_DIRENT_MATCHES_DIRENT64 -#include +#include /* Read a directory entry from DIRP. */ struct dirent * @@ -30,16 +30,12 @@ __readdir_unlocked (DIR *dirp) do { - size_t reclen; - if (dirp->offset >= dirp->size) { /* We've emptied out our buffer. Refill it. */ - size_t maxread = dirp->allocation; - ssize_t bytes; - - bytes = __getdents (dirp->fd, dirp->data, maxread); + ssize_t bytes = __getdents64 (dirp->fd, dirstream_data (dirp), + dirstream_alloc_size (dirp)); if (bytes <= 0) { /* On some systems getdents fails with ENOENT when the @@ -54,19 +50,18 @@ __readdir_unlocked (DIR *dirp) dp = NULL; break; } - dirp->size = (size_t) bytes; + dirp->size = bytes; /* Reset the offset into the buffer. */ dirp->offset = 0; } - dp = (struct dirent *) &dirp->data[dirp->offset]; - - reclen = dp->d_reclen; - - dirp->offset += reclen; - - dirp->filepos = dp->d_off; + dp = dirstream_ret_entry (dirp); + if (dp == NULL) + { + __set_errno (EOVERFLOW); + break; + } /* Skip deleted files. */ } while (dp->d_ino == 0); diff --git a/sysdeps/unix/sysv/linux/readdir.h b/sysdeps/unix/sysv/linux/readdir.h new file mode 100644 index 0000000000..4dc219e220 --- /dev/null +++ b/sysdeps/unix/sysv/linux/readdir.h @@ -0,0 +1,117 @@ +/* Linux readdir internal implementation details. + Copyright (C) 2020 Free Software Foundation, Inc. + This file is part of the GNU C Library. + + The GNU C Library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + The GNU C Library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with the GNU C Library; if not, see + . */ + +#ifndef _DIRSTREAM_NOLFS_H +#define _DIRSTREAM_NOLFS_H + +#if !_DIRENT_MATCHES_DIRENT64 +# include + +/* getdents64 is used internally for both LFS and non-LFS implementations. + The non-LFS interface reserves part of the allocated buffer to return the + non-LFS 'struct dirent' entry. */ + +/* This defines the reserved space size on DIR internal buffer to use as the + returned 'struct dirent' from a 'readdir' call. + + The largest possible practical length of the d_name member are 255 + Unicode characters in UTF-8 encoding, so d_name is 766 bytes long, plus + 10 bytes from header, for a total of 776 bytes total. + + Also it should take in cosideration the alignment requirement for + getdents64 call. */ +enum { return_buffer_size = 1024 + + sizeof (off64_t) + - _Alignof (((struct __dirstream) {0}).data) }; + +_Static_assert ((_Alignof (((struct __dirstream) {0}).data) + + return_buffer_size) % sizeof (off64_t) == 0, + "return_buffer_size does not align the buffer properly"); + +/* Return the avaliable buffer size to use with getdents64 calls. */ +static inline size_t +dirstream_alloc_size (struct __dirstream *ds) +{ + return ds->allocation - return_buffer_size; +} + +/* Return the start of the allocated buffer minus the reserved part to use on + non-LFS readdir call. */ +static inline void * +dirstream_data (struct __dirstream *ds) +{ + return (char *) ds->data + return_buffer_size; +} + +/* Return the allocated buffer used on non-LFS readdir call. */ +static inline struct dirent * +dirstream_ret (struct __dirstream *ds) +{ + return (struct dirent *) ds->data; +} + +/* Return the current dirent64 entry from the reserved buffer used on + getdent64. */ +static inline struct dirent64 * +dirstream_entry (struct __dirstream *ds) +{ + size_t offset = return_buffer_size + ds->offset; + return (struct dirent64 *) ((char *) ds->data + offset); +} + +/* Copy one obtained entry from 'getdents64' call to the reserved space + on DS allocated buffer and updated its internal state. */ +static inline struct dirent * +dirstream_ret_entry (struct __dirstream *ds) +{ + struct dirent64 *dp64 = dirstream_entry (ds); + struct dirent *dp = dirstream_ret (ds); + + dp->d_ino = dp64->d_ino; + + dp->d_off = dp64->d_off; + if (dp->d_off != dp64->d_off) + /* Overflow. */ + return NULL; + + const size_t size_diff = (offsetof (struct dirent64, d_name) + - offsetof (struct dirent, d_name)); + const size_t alignment = _Alignof (struct dirent); + size_t new_reclen = (dp64->d_reclen - size_diff + alignment - 1) + & ~(alignment - 1); + if (new_reclen > return_buffer_size) + /* Overflow. */ + return NULL; + dp->d_reclen = new_reclen; + + dp->d_type = dp64->d_type; + + memcpy (dp->d_name, dp64->d_name, + dp64->d_reclen - offsetof (struct dirent64, d_name)); + + ds->offset += dp64->d_reclen; + ds->filepos = dp64->d_off; + + return dp; +} +#else +/* No need to reserve an buffer space if dirent has already LFS support. */ +enum { return_buffer_size = 0 }; +#endif /* _DIRENT_MATCHES_DIRENT64 */ + +#endif