xiuos/Ubiquitous/XiZi_IIoT/tool/shell/letter-shell/file_ext/tar.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 <xizi.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*lo", 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