Skip to content

Commit 0d35e2e

Browse files
cuiziweizwxiaoxiang781216
authored andcommitted
system/popen: add dpopen/dpclose fd-based interface
Add dpopen()/dpclose() as the descriptor-based counterpart of popen()/pclose(), analogous to how dprintf() relates to fprintf(). dpopen() returns a raw file descriptor instead of a FILE stream, avoiding the stdio.h dependency for callers that only need an fd. Refactor popen() as a thin wrapper: dpopen() + fdopen() + FILE container. All pipe creation and process spawning logic now lives in dpopen.c. Also remove the hard dependency on NSH_LIBRARY from SYSTEM_POPEN. When NSH is available, commands are executed through sh -c with full shell syntax support. When NSH is not available, commands are split by whitespace and executed directly via posix_spawnp(). Add CONFIG_SYSTEM_POPEN_MAXARGUMENTS (default 7) to control the argv array size for the no-shell path. Signed-off-by: cuiziwei <cuiziwei@xiaomi.com>
1 parent 3a255d5 commit 0d35e2e

5 files changed

Lines changed: 379 additions & 265 deletions

File tree

system/popen/CMakeLists.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,5 +21,5 @@
2121
# ##############################################################################
2222

2323
if(CONFIG_SYSTEM_POPEN)
24-
target_sources(apps PRIVATE popen.c)
24+
target_sources(apps PRIVATE dpopen.c popen.c)
2525
endif()

system/popen/Kconfig

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,14 +4,25 @@
44
#
55

66
config SYSTEM_POPEN
7-
bool "popen()/pclose() Functions"
7+
bool "popen()/pclose()/dpopen()/dpclose() Functions"
88
default n
99
select SCHED_WAITPID
1010
depends on NSH_LIBRARY && PIPES
1111
---help---
12-
Enable support for the popen() and pclose() interfaces.
13-
This will support execution of NSH commands from C code with
14-
pipe communications with the shell.
12+
Enable support for the popen(), pclose(), dpopen(), and
13+
dpclose() interfaces.
14+
15+
dpopen()/dpclose() are the descriptor-based counterparts
16+
of popen()/pclose(), analogous to how dprintf() relates
17+
to fprintf(). They return a raw file descriptor instead
18+
of a FILE stream, avoiding the stdio.h dependency.
19+
20+
popen()/pclose() are thin wrappers around dpopen()/dpclose()
21+
that additionally wrap the fd in a FILE stream.
22+
23+
Commands are executed through the NSH shell (sh -c command),
24+
supporting full shell syntax including pipes, redirects,
25+
and globbing.
1526

1627
if SYSTEM_POPEN
1728

system/popen/Makefile

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,8 +22,8 @@
2222

2323
include $(APPDIR)/Make.defs
2424

25-
# popen()/pclose functions
25+
# dpopen()/dpclose() core and popen()/pclose() FILE wrappers
2626

27-
CSRCS = popen.c
27+
CSRCS = dpopen.c popen.c
2828

2929
include $(APPDIR)/Application.mk

system/popen/dpopen.c

