forked from xuos/xiuos
				
			
		
			
				
	
	
		
			468 lines
		
	
	
		
			11 KiB
		
	
	
	
		
			C
		
	
	
	
			
		
		
	
	
			468 lines
		
	
	
		
			11 KiB
		
	
	
	
		
			C
		
	
	
	
/*
 | 
						|
 * Copyright (c) 2020 AIIT XUOS Lab
 | 
						|
 * XiUOS is licensed under Mulan PSL v2.
 | 
						|
 * You can use this software according to the terms and conditions of the Mulan PSL v2.
 | 
						|
 * You may obtain a copy of Mulan PSL v2 at:
 | 
						|
 *        http://license.coscl.org.cn/MulanPSL2
 | 
						|
 * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
 | 
						|
 * EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
 | 
						|
 * MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
 | 
						|
 * See the Mulan PSL v2 for more details.
 | 
						|
*/
 | 
						|
 | 
						|
#include <xiuos.h>
 | 
						|
 | 
						|
#if defined(FS_VFS) && defined(TOOL_SHELL)
 | 
						|
 | 
						|
#include <iot-vfs_posix.h>
 | 
						|
#include <stdlib.h>
 | 
						|
#include <string.h>
 | 
						|
#include <stdint.h>
 | 
						|
#include <stdio.h>
 | 
						|
#include <ctype.h>
 | 
						|
#include "tar.h"
 | 
						|
#include "utility.h"
 | 
						|
 | 
						|
char *bytebuf;
 | 
						|
 | 
						|
static void PrintOctal(char *str, int len, uint64_t value)
 | 
						|
{
 | 
						|
    char buf[32];
 | 
						|
    char *cp;
 | 
						|
    int written_len;
 | 
						|
 | 
						|
    written_len = sprintf(buf, "%0*llo", len, value);
 | 
						|
    cp = buf + written_len - len;
 | 
						|
 | 
						|
    if (*cp == '0')
 | 
						|
        cp++;
 | 
						|
 | 
						|
    memcpy(str, cp, len);
 | 
						|
}
 | 
						|
 | 
						|
static uint64_t ScanOctal(char *str, int len)
 | 
						|
{
 | 
						|
    uint64_t ret = 0;
 | 
						|
 | 
						|
    for (int i = 0; i < len; i++) {
 | 
						|
        if (!isdigit(str[i]))
 | 
						|
            break;
 | 
						|
        if (str[i] > '7')
 | 
						|
            break;
 | 
						|
        ret = ret * 8 + str[i] - '0';
 | 
						|
    }
 | 
						|
 | 
						|
    return ret;
 | 
						|
}
 | 
						|
 | 
						|
static void TarChecksumHeader(TarHdr_t *hdr)
 | 
						|
{
 | 
						|
    int chksum;
 | 
						|
 | 
						|
    memcpy(hdr->magic, TAR_MAGIC_STR, 6);
 | 
						|
    memcpy(hdr->version, "00", 2);
 | 
						|
 | 
						|
    memset(hdr->chksum, ' ', sizeof(hdr->chksum));
 | 
						|
    chksum = 0;
 | 
						|
    for (int i = 0; i < sizeof(TarHdr_t); i++)
 | 
						|
        chksum += ((unsigned char *)hdr)[i];
 | 
						|
    PrintOctal(hdr->chksum, sizeof(hdr->chksum), chksum);
 | 
						|
}
 | 
						|
 | 
						|
