| commit bdee910e88006ae33dc83ac3d2c0708adb6627d0 |
| Author: Florian Weimer <fweimer@redhat.com> |
| Date: Wed Jul 15 13:41:31 2020 +0200 |
| |
| nss: Add __nss_fgetent_r |
| |
| And helper functions __nss_readline, __nss_readline_seek, |
| __nss_parse_line_result. |
| |
| This consolidates common code for handling overlong lines and |
| parse files. Use the new functionality in internal_getent |
| in nss/nss_files/files-XXX.c. |
| |
| Tested-by: Carlos O'Donell <carlos@redhat.com> |
| Reviewed-by: Carlos O'Donell <carlos@redhat.com> |
| |
| diff -rupN a/include/nss_files.h b/include/nss_files.h |
| |
| |
| @@ -25,6 +25,28 @@ |
| FILE *__nss_files_fopen (const char *path); |
| libc_hidden_proto (__nss_files_fopen) |
| |
| +/* Read a line from FP, storing it BUF. Strip leading blanks and skip |
| + comments. Sets errno and returns error code on failure. Special |
| + failure: ERANGE means the buffer is too small. The function writes |
| + the original offset to *POFFSET (which can be negative in the case |
| + of non-seekable input). */ |
| +int __nss_readline (FILE *fp, char *buf, size_t len, off64_t *poffset); |
| +libc_hidden_proto (__nss_readline) |
| + |
| +/* Seek FP to OFFSET. Sets errno and returns error code on failure. |
| + On success, sets errno to ERANGE and returns ERANGE (to indicate |
| + re-reading of the same input line to the caller). If OFFSET is |
| + negative, fail with ESPIPE without seeking. Intended to be used |
| + after parsing data read by __nss_readline failed with ERANGE. */ |
| +int __nss_readline_seek (FILE *fp, off64_t offset) attribute_hidden; |
| + |
| +/* Handles the result of a parse_line call (as defined by |
| + nss/nss_files/files-parse.c). Adjusts the file offset of FP as |
| + necessary. Returns 0 on success, and updates errno on failure (and |
| + returns that error code). */ |
| +int __nss_parse_line_result (FILE *fp, off64_t offset, int parse_line_result); |
| +libc_hidden_proto (__nss_parse_line_result) |
| + |
| struct parser_data; |
| |
| /* Instances of the parse_line function from |
| @@ -52,4 +74,11 @@ libnss_files_hidden_proto (_nss_files_pa |
| libc_hidden_proto (_nss_files_parse_sgent) |
| libc_hidden_proto (_nss_files_parse_spent) |
| |
| +/* Generic implementation of fget*ent_r. Reads lines from FP until |
| + EOF or a successful parse into *RESULT using PARSER. Returns 0 on |
| + success, ENOENT on EOF, ERANGE on too-small buffer. */ |
| +int __nss_fgetent_r (FILE *fp, void *result, |
| + char *buffer, size_t buffer_length, |
| + nss_files_parse_line parser) attribute_hidden; |
| + |
| #endif /* _NSS_FILES_H */ |
| diff -rupN a/nss/Makefile b/nss/Makefile |
| |
| |
| @@ -28,7 +28,9 @@ headers := nss.h |
| routines = nsswitch getnssent getnssent_r digits_dots \ |
| valid_field valid_list_field rewrite_field \ |
| $(addsuffix -lookup,$(databases)) \ |
| - compat-lookup nss_hash nss_files_fopen |
| + compat-lookup nss_hash nss_files_fopen \ |
| + nss_readline nss_parse_line_result \ |
| + nss_fgetent_r |
| |
| # These are the databases that go through nss dispatch. |
| # Caution: if you add a database here, you must add its real name |
| diff -rupN a/nss/Versions b/nss/Versions |
| |
| |
| @@ -21,7 +21,7 @@ libc { |
| __nss_passwd_lookup2; __nss_group_lookup2; __nss_hosts_lookup2; |
| __nss_services_lookup2; __nss_next2; __nss_lookup; |
| __nss_hash; __nss_database_lookup2; |
| - __nss_files_fopen; |
| + __nss_files_fopen; __nss_readline; __nss_parse_line_result; |
| } |
| } |
| |
| diff -rupN a/nss/nss_fgetent_r.c b/nss/nss_fgetent_r.c |
| |
| |
| @@ -0,0 +1,55 @@ |
| +/* Generic implementation of fget*ent_r. |
| + 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 |
| + <https://www.gnu.org/licenses/>. */ |
| + |
| +#include <errno.h> |
| +#include <nss_files.h> |
| + |
| +int |
| +__nss_fgetent_r (FILE *fp, void *result, char *buffer, size_t buffer_length, |
| + nss_files_parse_line parser) |
| +{ |
| + int ret; |
| + |
| + _IO_flockfile (fp); |
| + |
| + while (true) |
| + { |
| + off64_t original_offset; |
| + ret = __nss_readline (fp, buffer, buffer_length, &original_offset); |
| + if (ret == 0) |
| + { |
| + /* Parse the line into *RESULT. */ |
| + ret = parser (buffer, result, |
| + (struct parser_data *) buffer, buffer_length, &errno); |
| + |
| + /* Translate the result code from the parser into an errno |
| + value. Also seeks back to the start of the line if |
| + necessary. */ |
| + ret = __nss_parse_line_result (fp, original_offset, ret); |
| + |
| + if (ret == EINVAL) |
| + /* Skip over malformed lines. */ |
| + continue; |
| + } |
| + break; |
| + } |
| + |
| + _IO_funlockfile (fp); |
| + |
| + return ret; |
| +} |
| diff -rupN a/nss/nss_files/files-XXX.c b/nss/nss_files/files-XXX.c |
| |
| |
| @@ -135,10 +135,9 @@ internal_getent (FILE *stream, struct ST |
| char *buffer, size_t buflen, int *errnop H_ERRNO_PROTO |
| EXTRA_ARGS_DECL) |
| { |
| - char *p; |
| struct parser_data *data = (void *) buffer; |
| size_t linebuflen = buffer + buflen - data->linebuffer; |
| - int parse_result; |
| + int saved_errno = errno; /* Do not clobber errno on success. */ |
| |
| if (buflen < sizeof *data + 2) |
| { |
| @@ -149,66 +148,42 @@ internal_getent (FILE *stream, struct ST |
| |
| while (true) |
| { |
| - ssize_t r = __libc_readline_unlocked |
| - (stream, data->linebuffer, linebuflen); |
| - if (r < 0) |
| - { |
| - *errnop = errno; |
| - H_ERRNO_SET (NETDB_INTERNAL); |
| - if (*errnop == ERANGE) |
| - /* Request larger buffer. */ |
| - return NSS_STATUS_TRYAGAIN; |
| - else |
| - /* Other read failure. */ |
| - return NSS_STATUS_UNAVAIL; |
| - } |
| - else if (r == 0) |
| + off64_t original_offset; |
| + int ret = __nss_readline (stream, data->linebuffer, linebuflen, |
| + &original_offset); |
| + if (ret == ENOENT) |
| { |
| /* End of file. */ |
| H_ERRNO_SET (HOST_NOT_FOUND); |
| + __set_errno (saved_errno); |
| return NSS_STATUS_NOTFOUND; |
| } |
| - |
| - /* Everything OK. Now skip leading blanks. */ |
| - p = data->linebuffer; |
| - while (isspace (*p)) |
| - ++p; |
| - |
| - /* Ignore empty and comment lines. */ |
| - if (*p == '\0' || *p == '#') |
| - continue; |
| - |
| - /* Parse the line. */ |
| - *errnop = EINVAL; |
| - parse_result = parse_line (p, result, data, buflen, errnop EXTRA_ARGS); |
| - |
| - if (parse_result == -1) |
| + else if (ret == 0) |
| { |
| - if (*errnop == ERANGE) |
| + ret = __nss_parse_line_result (stream, original_offset, |
| + parse_line (data->linebuffer, |
| + result, data, buflen, |
| + errnop EXTRA_ARGS)); |
| + if (ret == 0) |
| { |
| - /* Return to the original file position at the beginning |
| - of the line, so that the next call can read it again |
| - if necessary. */ |
| - if (__fseeko64 (stream, -r, SEEK_CUR) != 0) |
| - { |
| - if (errno == ERANGE) |
| - *errnop = EINVAL; |
| - else |
| - *errnop = errno; |
| - H_ERRNO_SET (NETDB_INTERNAL); |
| - return NSS_STATUS_UNAVAIL; |
| - } |
| + /* Line has been parsed successfully. */ |
| + __set_errno (saved_errno); |
| + return NSS_STATUS_SUCCESS; |
| } |
| - H_ERRNO_SET (NETDB_INTERNAL); |
| - return NSS_STATUS_TRYAGAIN; |
| + else if (ret == EINVAL) |
| + /* If it is invalid, loop to get the next line of the file |
| + to parse. */ |
| + continue; |
| } |
| |
| - /* Return the data if parsed successfully. */ |
| - if (parse_result != 0) |
| - return NSS_STATUS_SUCCESS; |
| - |
| - /* If it is invalid, loop to get the next line of the file to |
| - parse. */ |
| + *errnop = ret; |
| + H_ERRNO_SET (NETDB_INTERNAL); |
| + if (ret == ERANGE) |
| + /* Request larger buffer. */ |
| + return NSS_STATUS_TRYAGAIN; |
| + else |
| + /* Other read failure. */ |
| + return NSS_STATUS_UNAVAIL; |
| } |
| } |
| |
| diff -rupN a/nss/nss_parse_line_result.c b/nss/nss_parse_line_result.c |
| |
| |
| @@ -0,0 +1,46 @@ |
| +/* Implementation of __nss_parse_line_result. |
| + 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 |
| + <https://www.gnu.org/licenses/>. */ |
| + |
| +#include <nss_files.h> |
| + |
| +#include <assert.h> |
| +#include <errno.h> |
| + |
| +int |
| +__nss_parse_line_result (FILE *fp, off64_t offset, int parse_line_result) |
| +{ |
| + assert (parse_line_result >= -1 && parse_line_result <= 1); |
| + |
| + switch (__builtin_expect (parse_line_result, 1)) |
| + { |
| + case 1: |
| + /* Sucess. */ |
| + return 0; |
| + case 0: |
| + /* Parse error. */ |
| + __set_errno (EINVAL); |
| + return EINVAL; |
| + case -1: |
| + /* Out of buffer space. */ |
| + return __nss_readline_seek (fp, offset); |
| + |
| + default: |
| + __builtin_unreachable (); |
| + } |
| +} |
| +libc_hidden_def (__nss_parse_line_result) |
| diff -rupN a/nss/nss_readline.c b/nss/nss_readline.c |
| |
| |
| @@ -0,0 +1,99 @@ |
| +/* Read a line from an nss_files database file. |
| + 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 |
| + <https://www.gnu.org/licenses/>. */ |
| + |
| +#include <nss_files.h> |
| + |
| +#include <ctype.h> |
| +#include <errno.h> |
| +#include <string.h> |
| + |
| +int |
| +__nss_readline (FILE *fp, char *buf, size_t len, off64_t *poffset) |
| +{ |
| + /* We need space for at least one character, the line terminator, |
| + and the NUL byte. */ |
| + if (len < 3) |
| + { |
| + *poffset = -1; |
| + __set_errno (ERANGE); |
| + return ERANGE; |
| + } |
| + |
| + while (true) |
| + { |
| + /* Keep original offset for retries. */ |
| + *poffset = __ftello64 (fp); |
| + |
| + buf[len - 1] = '\xff'; /* Marker to recognize truncation. */ |
| + if (fgets_unlocked (buf, len, fp) == NULL) |
| + { |
| + if (feof_unlocked (fp)) |
| + { |
| + __set_errno (ENOENT); |
| + return ENOENT; |
| + } |
| + else |
| + { |
| + /* Any other error. Do not return ERANGE in this case |
| + because the caller would retry. */ |
| + if (errno == ERANGE) |
| + __set_errno (EINVAL); |
| + return errno; |
| + } |
| + } |
| + else if (buf[len - 1] != '\xff') |
| + /* The buffer is too small. Arrange for re-reading the same |
| + line on the next call. */ |
| + return __nss_readline_seek (fp, *poffset); |
| + |
| + /* fgets_unlocked succeeded. */ |
| + |
| + /* Remove leading whitespace. */ |
| + char *p = buf; |
| + while (isspace (*p)) |
| + ++p; |
| + if (*p == '\0' || *p == '#') |
| + /* Skip empty lines and comments. */ |
| + continue; |
| + if (p != buf) |
| + memmove (buf, p, strlen (p)); |
| + |
| + /* Return line to the caller. */ |
| + return 0; |
| + } |
| +} |
| +libc_hidden_def (__nss_readline) |
| + |
| +int |
| +__nss_readline_seek (FILE *fp, off64_t offset) |
| +{ |
| + if (offset < 0 /* __ftello64 failed. */ |
| + || __fseeko64 (fp, offset, SEEK_SET) < 0) |
| + { |
| + /* Without seeking support, it is not possible to |
| + re-read the same line, so this is a hard failure. */ |
| + fseterr_unlocked (fp); |
| + __set_errno (ESPIPE); |
| + return ESPIPE; |
| + } |
| + else |
| + { |
| + __set_errno (ERANGE); |
| + return ERANGE; |
| + } |
| +} |