Skip to content

system/popen: Avoid copying FILE#3511

Open
nightt5879 wants to merge 2 commits into
apache:masterfrom
nightt5879:nightt5879/fix-popen-file-copy-2937
Open

system/popen: Avoid copying FILE#3511
nightt5879 wants to merge 2 commits into
apache:masterfrom
nightt5879:nightt5879/fix-popen-file-copy-2937

Conversation

@nightt5879
Copy link
Copy Markdown
Contributor

Summary

Fixes #2937.

system/popen was embedding a copied FILE object in its private tracking container and copying it back in pclose(). That depends on libc FILE layout details.

This PR makes popen() return the actual stream from fdopen() and keeps a small private FILE * -> pid tracking list so pclose() can find the spawned shell process without copying FILE.

Scope:

  • Does not change supported popen() modes.
  • Does not change the spawn/file-action setup.
  • Keeps pclose() responsible for closing the stream and waiting for the shell pid.
  • Returns EINVAL if pclose() cannot find the stream in the private popen list.

Impact

popen()/pclose() no longer rely on libc FILE internals.

  • New feature: NO
  • User adaptation required: NO
  • Build process change: NO
  • Hardware/architecture/board change: NO
  • Documentation update required: NO
  • Security impact: NO
  • Compatibility impact: NO intended compatibility impact

Testing

Host:

  • Windows with WSL Ubuntu 24.04
  • Target: sim:nsh

Checks:

  • git diff --check: pass
  • checkpatch.sh -g HEAD~1..HEAD: pass
  • sim:nsh build with CONFIG_SYSTEM_POPEN=y and CONFIG_EXAMPLES_POPEN=y: pass
    • confirmed CC: popen.c
    • result: SIM elf with dynamic libs archive in nuttx.tgz
  • printf 'popen\npoweroff\n' | timeout 30s ./nuttx: pass
    • confirmed popen("help") output is read
    • confirmed execution reaches pclose()

Track popen streams in a small private list so pclose() can find the spawned shell pid without embedding a copied FILE object in the tracking container.

This keeps the returned stream owned by fdopen()/fclose() and avoids depending on libc FILE layout details.

Fixes apache#2937.

Signed-off-by: Nightt <87569709+nightt5879@users.noreply.github.com>
@nightt5879 nightt5879 marked this pull request as ready for review May 29, 2026 07:34
acassis
acassis previously approved these changes May 29, 2026
@acassis
Copy link
Copy Markdown
Contributor

acassis commented May 29, 2026

@nightt5879 it would be nice to have some test coverage to test it too.

Add a small popen test that opens two read streams, verifies their output, and closes the first stream before the second.

This covers reading from the returned popen stream and exercises pclose lookup/removal when the stream is not the first entry in the tracking list.

Signed-off-by: Nightt <87569709+nightt5879@users.noreply.github.com>
@nightt5879
Copy link
Copy Markdown
Contributor Author

nightt5879 commented May 29, 2026

Thanks, I added a small testing/libc/popen test in commit 23a698f. @acassis

The test opens two popen(..., "r") streams, verifies the output read from both streams, and closes the first stream before the second. That covers reading from the returned popen() stream and also exercises the private stream-to-pid lookup/removal path added by this PR when the stream is not the first entry in the tracking list.

I verified the added test with:

  • checkpatch.sh -g HEAD~1..HEAD
  • sim:nsh build with CONFIG_SYSTEM_POPEN=y and CONFIG_TESTING_POPEN_TEST=y
  • printf 'popen_test\npoweroff\n' | timeout 30s ./nuttx

The simulator run prints popen_test: successful.

@acassis
Copy link
Copy Markdown
Contributor

acassis commented May 29, 2026

Thanks, I added a small testing/libc/popen test in commit 23a698f. @acassis

The test opens two popen(..., "r") streams, verifies the output read from both streams, and closes the first stream before the second. That covers reading from the returned popen() stream and also exercises the private stream-to-pid lookup/removal path added by this PR when the stream is not the first entry in the tracking list.

I verified the added test with:

* `checkpatch.sh -g HEAD~1..HEAD`

* `sim:nsh` build with `CONFIG_SYSTEM_POPEN=y` and `CONFIG_TESTING_POPEN_TEST=y`

* `printf 'popen_test\npoweroff\n' | timeout 30s ./nuttx`

The simulator run prints popen_test: successful.

My bad, I looked only the first commit. So, everything is fine!

@nightt5879
Copy link
Copy Markdown
Contributor Author

Thanks, I added a small testing/libc/popen test in commit 23a698f. @acassis
The test opens two popen(..., "r") streams, verifies the output read from both streams, and closes the first stream before the second. That covers reading from the returned popen() stream and also exercises the private stream-to-pid lookup/removal path added by this PR when the stream is not the first entry in the tracking list.
I verified the added test with:

* `checkpatch.sh -g HEAD~1..HEAD`

* `sim:nsh` build with `CONFIG_SYSTEM_POPEN=y` and `CONFIG_TESTING_POPEN_TEST=y`

* `printf 'popen_test\npoweroff\n' | timeout 30s ./nuttx`

The simulator run prints popen_test: successful.

My bad, I looked only the first commit. So, everything is fine!

Just to clarify: I added the additional commit after seeing your suggestion.
Thanks for reviewing it again!

Comment thread system/popen/popen.c
****************************************************************************/

static sem_t g_popen_sem = SEM_INITIALIZER(1);
static FAR struct popen_file_s *g_popen_files;
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

but it isn't good (or even worse) to add a global list to fix a minor issue.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[BUG] system/popen memcpy FILE

3 participants