Skip to content

Instantly share code, notes, and snippets.

@karthick18
Created April 13, 2022 19:09
Show Gist options
  • Save karthick18/e93d3031998b853c5a1d3a4e836888f7 to your computer and use it in GitHub Desktop.
Save karthick18/e93d3031998b853c5a1d3a4e836888f7 to your computer and use it in GitHub Desktop.
Use splice with vmsplice to move user space buffers to output file
/*
* An example using vmsplice to move user space buffers to pipe before using
* splice syscall which avoids copying to/from user space buffers to kernel space
* and uses the pipe buffers allocated in kernel space as an intermediate to directly xfer from one file to another
*
* gcc -o splice splice.c -g
*/
#define _GNU_SOURCE
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <fcntl.h>
#include <unistd.h>
#include <limits.h>
#include <sys/uio.h>
#define __IO_BUFSIZE (1<<20) /*reasonable buffer size based on pipe buffers */
#ifdef NO_SPLICE
#define do_copy std_copy
#else
#define do_copy spliced_copy
#endif
/*
* Read from in and write to out without using user space buffers.
* Directly splice using the pipe buffer.
*/
#ifndef NO_SPLICE
static int
spliced_copy (struct iovec *vec, int num_buffers, int out_fd)
{
loff_t out_off = 0;
ssize_t len;
ssize_t actual_len = 0;
int i;
int filedes[2];
int err = -1;
if (pipe (filedes) < 0)
{
perror ("pipe:");
goto out;
}
for (i = 0; i < num_buffers; i++)
{
actual_len += vec[i].iov_len;
}
// if you want, you can change pipe buffer size from 64kb to the input buffers size
// needs admin privilege
#if 0
if (fcntl (filedes[1], F_SETPIPE_SZ, actual_len) < 0)
{
perror ("setpipe size:");
}
#endif
// move from buffer to pipe with vmsplice gifting the buffer to kernel hinting
// no writes to our user space buffer
len =
vmsplice (filedes[1], vec, num_buffers, SPLICE_F_MOVE | SPLICE_F_GIFT);
if (len < 0)
{
perror ("vmsplice:");
goto out_close;
}
printf ("Transferring %ld out %ld bytes\n", len, actual_len);
while (len > 0)
{
// move from pipe to out file
/*
* move from pipe buffer to out_fd
*/
err =
splice (filedes[0], NULL, out_fd, &out_off, len,
SPLICE_F_MOVE | SPLICE_F_MORE);
if (err < 0)
{
perror ("splice:");
goto out_close;
}
len -= out_off;
}
err = 0;
out_close:
close (filedes[0]);
close (filedes[1]);
out:
return err;
}
#else
static int
std_copy (struct iovec *vec, int num_buffers, int out_fd)
{
int err = -1;
ssize_t bytes;
if ((bytes = writev (out_fd, vec, num_buffers)) < 0)
{
perror ("writev:");
goto out;
}
printf ("Written %ld bytes to out file\n", bytes);
err = 0;
out:
return err;
}
#endif
// read from infile to a buffer
// this is just for testing splice from buffer to out file
static int
read_into_buffer (int in_fd, struct iovec **vecp, int *num_buffers_p)
{
char buf[__IO_BUFSIZE];
ssize_t bytes;
struct iovec *vec = NULL;
int num_buffers = 0;
int err = -1;
*vecp = NULL;
*num_buffers_p = 0;
while ((bytes = read (in_fd, buf, sizeof (buf))) > 0)
{
vec = realloc (vec, sizeof (*vec) * (num_buffers + 1));
if (vec == NULL)
{
fprintf (stderr, "realloc failure\n");
goto out;
}
char *seg = malloc (bytes);
if (seg == NULL)
{
fprintf (stderr, "malloc failure\n");
goto out;
}
memcpy (seg, buf, bytes);
vec[num_buffers].iov_base = seg;
vec[num_buffers].iov_len = bytes;
num_buffers++;
}
if (bytes < 0)
{
fprintf (stderr, "error reading from infile\n");
if (vec != NULL)
{
free (vec);
}
goto out;
}
*vecp = vec;
*num_buffers_p = num_buffers;
err = 0;
out:
return err;
}
int
main (int argc, char **argv)
{
char infile[0xff + 1], outfile[0xff + 1];
int in_fd = -1, out_fd = -1;
int err = -1;
if (argc != 3)
{
fprintf (stderr, "%s infile outfile\n", argv[0]);
goto out;
}
infile[0] = 0, outfile[0] = 0;
strncat (infile, argv[1], sizeof (infile) - 1);
strncat (outfile, argv[2], sizeof (outfile) - 1);
in_fd = open (infile, O_RDONLY);
if (in_fd < 0)
{
perror ("open:");
goto out;
}
out_fd = open (outfile, O_CREAT | O_WRONLY | O_TRUNC, 0777);
if (out_fd < 0)
{
perror ("open2:");
goto out_close;
}
struct iovec *vec = NULL;
int num_buffers = 0;
if ((err = read_into_buffer (in_fd, &vec, &num_buffers)) < 0)
{
goto out_close2;
}
if (num_buffers > IOV_MAX)
{
printf ("Num iov buffers exceeded. Actual %d, supported %d\n",
num_buffers, IOV_MAX);
goto out_close2;
}
if ((err = do_copy (vec, num_buffers, out_fd)) < 0)
{
printf ("Error copying input file [%s] buffers to output file [%s]\n",
infile, outfile);
}
out_close2:
close (out_fd);
out_close:
close (in_fd);
out:
return err;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment