I am writing a tar implementation for education purposes and I started with reading from a tar file and printing the file content.
I use recursion for getting the next tar header and googled "When to use recursion?" Landed on this post where Peter Burns said:
There is a technique that language implementers can use called tail call optimization which can eliminate some classes of stack overflow. Put succinctly: if a function's return expression is simply the result of a function call, then you don't need to add a new level onto the stack, you can reuse the current one for the function being called. Regrettably, few imperative language-implementations have tail-call optimization built in.
Did he explicitly mean language implementers or can this be used by me too then?
As I was told to add the complete code so I will add what I got for now. It's not much because this is my first time working with a standardized format and I first want to be a able to read every header and get the offsets right, as tar pads content to next bigger blocksize.
It only works for tar file containing files only. I will implement all other kind of types if I understand more.
For now as already asked. Is the tar_getNextHeader()
function badly designed regarding tail recursion and the quote above?
And I would like to improve my naming skills for structures, definitions because I think some structure and definitions are not nicely named.
Also if you see something I can do better or should not do/use, let me know.
tar.h:
#ifndef _TAR_H // BEGIN INCLUDE GUARD
#define _TAR_H
/*********************************************************************************************************/
/* DEPENDANICIES */
/*********************************************************************************************************/
#include <stdio.h>
/*********************************************************************************************************/
/* DEFINITIONS */
/*********************************************************************************************************/
typedef struct tar tar;
/*********************************************************************************************************/
/* FUNCTION DEFINITIONS */
/*********************************************************************************************************/
tar* tar_load(const char *filename);
void tar_print(tar *tar);
#endif
tar.c:
/*********************************************************************************************************/
/* DEPENDANICIES */
/*********************************************************************************************************/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdbool.h>
#include "tar.h"
#include "tar_internal.h"
/*********************************************************************************************************/
/* DEFINITIONS */
/*********************************************************************************************************/
/*********************************************************************************************************/
/* FUNCTION DEFINITIONS */
/*********************************************************************************************************/
/**
* @brief Loads a tar file and parses/allocates included headers
* NOTE: check for errno after calling
*
* @param path path to tar file
* @retval tar* on success
* @retval NULL on failure
*/
tar* tar_load(const char *path)
{
struct stat fi;
if(stat(path, &fi) != 0) {
return NULL;
}
tar *tar = malloc(sizeof *tar);
if(tar == NULL) {
return NULL;
}
tar->size = fi.st_size;
tar->filename = path;
tar->fd = open(path, O_RDONLY);
if(tar->fd == -1) {
free(tar);
return NULL;
}
tar->files = tar_getNextHeader(tar);
return tar;
}
/**
* @brief Prints file content (for testing purposes)
*
* @param tar
*/
void tar_print(tar *tar)
{
lseek(tar->fd, 0, SEEK_SET);
tar_entry *current = tar->files;
while(current) {
lseek(tar->fd, current->offset, SEEK_SET);
char buffer[512];
if(read(tar->fd, buffer, current->size) == 0) {
printf("EOF\n");
return;
}
printf("File: %s\n", current->header.filename);
printf("Content:\n%s\n\n", buffer);
current = current->next;
}
}
tar_internal.h:
#ifndef _TAR_INTERNAL_H // BEGIN INCLUDE GUARD
#define _TAR_INTERNAL_H
/*********************************************************************************************************/
/* DEPENDANICIES */
/*********************************************************************************************************/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdbool.h>
#include <errno.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <dirent.h>
/*********************************************************************************************************/
/* DEFINITIONS */
/*********************************************************************************************************/
#define NORMAL_FILE '0'
#define HARD_LINK '1'
#define SYMBOLIC_LINK '2'
#define CHARACTER_SPECIAL '3'
#define BLOCK_SPECIAL '4'
#define DIRECTORY '5'
#define FIFO '6'
#define CONTIGUOUS_FILE '7'
#define GLOBAL_EXTENDET_HEADER 'g'
#define EXTENDED_HEADER 'x'
/*
*/
#define TAR_BLOCKSIZE 512
#define USTAR "ustar"
typedef struct tar_header {
union {
struct {
char filename[100];
char mode[8];
char uid[8];
char gid[8];
char size[12];
char mtime[12];
char chksum[8];
char type;
char link_filename[100];
char magic[6];
char version[2];
char owner[32];
char group[32];
char devicemajor[8];
char deviceminor[8];
char prefix[155];
};
char padding[TAR_BLOCKSIZE];
};
} tar_header;
typedef struct tar_entry {
struct tar_entry *next;
const char *filename;
off_t offset;
size_t size;
tar_header header;
} tar_entry;
typedef struct tar {
const char *filename; // tar filename
size_t size; // tar file size
tar_entry *files; // linked list
size_t fd;
} tar;
/*********************************************************************************************************/
/* FUNCTION DEFINITIONS */
/*********************************************************************************************************/
size_t RoundUpBlock(size_t size, size_t blksz);
/*********************************************************************************************************/
/* TAR HEADER INTERFACE */
/*********************************************************************************************************/
bool tar_header_isUstar(tar_header *header);
void tar_header_setEmpty(tar_header *header);
bool tar_header_isDirectory(tar_header *header);
bool tar_header_isRegFile(tar_header *header);
bool tar_header_isLongName(tar_header *header);
bool tar_header_isLongLink(tar_header *header);
size_t tar_header_getSize(tar_header *header);
void tar_header_getPath(tar_header *header, char *path_buf, size_t len);
/*********************************************************************************************************/
/* TAR FILE INTERFACE */
/*********************************************************************************************************/
tar_entry* tar_getNextHeader(tar *tar);
int tar_skipFileContent(tar *tar, size_t filesz);
#endif
tar_internal.c:
/*********************************************************************************************************/
/* DEPENDANICIES */
/*********************************************************************************************************/
#include "tar.h"
#include "tar_internal.h"
/*********************************************************************************************************/
/* DEFINITIONS */
/*********************************************************************************************************/
/*********************************************************************************************************/
/* FUNCTION DEFINITIONS */
/*********************************************************************************************************/
bool tar_header_isUstar(tar_header *header)
{
return ( strncmp(header->magic, USTAR, sizeof(USTAR)-1) == 0 );
}
size_t RoundUpBlock(size_t size, size_t blksz)
{
size_t result = size % blksz;
if( result == size ) {
return blksz;
}
else if( result == 0 ) {
return result;
}
return (size / blksz + 1) * blksz;
}
void tar_header_setEmpty(tar_header *head)
{
memset(head->padding, 0, TAR_BLOCKSIZE);
}
bool tar_header_isDirectory(tar_header *head)
{
return head->type == DIRECTORY;
}
bool tar_header_isRegFile(tar_header *head)
{
return head->type == NORMAL_FILE;
}
/*
always check errno for error after this call
*/
size_t tar_header_getSize(tar_header *head)
{
return strtoul(head->size, NULL, 8);
}
/**
* @brief skips the file content inside file and jumps to the next header
*
* @param tar tar*
* @param filesz size of file of the current header
* @retval -1 on failure
* @retval current offset in file in bytes
*/
int tar_skipFileContent(tar *tar, size_t filesz)
{
off_t curpos = lseek(tar->fd, 0, SEEK_CUR);
size_t roundup = RoundUpBlock(filesz, TAR_BLOCKSIZE);
if(curpos == -1) {
return -1;
}
if(curpos + roundup > tar->size) {
printf("DEBUG: TAR OVERREAD!!!\n");
return -1;
}
if(lseek(tar->fd, roundup, SEEK_CUR) == -1) {
return -1;
}
return curpos;
}
/**
* @brief Recusive call to fill the linked list of entries
* NOTE: alway check errno for this call. if errno == 0, and NULL is returned then EOF is reached, otherwise an error occured
* NOTE: this function checks for ustar magic string to skip 0-byte sequences at the end of file, it does not work for tar files using the old standard
* @param tar
* @retval NULL on failure (EOF or error while read)
* @retval tar_entry *
*/
tar_entry* tar_getNextHeader(tar *tar)
{
tar_entry *entry = malloc(sizeof *entry);
if(entry == NULL) {
return NULL;
}
size_t ret = read(tar->fd, &entry->header, TAR_BLOCKSIZE);
if(ret == 0 || ret == -1) {
goto CLEANUP;
}
if(!tar_header_isUstar(&entry->header)) {
goto CLEANUP;
}
entry->size = tar_header_getSize(&entry->header);
if(errno) {
perror("tar_getNextHeader()->tar_header_getSize()");
goto CLEANUP;
}
entry->offset = tar_skipFileContent(tar, entry->size);
if(entry->offset == -1) {
goto CLEANUP;
}
entry->next = tar_getNextHeader(tar);
if(errno) {
perror("tar_getNextHeader()->tar_getNextHeader()");
return NULL;
}
return entry;
CLEANUP:
free(entry);
return NULL;
}
strtoul()
forsize_t tar_header_getSize
vs saystrtoull()
or others? \$\endgroup\$