Message ID | 7448b77dd7a68200489ac39e8c643ca8db04a3f2.1594974444.git.fweimer@redhat.com |
---|---|
State | Committed |
Headers | show |
Series | Fix fgetsgent_r data corruption bug (20338) | expand |
On 7/17/20 4:30 AM, Florian Weimer via Libc-alpha wrote: > 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. OK for 2.32. Very clean! Tested-by: Carlos O'Donell <carlos@redhat.com> Reviewed-by: Carlos O'Donell <carlos@redhat.com> > --- > include/nss_files.h | 29 +++++++++++ > nss/Makefile | 4 +- > nss/Versions | 2 +- > nss/nss_fgetent_r.c | 55 +++++++++++++++++++++ > nss/nss_files/files-XXX.c | 79 ++++++++++------------------- > nss/nss_parse_line_result.c | 46 +++++++++++++++++ > nss/nss_readline.c | 99 +++++++++++++++++++++++++++++++++++++ > 7 files changed, 260 insertions(+), 54 deletions(-) > create mode 100644 nss/nss_fgetent_r.c > create mode 100644 nss/nss_parse_line_result.c > create mode 100644 nss/nss_readline.c > > diff --git a/include/nss_files.h b/include/nss_files.h > index d0f26815b5..f45ea02dc0 100644 > --- 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_parse_servent) > 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 --git a/nss/Makefile b/nss/Makefile > index 00f4d89310..20c412c3e1 100644 > --- 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 --git a/nss/Versions b/nss/Versions > index f489cb6eb0..71703750bf 100644 > --- a/nss/Versions > +++ b/nss/Versions > @@ -18,7 +18,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 --git a/nss/nss_fgetent_r.c b/nss/nss_fgetent_r.c > new file mode 100644 > index 0000000000..8f7c5b5cc7 > --- /dev/null > +++ 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 --git a/nss/nss_files/files-XXX.c b/nss/nss_files/files-XXX.c > index 9cc5137953..1db9e46127 100644 > --- a/nss/nss_files/files-XXX.c > +++ b/nss/nss_files/files-XXX.c > @@ -135,10 +135,9 @@ internal_getent (FILE *stream, struct STRUCTURE *result, > 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 STRUCTURE *result, > > 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); OK. > + 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)); OK. > + 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 --git a/nss/nss_parse_line_result.c b/nss/nss_parse_line_result.c > new file mode 100644 > index 0000000000..cd008e3c36 > --- /dev/null > +++ 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 --git a/nss/nss_readline.c b/nss/nss_readline.c > new file mode 100644 > index 0000000000..44e0dd9319 > --- /dev/null > +++ 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) > +{ OK. Like this clean implementation. > + /* 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. */ OK. > + fseterr_unlocked (fp); > + __set_errno (ESPIPE); > + return ESPIPE; > + } > + else > + { > + __set_errno (ERANGE); > + return ERANGE; > + } > +} >
diff --git a/include/nss_files.h b/include/nss_files.h index d0f26815b5..f45ea02dc0 100644 --- 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_parse_servent) 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 --git a/nss/Makefile b/nss/Makefile index 00f4d89310..20c412c3e1 100644 --- 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 --git a/nss/Versions b/nss/Versions index f489cb6eb0..71703750bf 100644 --- a/nss/Versions +++ b/nss/Versions @@ -18,7 +18,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 --git a/nss/nss_fgetent_r.c b/nss/nss_fgetent_r.c new file mode 100644 index 0000000000..8f7c5b5cc7 --- /dev/null +++ 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 --git a/nss/nss_files/files-XXX.c b/nss/nss_files/files-XXX.c index 9cc5137953..1db9e46127 100644 --- a/nss/nss_files/files-XXX.c +++ b/nss/nss_files/files-XXX.c @@ -135,10 +135,9 @@ internal_getent (FILE *stream, struct STRUCTURE *result, 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 STRUCTURE *result, 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 --git a/nss/nss_parse_line_result.c b/nss/nss_parse_line_result.c new file mode 100644 index 0000000000..cd008e3c36 --- /dev/null +++ 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 --git a/nss/nss_readline.c b/nss/nss_readline.c new file mode 100644 index 0000000000..44e0dd9319 --- /dev/null +++ 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; + } +}