static int ftpd_stream()

in netutils/ftpd/ftpd.c [1765:2085]


static int ftpd_stream(FAR struct ftpd_session_s *session, int cmdtype)
{
  FAR char *abspath;
  FAR char *path;
  bool isnew;
  int oflags;
  FAR char *buffer;
  size_t buflen;
  size_t wantsize;
  ssize_t rdbytes;
  ssize_t wrbytes;
  int errval = 0;
  int ret;

  ret = ftpd_getpath(session, session->param, &abspath, NULL);
  if (ret < 0)
    {
      ftpd_response(session->cmd.sd, session->txtimeout,
                    g_respfmt1, 550, ' ', "Stream error !");
      goto errout;
    }

  path = abspath;

  ret = ftpd_dataopen(session);
  if (ret < 0)
    {
      goto errout_with_path;
    }

  switch (cmdtype)
    {
      case 0: /* retr */
        oflags = O_RDONLY;
        break;

      case 1: /* stor */
        oflags = O_CREAT | O_WRONLY;
         break;

      case 2: /* appe */
        oflags = O_CREAT | O_WRONLY | O_APPEND;
        break;

      default:
        oflags = O_RDONLY;
        break;
    }

#if defined(O_LARGEFILE)
  oflags |= O_LARGEFILE;
#endif

  /* Are we creating the file? */

  if ((oflags & O_CREAT) != 0)
    {
      int mode = S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH;

      if (session->restartpos <= 0)
        {
          oflags |= O_TRUNC;
        }

      isnew = true;
      session->fd = open(path, oflags | O_EXCL, mode);
      if (session->fd < 0)
        {
          isnew = false;
          session->fd = open(path, oflags, mode);
        }
    }
  else
    {
      /* No.. we are opening an existing file */

      isnew = false;
      session->fd = open(path, oflags);
    }

  if (session->fd < 0)
    {
      ret = -errno;
      ftpd_response(session->cmd.sd, session->txtimeout,
                    g_respfmt1, 550, ' ', "Can not open file !");
      goto errout_with_data;
    }

  /* Restart position */

  if (session->restartpos > 0)
    {
      off_t seekoffs = (off_t)-1;
      off_t seekpos;

      /* Get the seek position */

      if (session->type == FTPD_SESSIONTYPE_A)
        {
          seekpos = ftpd_offsatoi(path, session->restartpos);
          if (seekpos < 0)
            {
              nerr("ERROR: ftpd_offsatoi failed: %jd\n", (intmax_t)seekpos);
              errval = -seekpos;
            }
        }
      else
        {
          seekpos = session->restartpos;
          if (seekpos < 0)
            {
              nerr("ERROR: Bad restartpos: %jd\n", (intmax_t)seekpos);
              errval = EINVAL;
            }
        }

      /* Seek to the request position */

      if (seekpos >= 0)
        {
          seekoffs = lseek(session->fd, seekpos, SEEK_SET);
          if (seekoffs < 0)
            {
              errval = errno;
              nerr("ERROR: lseek failed: %d\n", errval);
            }
        }

      /* Report errors.  If an error occurred, seekoffs will be negative and
       * errval will hold the (positive) error code.
       */

      if (seekoffs < 0)
        {
          ftpd_response(session->cmd.sd, session->txtimeout,
                        g_respfmt1, 550, ' ', "Can not seek file !");
          ret = -errval;
          goto errout_with_session;
        }
    }

  /* Send success message */

  ret = ftpd_response(session->cmd.sd, session->txtimeout,
                      g_respfmt1, 150, ' ', "Opening data connection");
  if (ret < 0)
    {
      nerr("ERROR: ftpd_response failed: %d\n", ret);
      goto errout_with_session;
    }

  for (; ; )
    {
      /* Read from the source (file or TCP connection) */

      if (session->type == FTPD_SESSIONTYPE_A)
        {
          buffer   = &session->data.buffer[session->data.buflen >> 2];
          wantsize = session->data.buflen >> 2;
        }
      else
        {
          buffer   = session->data.buffer;
          wantsize = session->data.buflen;
        }

      if (cmdtype == 0)
        {
          /* Read from the file. */

          rdbytes = read(session->fd, session->data.buffer, wantsize);
          if (rdbytes < 0)
            {
              errval = errno;
            }
        }
      else
        {
          /* Read from the TCP connection, ftpd_recve returns the negated
           * error condition.
           */

          rdbytes = ftpd_recv(session->data.sd, session->data.buffer,
                              wantsize, session->rxtimeout);
          if (rdbytes < 0)
            {
              errval = -rdbytes;
            }
        }

      /* A negative value of rdbytes indicates a read error.  errval has the
       * (positive) error code associated with the failure.
       */

      if (rdbytes < 0)
        {
          nerr("ERROR: Read failed: rdbytes=%zu errval=%d\n",
               rdbytes, errval);
          ftpd_response(session->cmd.sd, session->txtimeout,
                        g_respfmt1, 550, ' ', "Data read error !");
          ret = -errval;
          break;
        }

      /* A value of rdbytes == 0 means that we have read the entire source
       * stream.
       */

      if (rdbytes == 0)
        {
          /* End-of-file */

          ftpd_response(session->cmd.sd, session->txtimeout,
                        g_respfmt1, 226, ' ', "Transfer complete");

          /* Return success */

          ret = 0;
          break;
        }

      /* Write to the destination (file or TCP connection) */

      if (session->type == FTPD_SESSIONTYPE_A)
        {
          /* Change to ascii */

          size_t offset = 0;
          buflen = 0;
          while (offset < ((size_t)rdbytes))
            {
              if (session->data.buffer[offset] == '\n')
                {
                  buffer[buflen++] = '\r';
                }

              buffer[buflen++] = session->data.buffer[offset++];
            }
        }
      else
        {
          buffer = session->data.buffer;
          buflen = (size_t)rdbytes;
        }

      if (cmdtype == 0)
        {
          /* Write to the TCP connection */

          wrbytes = ftpd_send(session->data.sd, buffer, buflen,
                              session->txtimeout);
          if (wrbytes < 0)
            {
              errval = -wrbytes;
              nerr("ERROR: ftpd_send failed: %d\n", errval);
            }
        }
      else
        {
          int remaining;
          int nwritten;
          FAR char *next;

          remaining = buflen;
          next = buffer;

          /* Write to the file */

          do
            {
              nwritten = write(session->fd, next, remaining);
              if (nwritten < 0)
                {
                  errval = errno;
                  nerr("ERROR: write() failed: %d\n", errval);
                  break;
                }

              remaining -= nwritten;
              next += nwritten;
            }
          while (remaining > 0);

          wrbytes = next - buffer;
        }

      /* If the number of bytes returned by the write is not equal to the
       * number that we wanted to write, then an error (or at least an
       * unhandled condition) has occurred.  errval should should hold
       * the (positive) error code.
       */

      if (wrbytes != ((ssize_t)buflen))
        {
          nerr("ERROR: Write failed: wrbytes=%zu errval=%d\n",
               wrbytes, errval);
          ftpd_response(session->cmd.sd, session->txtimeout,
                        g_respfmt1, 550, ' ', "Data send error !");
          ret = -errval;
          break;
        }
    }

errout_with_session:;
    close(session->fd);
    session->fd = -1;

    if (isnew && ret < 0)
      {
        unlink(path);
      }

errout_with_data:;
    ftpd_dataclose(session);

errout_with_path:
    free(abspath);

errout:
    return ret;
}