1110 lines
34 KiB
C
Executable File
1110 lines
34 KiB
C
Executable File
#include "inc/deps.h"
|
|
#include "inc/tar.h"
|
|
|
|
#define MIN(x, y) (((x) < (y)) ? (x) : (y))
|
|
#define MAX(x, y) (((x) > (y)) ? (x) : (y))
|
|
// only print in verbose mode
|
|
#define V_PRINT(f, fmt, ...) if (verbosity) { fprintf(f, fmt "\n", ##__VA_ARGS__); }
|
|
// generic error
|
|
#define ERROR(fmt, ...) fprintf(stderr, "Error: " fmt "\n", ##__VA_ARGS__); return -1;
|
|
// capture errno when erroring
|
|
#define RC_ERROR(fmt, ...) const int rc = errno; ERROR(fmt, ##__VA_ARGS__); return -1;
|
|
#define EXIST_ERROR(fmt, ...) const int rc = errno; if (rc != EEXIST) { ERROR(fmt, ##__VA_ARGS__); return -1; }
|
|
|
|
// force read() to complete
|
|
static int read_size(int fd, char * buf, int size);
|
|
|
|
// force write() to complete
|
|
static int write_size(int fd, char * buf, int size);
|
|
|
|
// convert octal string to unsigned integer
|
|
static unsigned int oct2uint(char * oct, unsigned int size);
|
|
|
|
// check if a buffer is zeroed
|
|
static int iszeroed(char * buf, size_t size);
|
|
|
|
// make directory recursively - no need for verbose pass
|
|
static int recursive_mkdir(const char * dir, const unsigned int mode);
|
|
|
|
int tar_read(const int fd, struct tar_t ** archive, const char verbosity){
|
|
if (fd < 0){
|
|
ERROR("Bad file descriptor");
|
|
}
|
|
|
|
if (!archive || *archive){
|
|
ERROR("Bad archive");
|
|
}
|
|
|
|
unsigned int offset = 0;
|
|
int count = 0;
|
|
|
|
struct tar_t ** tar = archive;
|
|
char update = 1;
|
|
|
|
for(count = 0; ; count++){
|
|
*tar = malloc(sizeof(struct tar_t) + 1);
|
|
if (update && (read_size(fd, (*tar) -> block, 512) != 512)){
|
|
V_PRINT(stderr, "Error: Bad read. Stopping");
|
|
tar_free(*tar);
|
|
*tar = (void *)0;
|
|
break;
|
|
}
|
|
|
|
update = 1;
|
|
// if current block is all zeros
|
|
if (iszeroed((*tar) -> block, 512)){
|
|
if (read_size(fd, (*tar) -> block, 512) != 512){
|
|
V_PRINT(stderr, "Error: Bad read. Stopping");
|
|
tar_free(*tar);
|
|
*tar = (void *)0;
|
|
break;
|
|
}
|
|
|
|
// check if next block is all zeros as well
|
|
if (iszeroed((*tar) -> block, 512)){
|
|
// skip to end of record
|
|
if (lseek(fd, RECORDSIZE - (offset % RECORDSIZE), SEEK_CUR) == (off_t) (-1)){
|
|
RC_ERROR("Unable to seek file: %s", strerror(rc));
|
|
}
|
|
|
|
if(*tar != (void *)0)
|
|
{
|
|
tar_free(*tar);
|
|
*tar = (void *)0;
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
update = 0;
|
|
}
|
|
|
|
// set current entry's file offset
|
|
(*tar) -> begin = offset;
|
|
|
|
// skip over data and unfilled block
|
|
unsigned int jump = oct2uint((*tar) -> size, 11);
|
|
if (jump % 512){
|
|
jump += 512 - (jump % 512);
|
|
}
|
|
|
|
// move file descriptor
|
|
offset += 512 + jump;
|
|
if (lseek(fd, jump, SEEK_CUR) == (off_t) (-1)){
|
|
RC_ERROR("Unable to seek file: %s", strerror(rc));
|
|
}
|
|
|
|
// ready next value
|
|
tar = &((*tar) -> next);
|
|
}
|
|
|
|
return count;
|
|
}
|
|
|
|
int tar_write(const int fd, struct tar_t **archive, const size_t filecount, const char * files[], const char verbosity){
|
|
if (fd < 0){
|
|
ERROR("Bad file descriptor");
|
|
}
|
|
|
|
if (!archive){
|
|
ERROR("Bad archive");
|
|
}
|
|
|
|
// where file descriptor offset is
|
|
int offset = 0;
|
|
|
|
// if there is old data
|
|
struct tar_t ** tar = archive;
|
|
if (*tar){
|
|
// skip to last entry
|
|
while (*tar && (*tar) -> next){
|
|
tar = &((*tar) -> next);
|
|
}
|
|
|
|
// get offset past final entry
|
|
unsigned int jump = 512 + oct2uint((*tar) -> size, 11);
|
|
if (jump % 512){
|
|
jump += 512 - (jump % 512);
|
|
}
|
|
|
|
// move file descriptor
|
|
offset = (*tar) -> begin + jump;
|
|
if (lseek(fd, offset, SEEK_SET) == (off_t) (-1)){
|
|
RC_ERROR("Unable to seek file: %s", strerror(rc));
|
|
}
|
|
tar = &((*tar) -> next);
|
|
}
|
|
|
|
// write entries first
|
|
if (write_entries(fd, tar, archive, filecount, files, &offset, verbosity) < 0){
|
|
tar_free(*archive);
|
|
*tar = (void *)0;
|
|
}
|
|
|
|
// write ending data
|
|
if (write_end_data(fd, offset, verbosity) < 0){
|
|
ERROR("Failed to write end data");
|
|
}
|
|
|
|
// clear original names from data
|
|
tar = archive;
|
|
while (*tar){
|
|
memset((*tar) -> name, 0, 100);
|
|
tar = &((*tar) -> next);
|
|
}
|
|
return offset;
|
|
}
|
|
|
|
void tar_free (struct tar_t *archive)
|
|
{
|
|
while (archive){
|
|
struct tar_t * next = archive -> next;
|
|
if(archive != (void *) 0)
|
|
{
|
|
free(archive);
|
|
archive = (void *) 0;
|
|
archive = next;
|
|
}
|
|
}
|
|
}
|
|
|
|
int tar_ls(FILE * f, struct tar_t * archive, const size_t filecount, const char * files[], const char verbosity){
|
|
if (!verbosity){
|
|
return 0;
|
|
}
|
|
|
|
if (filecount && !files){
|
|
ERROR("Non-zero file count provided, but file list is NULL");
|
|
}
|
|
|
|
while (archive){
|
|
if (ls_entry(f, archive, filecount, files, verbosity) < 0){
|
|
return -1;
|
|
}
|
|
archive = archive -> next;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
int tar_extract(const int fd, struct tar_t * archive, const size_t filecount, const char * files[], const char verbosity){
|
|
int ret = 0;
|
|
|
|
// extract entries with given names
|
|
if (filecount){
|
|
if (!files){
|
|
ERROR("Received non-zero file count but got NULL file list");
|
|
}
|
|
|
|
while (archive){
|
|
for(size_t i = 0; i < filecount; i++){
|
|
if (!strncmp(archive -> name, files[i], MAX(strlen(archive -> name), strlen(files[i])))){
|
|
if (lseek(fd, archive -> begin, SEEK_SET) == (off_t) (-1)){
|
|
RC_ERROR("Unable to seek file: %s", strerror(rc));
|
|
}
|
|
|
|
if (extract_entry(fd, archive, verbosity) < 0){
|
|
ret = -1;
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
archive = archive -> next;
|
|
}
|
|
}
|
|
// extract all
|
|
else{
|
|
// move offset to beginning
|
|
if (lseek(fd, 0, SEEK_SET) == (off_t) (-1)){
|
|
RC_ERROR("Unable to seek file: %s", strerror(rc));
|
|
}
|
|
|
|
// extract each entry
|
|
while (archive){
|
|
if (extract_entry(fd, archive, verbosity) < 0){
|
|
ret = -1;
|
|
}
|
|
archive = archive -> next;
|
|
}
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
int tar_update(const int fd, struct tar_t ** archive, const size_t filecount, const char * files[], const char verbosity){
|
|
if (!filecount){
|
|
return 0;
|
|
}
|
|
|
|
if (filecount && !files){
|
|
ERROR("Non-zero file count provided, but file list is NULL");
|
|
}
|
|
|
|
// buffer for subset of files that need to be updated
|
|
char ** newer = calloc(filecount, sizeof(char *));
|
|
|
|
struct stat st;
|
|
int count = 0;
|
|
int all = 1;
|
|
|
|
// check each source to see if it was updated
|
|
struct tar_t * tar = *archive;
|
|
for(int i = 0; i < (int)filecount; i++){
|
|
// make sure original file exists
|
|
if (lstat(files[i], &st)){
|
|
all = 0;
|
|
RC_ERROR("Could not stat %s: %s", files[i], strerror(rc));
|
|
}
|
|
|
|
// find the file in the archive
|
|
struct tar_t * old = exists(tar, files[i], 1);
|
|
newer[count] = calloc(strlen(files[i]) + 1, sizeof(char));
|
|
|
|
// if there is an older version, check its timestamp
|
|
if (old){
|
|
if (st.st_mtime > oct2uint(old -> mtime, 11)){
|
|
strncpy(newer[count++], files[i], strlen(files[i]));
|
|
V_PRINT(stdout, "%s", files[i]);
|
|
}
|
|
}
|
|
// if there is no older version, just add it
|
|
else{
|
|
strncpy(newer[count++], files[i], strlen(files[i]));
|
|
V_PRINT(stdout, "%s", files[i]);
|
|
}
|
|
}
|
|
|
|
// update listed files only
|
|
if (tar_write(fd, archive, count, (const char **) newer, verbosity) < 0){
|
|
ERROR("Unable to update archive");
|
|
}
|
|
|
|
// cleanup
|
|
for(int i = 0; i < count; i++){
|
|
free(newer[i]);
|
|
}
|
|
free(newer);
|
|
|
|
return all?0:-1;
|
|
}
|
|
|
|
int tar_remove(const int fd, struct tar_t ** archive, const size_t filecount, const char * files[], const char verbosity){
|
|
if (fd < 0){
|
|
return -1;
|
|
}
|
|
|
|
// archive has to exist
|
|
if (!archive || !*archive){
|
|
ERROR("Got bad archive");
|
|
}
|
|
|
|
if (filecount && !files){
|
|
ERROR("Non-zero file count provided, but file list is NULL");
|
|
}
|
|
|
|
if (!filecount){
|
|
V_PRINT(stderr, "No entries specified");
|
|
return 0;
|
|
}
|
|
|
|
// get file permissions
|
|
struct stat st;
|
|
if (fstat(fd, &st)){
|
|
RC_ERROR("Unable to stat archive: %s", strerror(rc));
|
|
}
|
|
|
|
// reset offset of original file
|
|
if (lseek(fd, 0, SEEK_SET) == (off_t) (-1)){
|
|
RC_ERROR("Unable to seek file: %s", strerror(rc));
|
|
}
|
|
|
|
// find first file to be removed that does not exist
|
|
int ret = 0;
|
|
for(int i = 0; i < (int)filecount; i++){
|
|
if (!exists(*archive, files[i], 0)){
|
|
ERROR("'%s' not found in archive", files[i]);
|
|
}
|
|
}
|
|
|
|
unsigned int read_offset = 0;
|
|
unsigned int write_offset = 0;
|
|
struct tar_t * prev = NULL;
|
|
struct tar_t * curr = *archive;
|
|
while(curr){
|
|
// get original size
|
|
int total = 512;
|
|
|
|
if ((curr -> type == REGULAR) || (curr -> type == NORMAL) || (curr -> type == CONTIGUOUS)){
|
|
total += oct2uint(curr -> size, 11);
|
|
if (total % 512){
|
|
total += 512 - (total % 512);
|
|
}
|
|
}
|
|
|
|
const int match = check_match(curr, filecount, files);
|
|
|
|
if (match < 0){
|
|
ERROR("Match failed");
|
|
}
|
|
else if (!match){
|
|
// if the old data is not in the right place, move it
|
|
if (write_offset < read_offset){
|
|
int got = 0;
|
|
while (got < total){
|
|
// go to old data
|
|
if (lseek(fd, read_offset, SEEK_SET) == (off_t) (-1)){
|
|
RC_ERROR("Cannot seek: %s", strerror(rc));
|
|
}
|
|
|
|
char buf[512];
|
|
|
|
// copy chunk out
|
|
if (read_size(fd, buf, 512) != 512){// guarenteed 512 octets
|
|
ERROR("Read error");
|
|
}
|
|
|
|
// go to new position
|
|
if (lseek(fd, write_offset, SEEK_SET) == (off_t) (-1)){
|
|
RC_ERROR("Cannot seek: %s", strerror(rc));
|
|
}
|
|
|
|
// write data in
|
|
if (write_size(fd, buf, 512) != 512){
|
|
RC_ERROR("Write error: %s", strerror(rc));
|
|
}
|
|
|
|
// increment offsets
|
|
got += 512;
|
|
read_offset += 512;
|
|
write_offset += 512;
|
|
}
|
|
}
|
|
else{
|
|
read_offset += total;
|
|
write_offset += total;
|
|
|
|
// skip past data
|
|
if (lseek(fd, read_offset, SEEK_SET) == (off_t) (-1)){
|
|
RC_ERROR("Cannot seek: %s", strerror(rc));
|
|
}
|
|
}
|
|
prev = curr;
|
|
curr = curr -> next;
|
|
}
|
|
else{// if name matches, skip the data
|
|
struct tar_t * tmp = curr;
|
|
if (!prev){
|
|
*archive = curr -> next;
|
|
if (*archive){
|
|
(*archive) -> begin = 0;
|
|
}
|
|
}
|
|
else{
|
|
prev -> next = curr -> next;
|
|
|
|
if (prev -> next){
|
|
prev -> next -> begin = curr -> begin;
|
|
}
|
|
}
|
|
curr = curr -> next;
|
|
free(tmp);
|
|
|
|
// next read starts after current entry
|
|
read_offset += total;
|
|
}
|
|
}
|
|
|
|
// resize file
|
|
if (ftruncate(fd, write_offset) < 0){
|
|
RC_ERROR("Could not truncate file: %s", strerror(rc));
|
|
}
|
|
|
|
// add end data
|
|
if (write_end_data(fd, write_offset, verbosity) < 0){
|
|
V_PRINT(stderr, "Error: Could not close file");
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
int tar_diff(FILE * f, struct tar_t * archive, const char verbosity){
|
|
struct stat st;
|
|
while (archive){
|
|
V_PRINT(f, "%s", archive -> name);
|
|
|
|
// if not found, print error
|
|
if (lstat(archive -> name, &st)){
|
|
int rc = errno;
|
|
fprintf(f, "Could not ");
|
|
if (archive -> type == SYMLINK){
|
|
fprintf(f, "readlink");
|
|
}
|
|
else{
|
|
fprintf(f, "stat");
|
|
}
|
|
fprintf(f, " %s: %s", archive -> name, strerror(rc));
|
|
}
|
|
else{
|
|
if (st.st_mtime != oct2uint(archive -> mtime, 11)){
|
|
fprintf(f, "%s: Mod time differs", archive -> name);
|
|
}
|
|
if (st.st_size != oct2uint(archive -> size, 11)){
|
|
fprintf(f, "%s: Mod time differs", archive -> name);
|
|
}
|
|
}
|
|
|
|
archive = archive -> next;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
int print_entry_metadata(FILE * f, struct tar_t * entry){
|
|
if (!entry){
|
|
return -1;
|
|
}
|
|
|
|
time_t mtime = oct2uint(entry -> mtime, 12);
|
|
char mtime_str[32];
|
|
strftime(mtime_str, sizeof(mtime_str), "%c", localtime(&mtime));
|
|
fprintf(f, "File Name: %s\n", entry -> name);
|
|
fprintf(f, "File Mode: %s (%03o)\n", entry -> mode, oct2uint(entry -> mode, 8));
|
|
fprintf(f, "Owner UID: %s (%d)\n", entry -> uid, oct2uint(entry -> uid, 12));
|
|
fprintf(f, "Owner GID: %s (%d)\n", entry -> gid, oct2uint(entry -> gid, 12));
|
|
fprintf(f, "File Size: %s (%d)\n", entry -> size, oct2uint(entry -> size, 12));
|
|
fprintf(f, "Time : %s (%s)\n", entry -> mtime, mtime_str);
|
|
fprintf(f, "Checksum : %s\n", entry -> check);
|
|
fprintf(f, "File Type: ");
|
|
switch (entry -> type){
|
|
case REGULAR: case NORMAL:
|
|
fprintf(f, "Normal File");
|
|
break;
|
|
case HARDLINK:
|
|
fprintf(f, "Hard Link");
|
|
break;
|
|
case SYMLINK:
|
|
fprintf(f, "Symbolic Link");
|
|
break;
|
|
case CHAR:
|
|
fprintf(f, "Character Special");
|
|
break;
|
|
case BLOCK:
|
|
fprintf(f, "Block Special");
|
|
break;
|
|
case DIRECTORY:
|
|
fprintf(f, "Directory");
|
|
break;
|
|
case FIFO:
|
|
fprintf(f, "FIFO");
|
|
break;
|
|
case CONTIGUOUS:
|
|
fprintf(f, "Contiguous File");
|
|
break;
|
|
}
|
|
fprintf(f, " (%c)\n", entry -> type?entry -> type:'0');
|
|
fprintf(f, "Link Name: %s\n", entry -> link_name);
|
|
fprintf(f, "Ustar\\000: %c%c%c%c%c\\%2x\\%2x\\%02x\n", entry -> ustar[0], entry -> ustar[1], entry -> ustar[2], entry -> ustar[3], entry -> ustar[4], entry -> ustar[5], entry -> ustar[6], entry -> ustar[7]);
|
|
fprintf(f, "Username : %s\n", entry -> owner);
|
|
fprintf(f, "Group : %s\n", entry -> group);
|
|
fprintf(f, "Major : %s\n", entry -> major);
|
|
fprintf(f, "Minor : %s\n", entry -> minor);
|
|
fprintf(f, "Prefix : %s\n", entry -> prefix);
|
|
fprintf(f, "\n");
|
|
|
|
return 0;
|
|
}
|
|
|
|
int print_tar_metadata(FILE * f, struct tar_t * archive){
|
|
while (archive){
|
|
print_entry_metadata(f, archive);
|
|
archive = archive -> next;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
struct tar_t * exists(struct tar_t * archive, const char * filename, const char ori){
|
|
while (archive){
|
|
if (ori){
|
|
if (!strncmp(archive -> original_name, filename, MAX(strlen(archive -> original_name), strlen(filename)) + 1)){
|
|
return archive;
|
|
}
|
|
}
|
|
else{
|
|
if (!strncmp(archive -> name, filename, MAX(strlen(archive -> name), strlen(filename)) + 1)){
|
|
return archive;
|
|
}
|
|
}
|
|
archive = archive -> next;
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
int format_tar_data(struct tar_t * entry, const char * filename, const char verbosity){
|
|
if (!entry){
|
|
ERROR("Bad destination entry");
|
|
}
|
|
|
|
struct stat st;
|
|
if (lstat(filename, &st)){
|
|
RC_ERROR("Cannot stat %s: %s", filename, strerror(rc));
|
|
}
|
|
|
|
// remove relative path
|
|
int move = 0;
|
|
if (!strncmp(filename, "/", 1)){
|
|
move = 1;
|
|
}
|
|
else if (!strncmp(filename, "./", 2)){
|
|
move = 2;
|
|
}
|
|
else if (!strncmp(filename, "../", 3)){
|
|
move = 3;
|
|
}
|
|
|
|
// start putting in new data (all fields are NULL terminated ASCII strings)
|
|
memset(entry, 0, sizeof(struct tar_t));
|
|
strncpy(entry -> original_name, filename, 100);
|
|
strncpy(entry -> name, filename + move, 100);
|
|
snprintf(entry -> mode, sizeof(entry -> mode), "%07o", st.st_mode & 0777);
|
|
snprintf(entry -> uid, sizeof(entry -> uid), "%07o", st.st_uid);
|
|
snprintf(entry -> gid, sizeof(entry -> gid), "%07o", st.st_gid);
|
|
snprintf(entry -> size, sizeof(entry -> size), "%011o", (int) st.st_size);
|
|
snprintf(entry -> mtime, sizeof(entry -> mtime), "%011o", (int) st.st_mtime);
|
|
strncpy(entry -> group, "None", 5); // default value
|
|
memcpy(entry -> ustar, "ustar \x00", 8);
|
|
|
|
// figure out filename type and fill in type-specific fields
|
|
switch (st.st_mode & S_IFMT) {
|
|
case S_IFREG:
|
|
entry -> type = NORMAL;
|
|
break;
|
|
case S_IFLNK:
|
|
entry -> type = SYMLINK;
|
|
|
|
// file size is 0, but will print link size
|
|
memset(entry -> size, '0', sizeof(entry -> size) - 1);
|
|
|
|
// get link name
|
|
if (readlink(filename, entry -> link_name, 100) < 0){
|
|
RC_ERROR("Could not read link %s: %s", filename, strerror(rc));
|
|
}
|
|
|
|
break;
|
|
case S_IFCHR:
|
|
entry -> type = CHAR;
|
|
// get character device major and minor values
|
|
snprintf(entry -> major, sizeof(entry -> major), "%07o", major(st.st_rdev));
|
|
snprintf(entry -> minor, sizeof(entry -> minor), "%07o", minor(st.st_rdev));
|
|
break;
|
|
case S_IFBLK:
|
|
entry -> type = BLOCK;
|
|
// get block device major and minor values
|
|
snprintf(entry -> major, sizeof(entry -> major), "%07o", major(st.st_rdev));
|
|
snprintf(entry -> minor, sizeof(entry -> minor), "%07o", minor(st.st_rdev));
|
|
break;
|
|
case S_IFDIR:
|
|
memset(entry -> size, '0', 11);
|
|
entry -> type = DIRECTORY;
|
|
break;
|
|
case S_IFIFO:
|
|
entry -> type = FIFO;
|
|
break;
|
|
case S_IFSOCK:
|
|
entry -> type = -1;
|
|
ERROR("Error: Cannot tar socket");
|
|
default:
|
|
entry -> type = -1;
|
|
ERROR("Error: Unknown filetype");
|
|
}
|
|
|
|
// get username
|
|
struct passwd pwd;
|
|
char buffer[4096];
|
|
struct passwd * result = NULL;
|
|
if (getpwuid_r(st.st_uid, &pwd, buffer, sizeof(buffer), &result)) {
|
|
const int err = errno;
|
|
V_PRINT(stderr, "Warning: Unable to get username of uid %u for entry '%s': %s", st.st_uid, filename, strerror(err));
|
|
}
|
|
|
|
strncpy(entry -> owner, buffer, sizeof(entry -> owner) - 1);
|
|
|
|
// get group name
|
|
struct group * grp = getgrgid(st.st_gid);
|
|
if (grp){
|
|
strncpy(entry -> group, grp -> gr_name, sizeof(entry -> group) - 1);
|
|
}
|
|
|
|
// get the checksum
|
|
calculate_checksum(entry);
|
|
|
|
return 0;
|
|
}
|
|
|
|
unsigned int calculate_checksum(struct tar_t * entry){
|
|
// use spaces for the checksum bytes while calculating the checksum
|
|
memset(entry -> check, ' ', 8);
|
|
|
|
// sum of entire metadata
|
|
unsigned int check = 0;
|
|
for(int i = 0; i < 512; i++){
|
|
check += (unsigned char) entry -> block[i];
|
|
}
|
|
|
|
snprintf(entry -> check, sizeof(entry -> check), "%06o0", check);
|
|
|
|
entry -> check[6] = '\0';
|
|
entry -> check[7] = ' ';
|
|
return check;
|
|
}
|
|
|
|
int ls_entry(FILE * f, struct tar_t * entry, const size_t filecount, const char * files[], const char verbosity){
|
|
if (!verbosity){
|
|
return 0;
|
|
}
|
|
|
|
if (filecount && !files){
|
|
V_PRINT(stderr, "Error: Non-zero file count given but no files given");
|
|
return -1;
|
|
}
|
|
|
|
// figure out whether or not to print
|
|
// if no files were specified, print everything
|
|
char print = !filecount;
|
|
// otherwise, search for matching names
|
|
for(size_t i = 0; i < filecount; i++){
|
|
if (strncmp(entry -> name, files[i], MAX(strlen(entry -> name), strlen(files[i])))){
|
|
print = 1;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (print){
|
|
if (verbosity > 1){
|
|
const mode_t mode = oct2uint(entry -> mode, 7);
|
|
const char mode_str[26] = { "-hlcbdp-"[entry -> type?entry -> type - '0':0],
|
|
mode & S_IRUSR?'r':'-',
|
|
mode & S_IWUSR?'w':'-',
|
|
mode & S_IXUSR?'x':'-',
|
|
mode & S_IRGRP?'r':'-',
|
|
mode & S_IWGRP?'w':'-',
|
|
mode & S_IXGRP?'x':'-',
|
|
mode & S_IROTH?'r':'-',
|
|
mode & S_IWOTH?'w':'-',
|
|
mode & S_IXOTH?'x':'-',
|
|
0};
|
|
fprintf(f, "%s %s/%s ", mode_str, entry -> owner, entry -> group);
|
|
char size_buf[22] = {0};
|
|
int rc = -1;
|
|
switch (entry -> type){
|
|
case REGULAR: case NORMAL: case CONTIGUOUS:
|
|
rc = sprintf(size_buf, "%u", oct2uint(entry -> size, 11));
|
|
break;
|
|
case HARDLINK: case SYMLINK: case DIRECTORY: case FIFO:
|
|
rc = sprintf(size_buf, "%u", oct2uint(entry -> size, 11));
|
|
break;
|
|
case CHAR: case BLOCK:
|
|
rc = sprintf(size_buf, "%d,%d", oct2uint(entry -> major, 7), oct2uint(entry -> minor, 7));
|
|
break;
|
|
}
|
|
|
|
if (rc < 0){
|
|
ERROR("Failed to write length");
|
|
}
|
|
|
|
fprintf(f, "%s", size_buf);
|
|
|
|
time_t mtime = oct2uint(entry -> mtime, 11);
|
|
struct tm * time = localtime(&mtime);
|
|
fprintf(f, " %d-%02d-%02d %02d:%02d ", time -> tm_year + 1900, time -> tm_mon + 1, time -> tm_mday, time -> tm_hour, time -> tm_min);
|
|
}
|
|
|
|
fprintf(f, "%s", entry -> name);
|
|
|
|
if (verbosity > 1){
|
|
switch (entry -> type){
|
|
case HARDLINK:
|
|
fprintf(f, " link to %s", entry -> link_name);
|
|
break;
|
|
case SYMLINK:
|
|
fprintf(f, " -> %s", entry -> link_name);
|
|
break;
|
|
break;
|
|
}
|
|
}
|
|
|
|
fprintf(f, "\n");
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
int extract_entry(const int fd, struct tar_t * entry, const char verbosity){
|
|
V_PRINT(stdout, "%s", entry -> name);
|
|
|
|
if ((entry -> type == REGULAR) || (entry -> type == NORMAL) || (entry -> type == CONTIGUOUS)){
|
|
// create intermediate directories
|
|
size_t len = strlen(entry -> name);
|
|
if (!len)
|
|
{
|
|
ERROR("Attempted to extract entry with empty name");
|
|
}
|
|
|
|
char * path = calloc(len + 1, sizeof(char));
|
|
strncpy(path, entry -> name, len);
|
|
|
|
// remove file from path
|
|
while (--len && (path[len] != '/'));
|
|
path[len] = '\0'; // if nothing was found, path is terminated
|
|
|
|
if (recursive_mkdir(path, DEFAULT_DIR_MODE) < 0){
|
|
V_PRINT(stderr, "Could not make directory %s", path);
|
|
free(path);
|
|
return -1;
|
|
}
|
|
free(path);
|
|
|
|
// create file
|
|
const unsigned int size = oct2uint(entry -> size, 11);
|
|
int f = open(entry -> name, O_WRONLY | O_CREAT | O_TRUNC, oct2uint(entry -> mode, 7) & 0777);
|
|
if (f < 0){
|
|
RC_ERROR("Unable to open file %s: %s", entry -> name, strerror(rc));
|
|
}
|
|
|
|
// move archive pointer to data location
|
|
if (lseek(fd, 512 + entry -> begin, SEEK_SET) == (off_t) (-1)){
|
|
RC_ERROR("Bad index: %s", strerror(rc));
|
|
}
|
|
|
|
// copy data to file
|
|
char buf[512];
|
|
int got = 0;
|
|
while (got < (int)size){
|
|
int r;
|
|
if ((r = read_size(fd, buf, MIN(size - got, 512))) < 0){
|
|
EXIST_ERROR("Unable to read from archive: %s", strerror(rc));
|
|
}
|
|
|
|
if (write(f, buf, r) != r){
|
|
EXIST_ERROR("Unable to write to %s: %s", entry -> name, strerror(rc));
|
|
}
|
|
|
|
got += r;
|
|
}
|
|
|
|
close(f);
|
|
}
|
|
else if ((entry -> type == CHAR) || (entry -> type == BLOCK)){
|
|
if (mknod(entry -> name, oct2uint(entry -> mode, 7), (oct2uint(entry -> major, 7) << 20) | oct2uint(entry -> minor, 7)) < 0){
|
|
EXIST_ERROR("Unable to make device %s: %s", entry -> name, strerror(rc));
|
|
}
|
|
}
|
|
else if (entry -> type == HARDLINK){
|
|
if (link(entry -> link_name, entry -> name) < 0){
|
|
EXIST_ERROR("Unable to create hardlink %s: %s", entry -> name, strerror(rc));
|
|
}
|
|
}
|
|
else if (entry -> type == SYMLINK){
|
|
if (symlink(entry -> link_name, entry -> name) < 0){
|
|
EXIST_ERROR("Unable to make symlink %s: %s", entry -> name, strerror(rc));
|
|
}
|
|
}
|
|
else if (entry -> type == CHAR){
|
|
if (mknod(entry -> name, S_IFCHR | (oct2uint(entry -> mode, 7) & 0777), (oct2uint(entry -> major, 7) << 20) | oct2uint(entry -> minor, 7)) < 0){
|
|
EXIST_ERROR("Unable to create directory %s: %s", entry -> name, strerror(rc));
|
|
}
|
|
}
|
|
else if (entry -> type == BLOCK){
|
|
if (mknod(entry -> name, S_IFBLK | (oct2uint(entry -> mode, 7) & 0777), (oct2uint(entry -> major, 7) << 20) | oct2uint(entry -> minor, 7)) < 0){
|
|
EXIST_ERROR("Unable to create directory %s: %s", entry -> name, strerror(rc));
|
|
}
|
|
}
|
|
else if (entry -> type == DIRECTORY){
|
|
if (recursive_mkdir(entry -> name, oct2uint(entry -> mode, 7) & 0777) < 0){
|
|
EXIST_ERROR("Unable to create directory %s: %s", entry -> name, strerror(rc));
|
|
}
|
|
}
|
|
else if (entry -> type == FIFO){
|
|
if (mkfifo(entry -> name, oct2uint(entry -> mode, 7) & 0777) < 0){
|
|
EXIST_ERROR("Unable to make pipe %s: %s", entry -> name, strerror(rc));
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
int write_entries(const int fd, struct tar_t ** archive, struct tar_t ** head, const size_t filecount, const char * files[], int * offset, const char verbosity){
|
|
if (fd < 0){
|
|
ERROR("Bad file descriptor");
|
|
}
|
|
|
|
if (!archive || *archive){
|
|
ERROR("Bad archive");
|
|
}
|
|
|
|
if (filecount && !files){
|
|
ERROR("Non-zero file count provided, but file list is NULL");
|
|
}
|
|
|
|
// add new data
|
|
struct tar_t **tar = archive; // current entry
|
|
for(unsigned int i = 0; i < filecount; i++){
|
|
*tar = malloc (sizeof (struct tar_t));
|
|
|
|
// stat file
|
|
if (format_tar_data(*tar, files[i], verbosity) < 0){
|
|
tar_free(*tar);
|
|
*tar = (void *)0;
|
|
}
|
|
|
|
(*tar) -> begin = *offset;
|
|
|
|
// directories need special handling
|
|
if ((*tar) -> type == DIRECTORY){
|
|
// save parent directory name (source will change)
|
|
const size_t len = strlen((*tar) -> name);
|
|
char * parent = calloc(len + 1, sizeof(char));
|
|
strncpy(parent, (*tar) -> name, len);
|
|
|
|
// add a '/' character to the end
|
|
if ((len < 99) && ((*tar) -> name[len - 1] != '/')){
|
|
(*tar) -> name[len] = '/';
|
|
(*tar) -> name[len + 1] = '\0';
|
|
calculate_checksum(*tar);
|
|
}
|
|
|
|
V_PRINT(stdout, "Writing %s", (*tar) -> name);
|
|
|
|
// write metadata to (*tar) file
|
|
if (write_size(fd, (*tar) -> block, 512) != 512){
|
|
tar_free(*tar);
|
|
*tar = (void *)0;
|
|
}
|
|
|
|
// go through directory
|
|
DIR * d = opendir(parent);
|
|
if (!d){
|
|
tar_free(*tar);
|
|
*tar = (void *)0;
|
|
}
|
|
|
|
struct dirent * dir;
|
|
while ((dir = readdir(d))){
|
|
// if not special directories . and ..
|
|
const size_t sublen = strlen(dir -> d_name);
|
|
if (strncmp(dir -> d_name, ".", sublen) && strncmp(dir -> d_name, "..", sublen)){
|
|
char * path = calloc(len + sublen + 2, sizeof(char));
|
|
sprintf(path, "%s/%s", parent, dir -> d_name);
|
|
|
|
// recursively write each subdirectory
|
|
if (write_entries(fd, &((*tar) -> next), head, 1, (const char **) &path, offset, verbosity) < 0){
|
|
tar_free(*tar);
|
|
*tar = (void *)0;
|
|
}
|
|
|
|
// go to end of new data
|
|
while ((*tar) -> next){
|
|
tar = &((*tar) -> next);
|
|
}
|
|
|
|
free(path);
|
|
}
|
|
}
|
|
closedir(d);
|
|
|
|
free(parent);
|
|
|
|
tar = &((*tar) -> next);
|
|
}
|
|
else{ // if (((*tar) -> type == REGULAR) || ((*tar) -> type == NORMAL) || ((*tar) -> type == CONTIGUOUS) || ((*tar) -> type == SYMLINK) || ((*tar) -> type == CHAR) || ((*tar) -> type == BLOCK) || ((*tar) -> type == FIFO)){
|
|
V_PRINT(stdout, "Writing %s", (*tar) -> name);
|
|
|
|
char tarred = 0; // whether or not the file has already been put into the archive
|
|
if (((*tar) -> type == REGULAR) || ((*tar) -> type == NORMAL) || ((*tar) -> type == CONTIGUOUS) || ((*tar) -> type == SYMLINK)){
|
|
struct tar_t * found = exists(*head, files[i], 1);
|
|
tarred = (found != (*tar));
|
|
|
|
// if file has already been included, modify the header
|
|
if (tarred){
|
|
// change type to hard link
|
|
(*tar) -> type = HARDLINK;
|
|
|
|
// change link name to (*tar)red file name (both are the same)
|
|
strncpy((*tar) -> link_name, (*tar) -> name, 100);
|
|
|
|
// change size to 0
|
|
memset((*tar) -> size, '0', sizeof((*tar) -> size) - 1);
|
|
|
|
// recalculate checksum
|
|
calculate_checksum(*tar);
|
|
}
|
|
}
|
|
|
|
// write metadata to (*tar) file
|
|
if (write_size(fd, (*tar) -> block, 512) != 512){
|
|
tar_free(*tar);
|
|
*tar = (void *)0;
|
|
}
|
|
|
|
if (((*tar) -> type == REGULAR) || ((*tar) -> type == NORMAL) || ((*tar) -> type == CONTIGUOUS)){
|
|
// if the file isn't already in the tar file, copy the contents in
|
|
if (!tarred){
|
|
int f = open((*tar) -> name, O_RDONLY);
|
|
if (f < 0){
|
|
tar_free(*tar);
|
|
*tar = (void *)0;
|
|
}
|
|
|
|
int r = 0;
|
|
char buf[512];
|
|
while ((r = read_size(f, buf, 512)) > 0){
|
|
if (write_size(fd, buf, r) != r){
|
|
RC_ERROR("Could not write to archive: %s", strerror(rc));
|
|
}
|
|
}
|
|
|
|
close(f);
|
|
}
|
|
}
|
|
|
|
// pad data to fill block
|
|
const unsigned int size = oct2uint((*tar) -> size, 11);
|
|
const unsigned int pad = 512 - size % 512;
|
|
if (pad != 512){
|
|
for(unsigned int j = 0; j < pad; j++){
|
|
if (write_size(fd, "\0", 1) != 1){
|
|
tar_free(*tar);
|
|
*tar = (void *)0;
|
|
}
|
|
}
|
|
*offset += pad;
|
|
}
|
|
*offset += size;
|
|
tar = &((*tar) -> next);
|
|
}
|
|
|
|
// add metadata size
|
|
*offset += 512;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
int write_end_data(const int fd, int size, const char verbosity){
|
|
if (fd < 0){
|
|
return -1;
|
|
}
|
|
|
|
// complete current record
|
|
const int pad = RECORDSIZE - (size % RECORDSIZE);
|
|
for(int i = 0; i < pad; i++){
|
|
if (write(fd, "\0", 1) != 1){
|
|
V_PRINT(stderr, "Error: Unable to close tar file");
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
// if the current record does not have 2 blocks of zeros, add a whole other record
|
|
if (pad < (2 * BLOCKSIZE)){
|
|
for(int i = 0; i < RECORDSIZE; i++){
|
|
if (write(fd, "\0", 1) != 1){
|
|
V_PRINT(stderr, "Error: Unable to close tar file");
|
|
return -1;
|
|
}
|
|
}
|
|
return pad + RECORDSIZE;
|
|
}
|
|
|
|
return pad;
|
|
}
|
|
|
|
int check_match(struct tar_t * entry, const size_t filecount, const char * files[]){
|
|
if (!entry){
|
|
return -1;
|
|
}
|
|
|
|
if (!filecount){
|
|
return 0;
|
|
}
|
|
|
|
if (filecount && !files){
|
|
return -1;
|
|
}
|
|
|
|
for(size_t i = 0; i < filecount; i++){
|
|
if (!strncmp(entry -> name, files[i], MAX(strlen(entry -> name), strlen(files[i])) + 1)){
|
|
return i + 1;
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
int read_size(int fd, char * buf, int size){
|
|
int got = 0, rc;
|
|
while ((got < size) && ((rc = read(fd, buf + got, size - got)) > 0)){
|
|
got += rc;
|
|
}
|
|
return got;
|
|
}
|
|
|
|
int write_size(int fd, char * buf, int size){
|
|
int wrote = 0, rc;
|
|
while ((wrote < size) && ((rc = write(fd, buf + wrote, size - wrote)) > 0)){
|
|
wrote += rc;
|
|
}
|
|
return wrote;
|
|
}
|
|
|
|
unsigned int oct2uint(char * oct, unsigned int size){
|
|
unsigned int out = 0;
|
|
int i = 0;
|
|
while ((i < (int)size) && oct[i]){
|
|
out = (out << 3) | (unsigned int) (oct[i++] - '0');
|
|
}
|
|
return out;
|
|
}
|
|
|
|
int iszeroed(char * buf, size_t size){
|
|
for(size_t i = 0; i < size; buf++, i++){
|
|
if (* (char *) buf){
|
|
return 0;
|
|
}
|
|
}
|
|
return 1;
|
|
}
|
|
|
|
int recursive_mkdir(const char * dir, const unsigned int mode){
|
|
int rc = 0;
|
|
const size_t len = strlen(dir);
|
|
|
|
if (!len){
|
|
return 0;
|
|
}
|
|
|
|
char * path = calloc(len + 1, sizeof(char));
|
|
strncpy(path, dir, len);
|
|
|
|
// remove last '/'
|
|
if (path[len - 1] == '/'){
|
|
path[len - 1] = 0;
|
|
}
|
|
|
|
// all subsequent directories do not exist
|
|
for(char * p = path + 1; *p; p++){
|
|
if (*p == '/'){
|
|
*p = '\0';
|
|
|
|
if ((rc = mkdir(path, mode?mode:DEFAULT_DIR_MODE))){
|
|
EXIST_ERROR("Could not create directory %s: %s", path, strerror(rc));
|
|
}
|
|
|
|
*p = '/';
|
|
}
|
|
}
|
|
|
|
if (mkdir(path, mode?mode:DEFAULT_DIR_MODE) < 0){
|
|
EXIST_ERROR("Could not create directory %s: %s", path, strerror(rc));
|
|
}
|
|
|
|
free(path);
|
|
return 0;
|
|
}
|