static int TarWriteHeader(int tar_fd, struct stat *tar_statbuf,
 | 
						|
        char *file_name, char *hdr_file_name, struct stat *statbuf)
 | 
						|
{
 | 
						|
    TarHdr_t hdr;
 | 
						|
    int written_len;
 | 
						|
 | 
						|
    memset(&hdr, 0, sizeof(TarHdr_t));
 | 
						|
 | 
						|
    strncpy(hdr.name, hdr_file_name, sizeof(hdr.name));
 | 
						|
 | 
						|
    PrintOctal(hdr.mode, sizeof(hdr.mode), statbuf->st_mode & 07777);
 | 
						|
    PrintOctal(hdr.uid, sizeof(hdr.uid), 1);
 | 
						|
    PrintOctal(hdr.gid, sizeof(hdr.gid), 1);
 | 
						|
    PrintOctal(hdr.mtime, sizeof(hdr.mtime),
 | 
						|
            statbuf->st_mtime >= 0 ? statbuf->st_mtime : 0);
 | 
						|
    
 | 
						|
    strcpy(hdr.uname, "rtthread");
 | 
						|
    strcpy(hdr.gname, "rtthread");
 | 
						|
 | 
						|
    if (S_ISDIR(statbuf->st_mode)) {
 | 
						|
        hdr.typeflag = TYPE_DIR;
 | 
						|
        if (strlen(hdr.name) < sizeof(hdr.name) - 1)
 | 
						|
            hdr.name[strlen(hdr.name)] = '/';
 | 
						|
    } else if (S_ISREG(statbuf->st_mode)) {
 | 
						|
        hdr.typeflag = TYPE_REG;
 | 
						|
 | 
						|
        uint64_t size = statbuf->st_size;
 | 
						|
        if (size <= 0777777777777)
 | 
						|
            PrintOctal(hdr.size, sizeof(hdr.size), size);
 | 
						|
        else {
 | 
						|
            return -1;
 | 
						|
        }
 | 
						|
    } else {
 | 
						|
        return -1;
 | 
						|
    }
 | 
						|
 | 
						|
    TarChecksumHeader(&hdr);
 | 
						|
 | 
						|
    written_len = write(tar_fd, &hdr, sizeof(TarHdr_t));
 | 
						|
    if (written_len != sizeof(TarHdr_t))
 | 
						|
        return -1;
 | 
						|
 | 
						|
    return 0;
 | 
						|
}
 | 
						|
 | 
						|
static int TarWriteFileRecursive(int tar_fd,
 | 
						|
        struct stat *tar_statbuf, char *file_name, int verbose)
 | 
						|
{
 | 
						|
    char *hdr_file_name;
 | 
						|
    struct stat statbuf;
 | 
						|
 | 
						|
    hdr_file_name = RemoveUnsafePrefix(file_name);
 | 
						|
    if (hdr_file_name [0] == '\0')
 | 
						|
        return 0;
 | 
						|
 | 
						|
    stat(file_name, &statbuf);
 | 
						|
 | 
						|
 | 
						|
    if (TarWriteHeader(tar_fd, tar_statbuf, file_name,
 | 
						|
            hdr_file_name, &statbuf) < 0)
 | 
						|
        return -1;
 | 
						|
    
 | 
						|
    if (verbose)
 | 
						|
        KPrintf("%s\n", file_name);
 | 
						|
 | 
						|
    if (S_ISREG(statbuf.st_mode)) {
 | 
						|
        int fd;
 | 
						|
        size_t written_size = 0, copied_size;
 | 
						|
 | 
						|
        fd = open(file_name, O_RDONLY);
 | 
						|
        if (fd < 0)
 | 
						|
            return -1;
 | 
						|
 | 
						|
        while ((copied_size = read(fd, bytebuf, TAR_BLOCK_SIZE)) > 0) {
 | 
						|
            if (write(tar_fd, bytebuf, copied_size) != copied_size) {
 | 
						|
                copied_size = -1;
 | 
						|
                break;
 | 
						|
            }
 | 
						|
            written_size += copied_size;
 | 
						|
        }
 | 
						|
        if (copied_size < 0 || written_size != statbuf.st_size) {
 | 
						|
            close(fd);
 | 
						|
            return -1;
 | 
						|
        }
 | 
						|
 | 
						|
        copied_size = TAR_BLOCK_SIZE - (written_size % TAR_BLOCK_SIZE);
 | 
						|
        copied_size %= TAR_BLOCK_SIZE;
 | 
						|
        memset(bytebuf, 0, copied_size);
 | 
						|
        if (write(tar_fd, bytebuf, copied_size) != copied_size) {
 | 
						|
            close(fd);
 | 
						|
            return -1;
 | 
						|
        }
 | 
						|
 | 
						|
        close(fd);
 | 
						|
    } else {
 | 
						|
        DIR *dir;
 | 
						|
        struct dirent *dirent;
 | 
						|
 | 
						|
        dir = opendir(file_name);
 | 
						|
        if (dir == NULL)
 | 
						|
            return -1;
 | 
						|
 | 
						|
        while ((dirent = readdir(dir)) != NULL) {
 | 
						|
            char *sub_file_name;
 | 
						|
            int ret;
 | 
						|
            
 | 
						|
            sub_file_name = malloc(strlen(file_name) +
 | 
						|
                    strlen(dirent->d_name) + 2);
 | 
						|
            if (sub_file_name == NULL)
 | 
						|
                return -1;
 | 
						|
 | 
						|
            sprintf(sub_file_name, "%s/%s", file_name, dirent->d_name);
 | 
						|
            ret = TarWriteFileRecursive(tar_fd, tar_statbuf, sub_file_name, verbose);
 | 
						|
 | 
						|
            free(sub_file_name);
 | 
						|
 | 
						|
            if (ret < 0) {
 | 
						|
                closedir(dir);
 | 
						|
                return -1;
 | 
						|
            }
 | 
						|
        }
 | 
						|
        closedir(dir);
 | 
						|
    }
 | 
						|
 | 
						|
    return 0;
 | 
						|
}
 | 
						|
 | 
						|
