#ifdef __linux__
#define _GNU_SOURCE /* See feature_test_macros(7) */
#endif
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <errno.h>
#include <unistd.h>
#include <limits.h>
#include "b_builder.h"
#include "b_header.h"
#include "b_string.h"
#include "b_buffer.h"
#include "b_file.h"
#define MAX_CHUNK_SIZE (16 * 1024 * 1024)
/*
* Meant to be used in conjunction with header.c/b_header_encode_longlink_block(),
* this method will write out as many 512-byte blocks as necessary to contain the
* full path.
*/
off_t b_file_write_path_blocks(b_buffer *buf, b_string *path) {
size_t i, len;
off_t blocklen = 0;
off_t total = 0;
len = b_string_len(path);
for (i=0; i<len; i+=B_BLOCK_SIZE) {
size_t left = len - i;
size_t copylen = left < B_BLOCK_SIZE? left: B_BLOCK_SIZE;
unsigned char *block;
if ((block = b_buffer_get_block(buf, B_BLOCK_SIZE, &blocklen)) == NULL) {
goto error_io;
}
memcpy(block, path->str + i, copylen);
total += blocklen;
}
return total;
error_io:
return -1;
}
off_t b_file_write_pax_path_blocks(b_buffer *buf, b_string *path, b_string *linkdest) {
size_t i, len, full_len, link_full_len = 0, buflen, total_len;
off_t blocklen = 0;
off_t total = 0;
char *buffer = NULL;
len = b_string_len(path);
full_len = b_header_compute_pax_length(path, "path");
if (linkdest)
link_full_len = b_header_compute_pax_length(linkdest, "linkpath");
total_len = full_len + link_full_len;
/* In case we have a broken snprintf. */
if (full_len == (size_t)-1)
goto error_io;
if ((buffer = malloc(total_len + 1)) == NULL)
goto error_mem;
snprintf(buffer, total_len + 1, "%d path=%s\n", full_len, path->str);
if (linkdest)
snprintf(buffer + full_len, link_full_len + 1, "%d linkpath=%s\n", link_full_len, linkdest->str);
for (i=0; i<total_len; i+=B_BLOCK_SIZE) {
size_t left = total_len - i;
size_t copylen = left < B_BLOCK_SIZE? left: B_BLOCK_SIZE;
unsigned char *block;
if ((block = b_buffer_get_block(buf, B_BLOCK_SIZE, &blocklen)) == NULL) {
goto error_io;
}
memcpy(block, buffer + i, copylen);
total += blocklen;
}
free(buffer);
return total;
error_io:
error_mem:
free(buffer);
return -1;
}
off_t b_file_write_contents(b_buffer *buf, int file_fd, off_t file_size) {
ssize_t rlen = 0;
off_t blocklen = 0, total = 0, real_total = 0, max_read = 0;
#ifdef __linux__
int emptied_buffer = 0, splice_total = 0;
#endif
do {
if (b_buffer_full(buf)) {
if (b_buffer_flush(buf) < 0) {
goto error_io;
}
#ifdef __linux__
emptied_buffer = 1;
#endif
}
max_read = file_size - real_total;
if (max_read > MAX_CHUNK_SIZE) {
max_read = MAX_CHUNK_SIZE;
}
else if (max_read == 0) {
break;
}
#ifdef __linux__
/*
* Once we have cleared out the buffer we can read the rest of the file
* with splice and write out a tar padding.
*/
if (emptied_buffer && buf->can_splice) {
if ((rlen = splice(file_fd, NULL, buf->fd, NULL, max_read, 0))){
if (rlen < 0) {
buf->can_splice = 0;
}
else {
splice_total += rlen;
total += rlen;
}
}
}
if (!emptied_buffer || !buf->can_splice) {
#endif
unsigned char *block;
if ((block = b_buffer_get_block(buf, b_buffer_unused(buf), &blocklen)) == NULL) {
goto error_io;
}
if (max_read > blocklen) {
max_read = blocklen;
}
read_retry:
if ((rlen = read(file_fd, block, max_read)) < max_read) {
if (rlen < 0 && errno == EINTR) { goto read_retry; }
goto error_io;
}
total += blocklen;
/*
* Reclaim any amount of bytes from the buffer that weren't used to
* store the chunk read() from the filesystem.
*/
if (blocklen - rlen) {
total -= b_buffer_reclaim(buf, rlen, blocklen);
}
#ifdef __linux__
}
#endif
real_total += rlen;
} while (rlen > 0);
#ifdef __linux__
if (splice_total && buf->can_splice && total % B_BUFFER_BLOCK_SIZE != 0) {
/*
* finished splice, now complete the block by writing out zeros to make
* tar happy
*/
if ((write(buf->fd, buf->data, B_BUFFER_BLOCK_SIZE - (total % B_BUFFER_BLOCK_SIZE))) < 0) {
goto error_io;
}
}
#endif
return total;
error_io:
if (!errno) {
errno = EINVAL;
}
return -1;
}