nuttx-apps/system/popen/popen.c
Nightt fa2dd70386 system/popen: Avoid copying FILE
Use fopencookie() to attach the popen fd and shell pid to the returned FILE stream instead of copying FILE into the popen container.

Keep the upstream dpopen()/dpclose() implementation as the process and descriptor backend, and make pclose() close the cookie-backed stream directly.

Fixes #2937.

Signed-off-by: Nightt <87569709+nightt5879@users.noreply.github.com>
2026-06-09 19:35:15 +08:00

261 lines
8.2 KiB
C

/****************************************************************************
* apps/system/popen/popen.c
*
* SPDX-License-Identifier: Apache-2.0
*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership. The
* ASF licenses this file to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance with the
* License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*
****************************************************************************/
/****************************************************************************
* Included Files
****************************************************************************/
#include <nuttx/config.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <fcntl.h>
#include <errno.h>
/****************************************************************************
* Private Types
****************************************************************************/
struct popen_file_s
{
int fd;
pid_t shell;
};
/****************************************************************************
* Private Functions
****************************************************************************/
/****************************************************************************
* Name: popen_file_read
****************************************************************************/
static ssize_t popen_file_read(FAR void *cookie, FAR char *buf,
size_t size)
{
FAR struct popen_file_s *filep = (FAR struct popen_file_s *)cookie;
return read(filep->fd, buf, size);
}
/****************************************************************************
* Name: popen_file_write
****************************************************************************/
static ssize_t popen_file_write(FAR void *cookie, FAR const char *buf,
size_t size)
{
FAR struct popen_file_s *filep = (FAR struct popen_file_s *)cookie;
return write(filep->fd, buf, size);
}
/****************************************************************************
* Name: popen_file_seek
****************************************************************************/
static off_t popen_file_seek(FAR void *cookie, FAR off_t *offset,
int whence)
{
set_errno(ESPIPE);
return ERROR;
}
/****************************************************************************
* Name: popen_file_close
****************************************************************************/
static int popen_file_close(FAR void *cookie)
{
FAR struct popen_file_s *filep = (FAR struct popen_file_s *)cookie;
int ret;
ret = dpclose(filep->fd, filep->shell);
free(filep);
return ret < 0 ? ERROR : OK;
}
/****************************************************************************
* Public Functions
****************************************************************************/
/****************************************************************************
* Name: popen
*
* Description:
* The popen() function will execute the command specified by the string
* command. It will create a pipe between the calling program and the
* executed command, and will return a pointer to a stream that can be
* used to either read from or write to the pipe.
*
* This is a thin wrapper around dpopen() that wraps the returned file
* descriptor in a FILE stream, analogous to how fprintf() relates to
* dprintf().
*
* The mode argument to popen() is a string that specifies I/O mode:
*
* - If mode is r, when the child process is started, its file descriptor
* STDOUT_FILENO will be the writable end of the pipe, and the file
* descriptor fileno(stream) in the calling process, where stream is
* the stream pointer returned by popen(), will be the readable end of
* the pipe.
*
* - If mode is w, when the child process is started its file descriptor
* STDIN_FILENO will be the readable end of the pipe, and the file
* descriptor fileno(stream) in the calling process, where stream is
* the stream pointer returned by popen(), will be the writable end of
* the pipe.
*
* Input Parameters:
* command - The command string to execute
* mode - "r" or "w"
*
* Returned Value:
* A non-NULL FILE stream connected to the child process is returned on
* success. NULL is returned on any failure with the errno variable set
* appropriately.
*
****************************************************************************/
FILE *popen(FAR const char *command, FAR const char *mode)
{
FAR struct popen_file_s *container;
FAR FILE *stream;
cookie_io_functions_t popen_io =
{
.read = popen_file_read,
.write = popen_file_write,
.seek = popen_file_seek,
.close = popen_file_close
};
int oflag;
int fd;
/* Allocate a container for returned FILE stream */
container = (FAR struct popen_file_s *)malloc(sizeof(struct popen_file_s));
if (container == NULL)
{
errno = ENOMEM;
return NULL;
}
/* Map mode string to open flags */
if (strstr(mode, "r+") || strstr(mode, "w+"))
{
oflag = O_RDWR;
}
else if (strstr(mode, "r"))
{
oflag = O_RDONLY;
}
else if (strstr(mode, "w"))
{
oflag = O_WRONLY;
}
else
{
free(container);
errno = EINVAL;
return NULL;
}
if (strchr(mode, 'e') != NULL)
{
oflag |= O_CLOEXEC;
}
/* Use dpopen() to do the real work */
fd = dpopen(command, oflag, &container->shell);
if (fd < 0)
{
free(container);
return NULL;
}
/* Wrap the raw fd in a FILE stream */
container->fd = fd;
stream = fopencookie(container, mode, popen_io);
if (stream == NULL)
{
int errcode = errno;
dpclose(fd, container->shell);
free(container);
errno = errcode;
return NULL;
}
return stream;
}
/****************************************************************************
* Name: pclose
*
* Description:
* The pclose() function will close a stream that was opened by popen(),
* wait for the command to terminate, and return the termination status of
* the process that was running the command language interpreter. However,
* if a call caused the termination status to be unavailable to pclose(),
* then pclose() will return -1 with errno set to ECHILD to report this
* situation. This can happen if the application calls one of the following
* functions:
*
* wait()
* waitpid() with a pid argument less than or equal to 0 or equal to the
* process ID of the command line interpreter
*
* Any other function not defined in this volume of IEEE Std 1003.1-2001
* that could do one of the above
*
* In any case, pclose() will not return before the child process created
* by popen() has terminated.
*
* If the command language interpreter cannot be executed, the child
* termination status returned by pclose() will be as if the command
* language interpreter terminated using exit(127) or _exit(127).
*
* The pclose() function will not affect the termination status of any
* child of the calling process other than the one created by popen() for
* the associated stream.
*
* If the argument stream to pclose() is not a pointer to a stream created
* by popen(), the result of pclose() is undefined.
*
* Input Parameters:
* stream - The stream reference returned by a previous call to popen()
*
* Returned Value:
* Zero (OK) is returned on success; otherwise -1 (ERROR) is returned and
* errno is set appropriately.
*
****************************************************************************/
int pclose(FILE *stream)
{
return fclose(stream);
}