<commit_before>/*
 * select replacement for kLIBC.
 * Copyright (C) 2016 bww bitwise works GmbH.
 * This file is part of the kLIBC Extension Library.
 * Authored by <NAME> <<EMAIL>>, 2016.
 *
 * The kLIBC Extension 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 kLIBC Extension 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
 * <http://www.gnu.org/licenses/>.
 */

#include <errno.h>
#include <string.h>
#include <sys/select.h>
#include <sys/stat.h>

#define TRACE_GROUP TRACE_GROUP_SELECT
#include "../shared.h"

#ifndef MAX
#define MAX(a,b)  ((a) > (b) ? (a) : (b))
#endif

int _std_select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds,
                struct timeval *timeout);

int select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds,
           struct timeval *timeout)
{
  TRACE("nfds %d, readfds %p, writefds %p, exceptfds %p timeout %p (%ld.%ld)\n",
        nfds, readfds, writefds, exceptfds, timeout,
        timeout ? timeout->tv_sec : 0, timeout ? timeout->tv_usec : 0);

  /*
   * Validate arguments. Note that although _std_select does that too, we
   * need to do it upfront since we use ndfs on our own.
   */
  if (nfds < 0 || nfds > FD_SETSIZE)
  {
    errno = EINVAL;
    return -1;
  }

  int fd;
  int n_ready_fds;
  int nfds_ret;
  struct stat st;

  fd_set regular_fds;

  fd_set r_new;
  fd_set w_new;
  fd_set e_new;
  int max_fd;

  FD_ZERO (&regular_fds);

  FD_ZERO (&r_new);
  FD_ZERO (&w_new);
  FD_ZERO (&e_new);

  if (readfds)
    r_new = *readfds;
  if (writefds)
    w_new = *writefds;
  if (exceptfds)
    e_new = *exceptfds;

  n_ready_fds = 0;
  max_fd = -1;