static int TarCreate(char *tar_name, int npaths, char **paths, int verbose)
 | 
						|
{
 | 
						|
    int tar_fd, written_size;
 | 
						|
    struct stat tar_statbuf;
 | 
						|
 | 
						|
    if (npaths == 0)
 | 
						|
        return 0;
 | 
						|
 | 
						|
    tar_fd = open(tar_name, O_WRONLY | O_CREAT | O_TRUNC);
 | 
						|
    if (tar_fd < 0)
 | 
						|
        return -1;
 | 
						|
    fstat(tar_fd, &tar_statbuf);
 | 
						|
 | 
						|
    for (int i = 0; i < npaths; i++) {
 | 
						|
        int len = strlen(paths[i]);
 | 
						|
        if (len > 1 && paths[i][len - 1] == '/')
 | 
						|
            paths[len - 1] = '\0';
 | 
						|
 | 
						|
        if (TarWriteFileRecursive(tar_fd, &tar_statbuf, paths[i], verbose) < 0) {
 | 
						|
            close(tar_fd);
 | 
						|
            return -1;
 | 
						|
        }
 | 
						|
    }
 | 
						|
 | 
						|
    memset(bytebuf, 0, TAR_BLOCK_SIZE);
 | 
						|
    written_size = 0;
 | 
						|
    for (int i = 0; i < 2; i++)
 | 
						|
        written_size += write(tar_fd, bytebuf, TAR_BLOCK_SIZE);
 | 
						|
 | 
						|
    close(tar_fd);
 | 
						|
    
 | 
						|
    return written_size == 2 * TAR_BLOCK_SIZE ? 0 : -1;
 | 
						|
}
 | 
						|
 | 
						|
enum {
 | 
						|
    RET_SUCC = 0,
 | 
						|
    RET_EMPTY_HDR,
 | 
						|
    RET_EOF,
 | 
						|
};
 | 
						|
 | 
						|
