* Copyright 2017, Andrew Lindesay <apl@lindesay.co.nz>.
* All rights reserved. Distributed under the terms of the MIT License.
*/
#include "TarArchiveService.h"
#include <stdio.h>
#include <Directory.h>
#include <File.h>
#include <StringList.h>
#include "Logger.h"
#include "StorageUtils.h"
#define LENGTH_BLOCK 512
status_t
TarArchiveService::Unpack(BDataIO& tarDataIo, BPath& targetDirectory,
Stoppable* stoppable)
{
uint8 buffer[LENGTH_BLOCK];
uint8 zero_buffer[LENGTH_BLOCK];
status_t result = B_OK;
uint32_t count_items_read = 0;
fprintf(stdout, "will unpack to [%s]\n", targetDirectory.Path());
memset(zero_buffer, 0, sizeof zero_buffer);
while (B_OK == result
&& (NULL == stoppable || !stoppable->WasStopped())
&& B_OK == (result = tarDataIo.ReadExactly(buffer, LENGTH_BLOCK))) {
count_items_read++;
if (0 == memcmp(zero_buffer, buffer, sizeof zero_buffer)) {
fprintf(stdout, "detected end of tar-ball\n");
return B_OK;
} else {
TarArchiveHeader* header = TarArchiveHeader::CreateFromBlock(
buffer);
if (NULL == header) {
fprintf(stderr, "unable to parse a tar header\n");
result = B_ERROR;
}
if (B_OK == result)
result = _UnpackItem(tarDataIo, targetDirectory, *header);
delete header;
}
}
fprintf(stdout, "did unpack %d tar items\n", count_items_read);
if (B_OK != result) {
fprintf(stdout, "error occurred unpacking tar items; %s\n",
strerror(result));
}
return result;
}
status_t
TarArchiveService::_EnsurePathToTarItemFile(
BPath& targetDirectoryPath, BString &tarItemPath)
{
if (tarItemPath.Length() == 0)
return B_ERROR;
BStringList components;
tarItemPath.Split("/", false, components);
for (int32 i = 0; i < components.CountStrings(); i++) {
BString component = components.StringAt(i);
if (_ValidatePathComponent(component) != B_OK) {
fprintf(stdout, "malformed component; [%s]\n", component.String());
return B_ERROR;
}
}
BPath parentPath;
BPath assembledPath(targetDirectoryPath);
status_t result = assembledPath.Append(tarItemPath);
if (result == B_OK)
result = assembledPath.GetParent(&parentPath);
if (result == B_OK)
result = create_directory(parentPath.Path(), 0777);
return result;
}
status_t
TarArchiveService::_UnpackItem(BDataIO& tarDataIo,
BPath& targetDirectoryPath,
TarArchiveHeader& header)
{
status_t result = B_OK;
BString entryFileName = header.GetFileName();
uint32 entryLength = header.GetLength();
if (Logger::IsDebugEnabled()) {
fprintf(stdout, "will unpack item [%s] length [%" B_PRIu32 "]b\n",
entryFileName.String(), entryLength);
}
if (!entryFileName.EndsWith("/") ||
header.GetFileType() != TAR_FILE_TYPE_NORMAL) {
result = _EnsurePathToTarItemFile(targetDirectoryPath,
entryFileName);
if (result == B_OK) {
BPath targetFilePath(targetDirectoryPath);
targetFilePath.Append(entryFileName, false);
result = _UnpackItemData(tarDataIo, targetFilePath, entryLength);
}
} else {
off_t blocksToSkip = (entryLength / LENGTH_BLOCK);
if (entryLength % LENGTH_BLOCK > 0)
blocksToSkip += 1;
if (0 != blocksToSkip) {
uint8 buffer[LENGTH_BLOCK];
for (uint32 i = 0; B_OK == result && i < blocksToSkip; i++)
tarDataIo.ReadExactly(buffer, LENGTH_BLOCK);
}
}
return result;
}
status_t
TarArchiveService::_UnpackItemData(BDataIO& tarDataIo,
BPath& targetFilePath, uint32 length)
{
uint8 buffer[LENGTH_BLOCK];
size_t remainingInItem = length;
status_t result = B_OK;
BFile targetFile(targetFilePath.Path(), O_WRONLY | O_CREAT);
while (remainingInItem > 0 &&
B_OK == result &&
B_OK == (result = tarDataIo.ReadExactly(buffer, LENGTH_BLOCK))) {
size_t writeFromBuffer = LENGTH_BLOCK;
if (remainingInItem < LENGTH_BLOCK)
writeFromBuffer = remainingInItem;
result = targetFile.WriteExactly(buffer, writeFromBuffer);
remainingInItem -= writeFromBuffer;
}
if (result != B_OK)
fprintf(stdout, "unable to unpack item data to; [%s]\n",
targetFilePath.Path());
return result;
}
status_t
TarArchiveService::_ValidatePathComponent(const BString& component)
{
if (component.Length() == 0)
return B_ERROR;
if (component == ".." || component == "." || component == "~")
return B_ERROR;
return B_OK;
}