  for (fd = 0; fd < nfds; ++fd)
  {
    int n_fd_set = 0;

    if (readfds && FD_ISSET(fd, readfds))
      ++n_fd_set;
    if (writefds && FD_ISSET(fd, writefds))
      ++n_fd_set;
    if (exceptfds && FD_ISSET(fd, exceptfds))
      ++n_fd_set;

    if (n_fd_set)
    {
      if (fstat (fd, &st) != -1 && S_ISREG (st.st_mode))
      {
        /*
         * Regular files should be always immediately ready for I/O,
         * remove this fd from the new set, no need to select.
         */
        TRACE("fd %d is regular file\n", fd);
        FD_CLR(fd, &r_new);
        FD_CLR(fd, &w_new);
        FD_CLR(fd, &e_new);
        n_ready_fds += n_fd_set;
        /* Remember regular fd for later */
        FD_SET(fd, &regular_fds);
      }
      else
      {
        /*
         * Note that while it seems that we could reset this non-regular fd in
         * the original sets since we copied it to the new sets (in order to
         * simply set it back if it's ready after the actual select), this can't
         * be done — _std_select may fail and many apps expect that the sets
         * remain unchanged in such a case (e.g. in case of EINTR many apps will
         * simply call select again with the same arguments and if we reset some
         * fds here these apps may not get needed notifications). Instead, we
         * remember regular fds in regular_fds above and then reset not ready
         * non-regular ones after _std_select returns success.<commit_msg>select: Avoid expensive I/O on regular files.<commit_after>/*
 * select replacement for kLIBC.
 * Copyright (C) 2016 bww bitwise works GmbH.
 * This file is part of the kLIBC Extension Library.
 * Authored by <NAME> <<EMAIL>>, 2016.
 *
 * The kLIBC Extension 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 kLIBC Extension 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
 * <http://www.gnu.org/licenses/>.
 */

#include <errno.h>
#include <string.h>
#include <sys/select.h>
#include <sys/stat.h>
#include <emx/io.h>

#define TRACE_GROUP TRACE_GROUP_SELECT
#include "../shared.h"

#ifndef MAX
#define MAX(a,b)  ((a) > (b) ? (a) : (b))
#endif

int _std_select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds,
                struct timeval *timeout);

int select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds,
           struct timeval *timeout)
{
  TRACE("nfds %d, readfds %p, writefds %p, exceptfds %p timeout %p (%ld.%ld)\n",
        nfds, readfds, writefds, exceptfds, timeout,
        timeout ? timeout->tv_sec : 0, timeout ? timeout->tv_usec : 0);

  /*
   * Validate arguments. Note that although _std_select does that too, we
   * need to do it upfront since we use ndfs on our own.
   */
  if (nfds < 0 || nfds > FD_SETSIZE)
  {
    errno = EINVAL;
    return -1;
  }

  int fd;
  int n_ready_fds;
  int nfds_ret;

  fd_set regular_fds;

  fd_set r_new;
  fd_set w_new;
  fd_set e_new;
  int max_fd;

  FD_ZERO (&regular_fds);

  FD_ZERO (&r_new);
  FD_ZERO (&w_new);
  FD_ZERO (&e_new);

  if (readfds)
    r_new = *readfds;
  if (writefds)
    w_new = *writefds;
  if (exceptfds)
    e_new = *exceptfds;

  n_ready_fds = 0;
  max_fd = -1;

  for (fd = 0; fd < nfds; ++fd)
  {
    int n_fd_set = 0;

    if (readfds && FD_ISSET(fd, readfds))
      ++n_fd_set;
    if (writefds && FD_ISSET(fd, writefds))
      ++n_fd_set;
    if (exceptfds && FD_ISSET(fd, exceptfds))
      ++n_fd_set;

    if (n_fd_set)
    {
      /*
       * fstat is too expensive to detect if it's a regular file (as it does an
       * expensive I/O operation to query file attributes), use LIBC internals
       * instead as we don't need those attributes.
       */
      int is_regular = 0;
      __LIBC_PFH pFH;
      pFH = __libc_FH(fd);
      if (pFH)
      {
        if ((pFH->fFlags & __LIBC_FH_TYPEMASK) == F_FILE)
        {
           is_regular = 1;
        }
        else if ((pFH->fFlags & __LIBC_FH_TYPEMASK) != F_SOCKET)
        {
          /*
           * TODO: Judging by __libc_Back_fsFileStatFH (fstat work horse), the
           * handle type may be not set, which will cause a call to
           * __libc_back_fsNativeFileStat if pFH->pszNativePath is not null.
           * Let it go in such a case - fd might still be a regular file.
           */
          TRACE("fd %d type %d, path [%s]\n", fd, (pFH->fFlags & __LIBC_FH_TYPEMASK), pFH->pszNativePath);
          struct stat st;
          if (fstat (fd, &st) != -1 && S_ISREG (st.st_mode))
            is_regular = 1;
        }
      }

      if (is_regular)
      {
        /*
         * Regular files should be always immediately ready for I/O,
         * remove this fd from the new set, no need to select.
         */
        TRACE("fd %d is regular file\n", fd);
        FD_CLR(fd, &r_new);
        FD_CLR(fd, &w_new);
        FD_CLR(fd, &e_new);
        n_ready_fds += n_fd_set;
        /* Remember regular fd for later */
        FD_SET(fd, &regular_fds);
      }
      else
      {
        /*
         * Note that while it seems that we could reset this non-regular fd in
         * the original sets since we copied it to the new sets (in order to
         * simply set it back if it's ready after the actual select), this can't
         * be done — _std_select may fail and many apps expect that the sets
         * remain unchanged in such a case (e.g. in case of EINTR many apps will
         * simply call select again with the same arguments and if we reset some
         * fds here these apps may not get needed notifications). Instead, we
         * remember regular fds in regular_fds above and then reset not ready
         * non-regular ones after _std_select returns success.
{'fraction_non_alphanumeric': 0.09838613269575612, 'fraction_numerical': 0.004901374775851763, 'mean_word_length': 2.5062866722548196, 'pattern_counts': {'":': 0, '<': 24, '<?xml version=': 0, '>': 33, 'https://': 0, 'lorem ipsum': 0, 'www.': 2, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '1833461', 'n_tokens_mistral': 2914, 'n_tokens_neox': 2804, 'n_words': 1031}