static int TarParseHeader(int tar_fd, int verbose)
 | 
						|
{
 | 
						|
    int ReadSize;
 | 
						|
    TarHdr_t hdr;
 | 
						|
 | 
						|
 | 
						|
    ReadSize = read(tar_fd, bytebuf, TAR_BLOCK_SIZE);
 | 
						|
    if (ReadSize == 0)
 | 
						|
        return RET_EOF;
 | 
						|
    if (ReadSize != TAR_BLOCK_SIZE)
 | 
						|
        return -1;
 | 
						|
    
 | 
						|
    memcpy(&hdr, bytebuf, TAR_BLOCK_SIZE);
 | 
						|
 | 
						|
    if (hdr.name[0] == '\0')
 | 
						|
        return RET_EMPTY_HDR;
 | 
						|
 | 
						|
    if (!IsPrefixedWith(hdr.magic, TAR_MAGIC_STR))
 | 
						|
        return -1;
 | 
						|
 | 
						|
    int old_chksum = ScanOctal(hdr.chksum, sizeof(hdr.chksum));
 | 
						|
    int correct_chksum = 0;
 | 
						|
    memset(hdr.chksum, ' ', sizeof(hdr.chksum));
 | 
						|
    for (int i = 0; i < sizeof(TarHdr_t); i++)
 | 
						|
        correct_chksum += ((unsigned char *)&hdr)[i];
 | 
						|
    if (old_chksum != correct_chksum)
 | 
						|
        return -1;
 | 
						|
 | 
						|
    if (hdr.typeflag != TYPE_REG && hdr.typeflag != TYPE_DIR)
 | 
						|
        return -1;
 | 
						|
 | 
						|
    char *file_name = RemoveUnsafePrefix(hdr.name);
 | 
						|
 | 
						|
    if (verbose)
 | 
						|
        KPrintf("%s\n", file_name);
 | 
						|
 | 
						|
    if (hdr.typeflag == TYPE_DIR) {
 | 
						|
        if (mkdir(file_name, 0777) < 0)
 | 
						|
            return -1;
 | 
						|
        return RET_SUCC;
 | 
						|
    }
 | 
						|
 | 
						|
    int size_to_write = ScanOctal(hdr.size, sizeof(hdr.size));
 | 
						|
    int written_size;
 | 
						|
 | 
						|
    int fd = open(file_name, O_WRONLY | O_CREAT | O_TRUNC);
 | 
						|
    if (fd < 0)
 | 
						|
        return -1;
 | 
						|
 | 
						|
    while (size_to_write > 0) {
 | 
						|
        ReadSize = read(tar_fd, bytebuf, TAR_BLOCK_SIZE);
 | 
						|
        if (ReadSize != TAR_BLOCK_SIZE) {
 | 
						|
            close(fd);
 | 
						|
            return -1;
 | 
						|
        }
 | 
						|
 | 
						|
        written_size = write(fd, bytebuf, MIN(size_to_write, TAR_BLOCK_SIZE));
 | 
						|
        if (written_size != MIN(size_to_write, TAR_BLOCK_SIZE)) {
 | 
						|
            close(fd);
 | 
						|
            return -1;
 | 
						|
        }
 | 
						|
 | 
						|
        size_to_write -= TAR_BLOCK_SIZE;
 | 
						|
    }
 | 
						|
 | 
						|
    close(fd);
 | 
						|
 | 
						|
    return RET_SUCC;
 | 
						|
}
 | 
						|
 | 
						|
static int TarExtract(char *tar_name, int verbose)
 | 
						|
{
 | 
						|
    int tar_fd, ret, empty_hdrs = 0;
 | 
						|
    struct stat tar_statbuf;
 | 
						|
 | 
						|
    if (stat(tar_name, &tar_statbuf) < 0)
 | 
						|
        return -1;
 | 
						|
    if (tar_statbuf.st_size % TAR_BLOCK_SIZE != 0)
 | 
						|
        return -1;
 | 
						|
 | 
						|
    tar_fd = open(tar_name, O_RDONLY);
 | 
						|
    if (tar_fd < 0)
 | 
						|
        return -1;
 | 
						|
 | 
						|
    while ((ret = TarParseHeader(tar_fd, verbose)) >= 0) {
 | 
						|
        if (ret == RET_EOF) {
 | 
						|
            ret = 0;
 | 
						|
            break;
 | 
						|
        }
 | 
						|
        if (ret == RET_EMPTY_HDR) {
 | 
						|
            empty_hdrs++;
 | 
						|
            if (empty_hdrs >= 2) {
 | 
						|
                ret = 0;
 | 
						|
                break;
 | 
						|
            }
 | 
						|
        }
 | 
						|
        else
 | 
						|
            empty_hdrs = 0;
 | 
						|
    }
 | 
						|
    close(tar_fd);
 | 
						|
 | 
						|
    return ret;
 | 
						|
}
 | 
						|
 | 
						|
void TarTruncateCmpExtension(char *file_name, int filter)
 | 
						|
{
 | 
						|
    switch (filter) {
 | 
						|
    case GZIP:
 | 
						|
        TruncateExtension(file_name, ".gz");
 | 
						|
        break;
 | 
						|
    case BZIP2:
 | 
						|
        TruncateExtension(file_name, ".bz2");
 | 
						|
        break;
 | 
						|
    }
 | 
						|
}
 | 
						|
 | 
						|
extern int GzipCompress(char *file_name);
 | 
						|
extern int GzipDecompress(char *file_name);
 | 
						|
extern int Bzip2Compress(char *file_name);
 | 
						|
extern int Bzip2Decompress(char *file_name);
 | 
						|
 | 
						|
