Home / Alt manpages / perlapio(1)

  • perlapio(1)
  • User command
  • linux

Use PerlIO Safely in Perl C Extensions

You will finish with a small, portable pattern for opening, writing and closing a file through Perl's C-level PerlIO interface, plus a way to decide when stdio interoperability is safe. The examples match Perl 5.38.2 from Ubuntu's perl-doc package and the installed USE_PERLIO build.

Allow about twenty minutes. You need a C extension or Perl core build tree, the Perl development headers, and enough familiarity with pointers and return values to handle I/O errors. No elevated privileges are needed. Do not test by writing into a system directory: use a file in a temporary or dedicated working directory.

1. Confirm the local Perl build

perlapio documents an interface, not a command-line utility. Start by checking the interpreter and the configuration that determines which implementation is underneath it:

$ perl -v | sed -n '1,6p'
$ perl -V:useperlio -V:use64bitint
useperlio='define';
use64bitint='define';
$ dpkg-query -W -f='${Package} ${Version}\n' perl perl-doc
perl 5.38.2-3.2ubuntu0.6
perl-doc 5.38.2-3.2ubuntu0.6

The installed build uses USE_PERLIO, so the PerlIO handle has an extra layer of indirection and layers can affect its behaviour. Treat PerlIO * as opaque. Do not dereference it, cast it to a structure you have invented, or assume it is interchangeable with FILE *.

Checkpoint

If perl -V:useperlio does not report define, the portability rules still apply, but layer operations may be less meaningful because the alternate stdio-backed implementation is in use.

2. Include the PerlIO declarations

Perl source and extensions should use the declarations provided by Perl's headers rather than calling ANSI C stdio directly. In normal Perl code, perlio.h is reached through perl.h:

#include <EXTERN.h>
#include <perl.h>

Build an extension with the compiler and linker flags supplied by ExtUtils::Embed, rather than copying an include directory from one machine to another:

perl -MExtUtils::Embed -e ccopts
perl -MExtUtils::Embed -e ldopts

Those commands print flags for the installed Perl. The exact output is machine-specific. If your extension already has the Perl context and build system expected by XS or Perl core code, include the headers through that existing setup. A standalone C program needs Perl interpreter initialisation and the appropriate context handling; the function calls below are API examples, not a complete embedding program.

3. Open, write and close through PerlIO

Use PerlIO_open(path, mode) in the same broad way as fopen, but put the PerlIO handle first in later calls. Always check for NULL before using it, and check the return from both the write and close:

PerlIO *stream;
const char message[] = "PerlIO smoke test\n";

stream = PerlIO_open("./perlapio-output.txt", "w");
if (stream == NULL) {
    /* errno describes the usual open failure. */
    return 1;
}

if (PerlIO_write(stream, message, sizeof(message) - 1) < 0) {
    PerlIO_close(stream);
    return 1;
}

if (PerlIO_close(stream) < 0)
    return 1;
return 0;

PerlIO_write returns a byte count on success, including zero, and a negative value on error. That differs from a success-only convention, so do not test it with == 0. The open call can also return NULL when the PerlIO handle limit is reached; the manual warns that errno may not be set for that implementation limit.

Writing may buffer data. Closing the handle is therefore part of the operation, not just tidy-up. A close failure can report a problem encountered while flushing. If an error path closes the stream, do not use the handle again afterwards.

Checkpoint

After a successful run, verify the file from the shell without using sudo:

test "$(cat ./perlapio-output.txt)" = 'PerlIO smoke test' && echo 'PerlIO write verified'

This example changes one ordinary file. Recovery is simply to remove ./perlapio-output.txt when you no longer need it. Check the path before using any cleanup command, and never replace the example path with a shared or production file unless overwriting it is intentional.

4. Read and distinguish end of file from failure

For byte-oriented reads, PerlIO_read returns the number of bytes read. A negative result is an error; zero is a successful zero-byte read and may also be how an end-of-file condition is observed. Check PerlIO_eof and PerlIO_error when the distinction matters:

unsigned char buffer[4096];
SSize_t got;

got = PerlIO_read(stream, buffer, sizeof buffer);
if (got < 0) {
    /* I/O error; errno may be EINTR after a signal. */
    return 1;
}
if (got == 0 && PerlIO_error(stream)) {
    return 1;
}
if (got == 0 && PerlIO_eof(stream)) {
    /* End of input. */
}

The handle state can be reset with PerlIO_clearerr. Do not treat a short positive read as an error: it is a valid byte count. For one-byte processing, PerlIO_getc returns the byte or EOF, but only byte values from 0 through 0xFF are defined.

5. Switch between reading and writing correctly

Use PerlIO_seek when changing direction on a read/write handle. It flushes buffered writes or discards buffered reads before positioning the underlying descriptor:

if (PerlIO_flush(stream) < 0)
    return 1;
if (PerlIO_seek(stream, (Off_t)0, SEEK_SET) < 0)
    return 1;

The manual specifically describes seeking as the correct operation for a read/write transition. A bare flush is not a universal substitute: on some stdio-backed implementations, flushing a read-only stream or a stream whose last operation was a read has undefined behaviour. Passing NULL to PerlIO_flush may flush all streams under one implementation and may even cause a core dump under another, so flush a known handle instead.

6. Keep stdio boundaries explicit

Use PerlIO_stdin(), PerlIO_stdout() and PerlIO_stderr() instead of the corresponding stdio variables. If a library genuinely requires a native FILE *, use the conversion API deliberately:

  • PerlIO_exportFILE creates a native stream for a PerlIO handle.
  • PerlIO_releaseFILE tells PerlIO that the exported stream is no longer in use.
  • PerlIO_importFILE adopts an existing FILE *; afterwards, close it through PerlIO_close, not fclose.

Exporting is not a harmless cast. It can add a :stdio layer, repeated exports create repeated native streams, and calling fclose without releasing the association leaves PerlIO's bookkeeping wrong. If you control both sides of the boundary, keep the handle as PerlIO * and avoid the conversion.

7. Use layers and binary mode only when needed

PerlIO_apply_layers is chiefly for the USE_PERLIO implementation. For portable binary or text-mode handling, use PerlIO_binmode with the correct direction character: < for read, > for write, and + for read/write. The manual's portable forms use O_BINARY with no layer string, or O_TEXT with :crlf. On Unix these calls normally have no effect, but on other systems they can control newline translation and text end-of-file handling.

Choose the mode before doing I/O where possible. The effect of changing it after buffered data exists depends on the implementation, so do not use a late mode change as a repair for data already read or written.

Done means

  • You confirmed the installed Perl and whether it uses USE_PERLIO.
  • You treat PerlIO * as opaque and use Perl's headers and build flags.
  • You check open, read, write and close results, distinguishing negative errors from zero-byte reads.
  • You use seek when switching direction on a read/write stream.
  • You avoid stdio conversion unless the receiving library requires FILE *, and release exported streams correctly.
  • Your test wrote only to a deliberate working file and you know how to remove that file afterwards.