commit bdee910e88006ae33dc83ac3d2c0708adb6627d0 Author: Florian Weimer 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 Reviewed-by: Carlos O'Donell diff -rupN a/include/nss_files.h b/include/nss_files.h --- a/include/nss_files.h 2020-09-14 17:48:49.353699306 -0400 +++ b/include/nss_files.h 2020-09-14 17:55:21.856488740 -0400 @@ -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 --- a/nss/Makefile 2020-09-14 17:48:49.293697045 -0400 +++ b/nss/Makefile 2020-09-14 17:55:21.860488891 -0400 @@ -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 --- a/nss/Versions 2020-09-14 17:48:49.294697083 -0400 +++ b/nss/Versions 2020-09-14 17:55:21.867489155 -0400 @@ -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 --- a/nss/nss_fgetent_r.c 1969-12-31 19:00:00.000000000 -0500 +++ b/nss/nss_fgetent_r.c 2020-09-14 17:55:21.870489268 -0400 @@ -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 + . */ + +#include +#include + +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 --- a/nss/nss_files/files-XXX.c 2020-09-14 17:48:49.296697158 -0400 +++ b/nss/nss_files/files-XXX.c 2020-09-14 17:55:21.878489569 -0400 @@ -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 --- a/nss/nss_parse_line_result.c 1969-12-31 19:00:00.000000000 -0500 +++ b/nss/nss_parse_line_result.c 2020-09-14 17:55:21.880489645 -0400 @@ -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 + . */ + +#include + +#include +#include + +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 --- a/nss/nss_readline.c 1969-12-31 19:00:00.000000000 -0500 +++ b/nss/nss_readline.c 2020-09-14 17:55:21.883489758 -0400 @@ -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 + . */ + +#include + +#include +#include +#include + +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; + } +}