Here is the extend_file_and_insert()
function, which does the job more or less.
#include <sys/stat.h> #include <unistd.h> enum { BUFFERSIZE = 64 * 1024 }; #define MIN(x, y) (((x) < (y)) ? (x) : (y)) /* off_t is signed ssize_t is signed size_t is unsigned off_t for lseek() offset and return size_t for read()/write() length ssize_t for read()/write() return off_t for st_size */ static int extend_file_and_insert(int fd, off_t offset, char const *insert, size_t inslen) { char buffer[BUFFERSIZE]; struct stat sb; int rc = -1; if (fstat(fd, &sb) == 0) { if (sb.st_size > offset) { /* Move data after offset up by inslen bytes */ size_t bytes_to_move = sb.st_size - offset; off_t read_end_offset = sb.st_size; while (bytes_to_move != 0) { ssize_t bytes_this_time = MIN(BUFFERSIZE, bytes_to_move); ssize_t rd_off = read_end_offset - bytes_this_time; ssize_t wr_off = rd_off + inslen; lseek(fd, rd_off, SEEK_SET); if (read(fd, buffer, bytes_this_time) != bytes_this_time) return -1; lseek(fd, wr_off, SEEK_SET); if (write(fd, buffer, bytes_this_time) != bytes_this_time) return -1; bytes_to_move -= bytes_this_time; read_end_offset -= bytes_this_time; /* Added 2013-07-19 */ } } lseek(fd, offset, SEEK_SET); write(fd, insert, inslen); rc = 0; } return rc; }
(Please note that the added date 2013-07-19 was added, it was an error that appears only when the buffer size is less than the amount of data that needs to be copied to the file. Thanks to malat to indicate the error. Code has now been verified using BUFFERSIZE = 4
)
This is a small test code:
#include <fcntl.h> #include <string.h> static const char base_data[] = "12345"; typedef struct Data { off_t posn; const char *data; } Data; static const Data insert[] = { { 2, "456" }, { 4, "XxxxxxX" }, { 12, "ZzzzzzzzzzzzzzzzzzzzzzzzX" }, { 22, "YyyyyyyyyyyyyyyY" }, }; enum { NUM_INSERT = sizeof(insert) / sizeof(insert[0]) }; int main(void) { int fd = open("test.dat", O_RDWR | O_TRUNC | O_CREAT, 0644); if (fd > 0) { ssize_t base_len = sizeof(base_data) - 1; if (write(fd, base_data, base_len) == base_len) { for (int i = 0; i < NUM_INSERT; i++) { off_t length = strlen(insert[i].data); if (extend_file_and_insert(fd, insert[i].posn, insert[i].data, length) != 0) break; lseek(fd, 0, SEEK_SET); char buffer[BUFFERSIZE]; ssize_t nbytes; while ((nbytes = read(fd, buffer, sizeof(buffer))) > 0) write(1, buffer, nbytes); write(1, "\n", 1); } } close(fd); } return(0); }
It outputs the result:
12456345 1245XxxxxxX6345 1245XxxxxxX6ZzzzzzzzzzzzzzzzzzzzzzzzZ345 1245XxxxxxX6ZzzzzzzzzzYyyyyyyyyyyyyyyYzzzzzzzzzzzzzzZ345
It should be tested on some larger files (larger than BUFFERSIZE, but it would be wise to test with BUFFERSIZE much less than 64 KiB, I used 32 bytes, and that seemed OK). I just looked at the results, but the templates are designed to make it easy to see that they are correct. The code does not check for any calls to lseek()
; that is a minor risk.