static void TarPrintUsage()
 | 
						|
{
 | 
						|
    KPrintf("Usage: tar [OPTIONS]... [FILES]...\n");
 | 
						|
    KPrintf("Supported option flags:\n");
 | 
						|
    KPrintf("    c   create a new tarball\n");
 | 
						|
    KPrintf("    x   extract files from an existing tarball\n");
 | 
						|
    KPrintf("    f   specify name of the tarball\n");
 | 
						|
    KPrintf("    v   show verbose status\n");
 | 
						|
    KPrintf("    z   filter the tarball through gzip\n");
 | 
						|
    KPrintf("    j   filter the tarball through bzip2\n");
 | 
						|
}
 | 
						|
 | 
						|
int tar(int argc, char **argv)
 | 
						|
{
 | 
						|
    int flag_create = 0, flag_extract = 0, flag_file = 0, verbose = 0;
 | 
						|
    int filter = 0;
 | 
						|
    int ret = 0;
 | 
						|
    char *tar_name;
 | 
						|
 | 
						|
    if (argc < 2 || argv[1][0] != '-') {
 | 
						|
        TarPrintUsage();
 | 
						|
        return 0;
 | 
						|
    }
 | 
						|
 | 
						|
    for (int i = 1; i < strlen(argv[1]); i++)
 | 
						|
        switch(argv[1][i]) {
 | 
						|
        case 'c':
 | 
						|
            flag_create = 1;
 | 
						|
            break;
 | 
						|
        case 'x':
 | 
						|
            flag_extract = 1;
 | 
						|
            break;
 | 
						|
        case 'f':
 | 
						|
            flag_file = 1;
 | 
						|
            break;
 | 
						|
        case 'z':
 | 
						|
            if (filter != 0) {
 | 
						|
                KPrintf("Bad options\n");
 | 
						|
                return 0;
 | 
						|
            }
 | 
						|
            filter = GZIP;
 | 
						|
            break;
 | 
						|
        case 'j':
 | 
						|
            if (filter != 0) {
 | 
						|
                KPrintf("Bad options\n");
 | 
						|
                return 0;
 | 
						|
            }
 | 
						|
            filter = BZIP2;
 | 
						|
            break;
 | 
						|
        case 'v':
 | 
						|
            verbose = 1;
 | 
						|
            break;
 | 
						|
        default:
 | 
						|
            KPrintf("Unknown option: %c\n", argv[1][i]);
 | 
						|
            return 0;
 | 
						|
        }
 | 
						|
    if (flag_create == flag_extract || flag_file == 0 || argc < 3) {
 | 
						|
        KPrintf("Bad options\n");
 | 
						|
        TarPrintUsage();
 | 
						|
        return 0;
 | 
						|
    }
 | 
						|
 | 
						|
    bytebuf = malloc(TAR_BLOCK_SIZE);
 | 
						|
 | 
						|
    tar_name = argv[2];
 | 
						|
    if (flag_create) {
 | 
						|
        TarTruncateCmpExtension(tar_name, filter);
 | 
						|
        ret = TarCreate(tar_name, argc - 3, &argv[3], verbose);
 | 
						|
        if (ret == 0) {
 | 
						|
            switch (filter) {
 | 
						|
            case GZIP:
 | 
						|
                ret = GzipCompress(tar_name);
 | 
						|
                break;
 | 
						|
            case BZIP2:
 | 
						|
                ret = Bzip2Compress(tar_name);
 | 
						|
                break;
 | 
						|
            }
 | 
						|
        }
 | 
						|
    } else if (flag_extract) {
 | 
						|
        if (argc > 3) {
 | 
						|
            KPrintf("Too many arguments\n");
 | 
						|
            ret = -1;
 | 
						|
        } else {
 | 
						|
            switch (filter) {
 | 
						|
            case GZIP:
 | 
						|
                ret = GzipDecompress(tar_name);
 | 
						|
                break;
 | 
						|
            case BZIP2:
 | 
						|
                ret = Bzip2Decompress(tar_name);
 | 
						|
            }
 | 
						|
            TarTruncateCmpExtension(tar_name, filter);
 | 
						|
            if (ret == 0) {
 | 
						|
                ret = TarExtract(tar_name, verbose);
 | 
						|
            }
 | 
						|
        }
 | 
						|
    }
 | 
						|
 | 
						|
    if (ret < 0)
 | 
						|
        KPrintf("Operation failed\n");
 | 
						|
 | 
						|
    free(bytebuf);
 | 
						|
 | 
						|
    return 0;
 | 
						|
}
 | 
						|
 | 
						|
 | 
						|
 | 
						|
#endif
 |