Lines changed: 321 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,321 @@
1+
/****************************************************************************
2+
* apps/system/popen/dpopen.c
3+
*
4+
* Licensed to the Apache Software Foundation (ASF) under one or more
5+
* contributor license agreements. See the NOTICE file distributed with
6+
* this work for additional information regarding copyright ownership. The
7+
* ASF licenses this file to you under the Apache License, Version 2.0 (the
8+
* "License"); you may not use this file except in compliance with the
9+
* License. You may obtain a copy of the License at
10+
*
11+
* http://www.apache.org/licenses/LICENSE-2.0
12+
*
13+
* Unless required by applicable law or agreed to in writing, software
14+
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
15+
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
16+
* License for the specific language governing permissions and limitations
17+
* under the License.
18+
*
19+
****************************************************************************/
20+
21+
/****************************************************************************
22+
* Included Files
23+
****************************************************************************/
24+
25+
#include <nuttx/config.h>
26+
27+
#include <sys/wait.h>
28+
#include <sys/ioctl.h>
29+
#include <unistd.h>
30+
#include <sched.h>
31+
#include <spawn.h>
32+
#include <debug.h>
33+
#include <fcntl.h>
34+
#include <errno.h>
35+
36+
#if defined(CONFIG_NET_LOCAL) && defined(CONFIG_NET_LOCAL_STREAM)
37+
# include <sys/socket.h>
38+
#endif
39+
40+
#include "nshlib/nshlib.h"
41+
42+
/****************************************************************************
43+
* Public Functions
44+
****************************************************************************/
45+
46+
/****************************************************************************
47+
* Name: dpopen
48+
*
49+
* Description:
50+
* Execute a command and return a raw file descriptor connected to the
51+
* child process via a pipe, along with the child PID.
52+
*
53+
* This is the descriptor-based counterpart of popen(), analogous to how
54+
* dprintf() relates to fprintf(). It avoids the FILE stream layer,
55+
* making it suitable for callers that only need a raw fd and want to
56+
* avoid the stdio.h dependency.
57+
*
58+
* When NSH is available, commands are executed through the shell
59+
* (sh -c command), supporting full shell syntax.
60+
*
61+
* Input Parameters:
62+
* command - The command string to execute
63+
* oflag - O_RDONLY to read child stdout, O_WRONLY to write child stdin,
64+
* O_RDWR for bidirectional socket mode
65+
* pid - Location to return the child process ID
66+
*
67+
* Returned Value:
68+
* A valid file descriptor on success, or -1 on failure with errno set.
69+
*
70+
****************************************************************************/
71+
72+
int dpopen(FAR const char *command, int oflag, FAR pid_t *pid)
73+
{
74+
struct sched_param param;
75+
posix_spawnattr_t attr;
76+
posix_spawn_file_actions_t file_actions;
77+
FAR char *argv[4];
78+
int fd[2];
79+
int childfd;
80+
int parentfd;
81+
int errcode;
82+
83+
if (command == NULL || pid == NULL)
84+
{
85+
errno = EINVAL;
86+
return -1;
87+
}
88+
89+
/* Create a pipe or socketpair. fd[0] refers to the read end of the
90+
* pipe; fd[1] refers to the write end of the pipe.
91+
*/
92+
93+
if ((oflag & O_RDWR) == O_RDWR)
94+
{
95+
#if defined(CONFIG_NET_LOCAL) && defined(CONFIG_NET_LOCAL_STREAM)
96+
/* Create a socketpair for bidirectional communication */
97+
98+
if (socketpair(AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC, 0, fd) < 0)
99+
{
100+
return -1;
101+
}
102+
103+
childfd = fd[0];
104+
parentfd = fd[1];
105+
#else
106+
errno = ENOTSUP;
107+
return -1;
108+
#endif
109+
}
110+
else if (pipe2(fd, O_CLOEXEC) < 0)
111+
{
112+
return -1;
113+
}
114+
else if ((oflag & O_ACCMODE) == O_RDONLY)
115+
{
116+
/* Pipe is the output from the child */
117+
118+
childfd = fd[1]; /* Child writes to pipe */
119+
parentfd = fd[0]; /* Parent reads from pipe */
120+
}
121+
else if ((oflag & O_ACCMODE) == O_WRONLY)
122+
{
123+
/* Pipe is the input to the child */
124+
125+
childfd = fd[0]; /* Child reads from pipe */
126+
parentfd = fd[1]; /* Parent writes to pipe */
127+
}
128+
else
129+
{
130+
errcode = EINVAL;
131+
goto errout_with_pipe;
132+
}
133+
134+
/* Initialize attributes for task_spawn() (or posix_spawn()). */
135+
136+
errcode = posix_spawnattr_init(&attr);
137+
if (errcode != 0)
138+
{
139+
goto errout_with_pipe;
140+
}
141+
142+
errcode = posix_spawn_file_actions_init(&file_actions);
143+
if (errcode != 0)
144+
{
145+
goto errout_with_attr;
146+
}
147+
148+
/* Set the correct stack size and priority */
149+
150+
param.sched_priority = CONFIG_SYSTEM_POPEN_PRIORITY;
151+
errcode = posix_spawnattr_setschedparam(&attr, &param);
152+
if (errcode != 0)
153+
{
154+
goto errout_with_actions;
155+
}
156+
157+
#ifndef CONFIG_SYSTEM_POPEN_SHPATH
158+
errcode = posix_spawnattr_setstacksize(&attr,
159+
CONFIG_SYSTEM_POPEN_STACKSIZE);
160+
if (errcode != 0)
161+
{
162+
goto errout_with_actions;
163+
}
164+
#endif
165+
166+
/* If robin robin scheduling is enabled, then set the scheduling policy
167+
* of the new task to SCHED_RR before it has a chance to run.
168+
*/
169+
170+
#if CONFIG_RR_INTERVAL > 0
171+
errcode = posix_spawnattr_setschedpolicy(&attr, SCHED_RR);
172+
if (errcode != 0)
173+
{
174+
goto errout_with_actions;
175+
}
176+
177+
errcode = posix_spawnattr_setflags(&attr,
178+
POSIX_SPAWN_SETSCHEDPARAM |
179+
POSIX_SPAWN_SETSCHEDULER);
180+
if (errcode != 0)
181+
{
182+
goto errout_with_actions;
183+
}
184+
185+
#else
186+
errcode = posix_spawnattr_setflags(&attr, POSIX_SPAWN_SETSCHEDPARAM);
187+
if (errcode != 0)
188+
{
189+
goto errout_with_actions;
190+
}
191+
192+
#endif
193+
194+
/* Redirect child's stdin or stdout to the pipe */
195+
196+
if ((oflag & O_RDWR) == O_RDWR)
197+
{
198+
errcode = posix_spawn_file_actions_adddup2(&file_actions, childfd,
199+
STDIN_FILENO);
200+
if (errcode != 0)
201+
{
202+
goto errout_with_actions;
203+
}
204+
205+
errcode = posix_spawn_file_actions_adddup2(&file_actions, childfd,
206+
STDOUT_FILENO);
207+
if (errcode != 0)
208+
{
209+
goto errout_with_actions;
210+
}
211+
}
212+
else
213+
{
214+
errcode = posix_spawn_file_actions_adddup2(&file_actions, childfd,
215+
(oflag & O_ACCMODE) == O_RDONLY ?
216+
STDOUT_FILENO : STDIN_FILENO);
217+
if (errcode != 0)
218+
{
219+
goto errout_with_actions;
220+
}
221+
}
222+
223+
/* Call task_spawn() (or posix_spawn), re-directing stdin or stdout
224+
* appropriately.
225+
*/
226+
227+
argv[1] = "-c";
228+
argv[2] = (FAR char *)command;
229+
argv[3] = NULL;
230+
231+
#ifdef CONFIG_SYSTEM_POPEN_SHPATH
232+
argv[0] = CONFIG_SYSTEM_POPEN_SHPATH;
233+
errcode = posix_spawn(pid, argv[0], &file_actions,
234+
&attr, argv, NULL);
235+
#else
236+
*pid = task_spawn("dpopen", nsh_system, &file_actions,
237+
&attr, argv + 1, NULL);
238+
if (*pid < 0)
239+
{
240+
errcode = -*pid;
241+
}
242+
#endif
243+
244+
if (errcode != 0)
245+
{
246+
serr("ERROR: dpopen spawn failed: %d\n", errcode);
247+
goto errout_with_pipe;
248+
}
249+
250+
/* Free attributes and file actions */
251+
252+
posix_spawn_file_actions_destroy(&file_actions);
253+
posix_spawnattr_destroy(&attr);
254+
255+
/* Close the child's end in the parent */
256+
257+
if (!(oflag & O_CLOEXEC))
258+
{
259+
ioctl(parentfd, FIONCLEX, 0);
260+
}
261+
262+
close(childfd);
263+
264+
return parentfd;
265+
266+
errout_with_actions:
267+
posix_spawn_file_actions_destroy(&file_actions);
268+
269+
errout_with_attr:
270+
posix_spawnattr_destroy(&attr);
271+
272+
errout_with_pipe:
273+
close(fd[0]);
274+
close(fd[1]);
275+
errno = errcode;
276+
return -1;
277+
}
278+
279+
/****************************************************************************
280+
* Name: dpclose
281+
*
282+
* Description:
283+
* Close a file descriptor opened by dpopen() and wait for the child
284+
* process to terminate.
285+
*
286+
* This is the descriptor-based counterpart of pclose().
287+
*
288+
* Input Parameters:
289+
* fd - The file descriptor returned by dpopen()
290+
* pid - The child process ID returned by dpopen()
291+
*
292+
* Returned Value:
293+
* The child termination status on success, or -1 on failure with
294+
* errno set.
295+
*
296+
****************************************************************************/
297+
298+
int dpclose(int fd, pid_t pid)
299+
{
300+
#ifdef CONFIG_SCHED_WAITPID
301+
int status;
302+
int ret;
303+
#endif
304+
305+
if (fd >= 0)
306+
{
307+
close(fd);
308+
}
309+
310+
#ifdef CONFIG_SCHED_WAITPID
311+
ret = waitpid(pid, &status, 0);
312+
if (ret < 0)
313+
{
314+
return ERROR;
315+
}
316+
317+
return status;
318+
#else
319+
return 0;
320+
#endif
321+
}

0 commit comments

Comments
 (0)