1 /* 2 * Copyright 2017-2018, Andrew Lindesay <apl@lindesay.co.nz>. 3 * All rights reserved. Distributed under the terms of the MIT License. 4 */ 5 6 7 #include "TarArchiveService.h" 8 9 #include <stdio.h> 10 11 #include <Directory.h> 12 #include <File.h> 13 #include <StringList.h> 14 15 #include "Logger.h" 16 #include "StorageUtils.h" 17 18 19 #define LENGTH_BLOCK 512 20 21 22 status_t 23 TarArchiveService::Unpack(BDataIO& tarDataIo, BPath& targetDirectory, 24 Stoppable* stoppable) 25 { 26 uint8 buffer[LENGTH_BLOCK]; 27 uint8 zero_buffer[LENGTH_BLOCK]; 28 status_t result = B_OK; 29 uint32_t count_items_read = 0; 30 31 fprintf(stdout, "will unpack to [%s]\n", targetDirectory.Path()); 32 33 memset(zero_buffer, 0, sizeof zero_buffer); 34 35 while (B_OK == result 36 && (NULL == stoppable || !stoppable->WasStopped()) 37 && B_OK == (result = tarDataIo.ReadExactly(buffer, LENGTH_BLOCK))) { 38 39 count_items_read++; 40 41 if (0 == memcmp(zero_buffer, buffer, sizeof zero_buffer)) { 42 if (Logger::IsDebugEnabled()) 43 printf("detected end of tar-ball\n"); 44 return B_OK; // end of tar-ball. 45 } else { 46 TarArchiveHeader* header = TarArchiveHeader::CreateFromBlock( 47 buffer); 48 49 if (NULL == header) { 50 fprintf(stderr, "unable to parse a tar header\n"); 51 result = B_ERROR; 52 } 53 54 if (B_OK == result) 55 result = _UnpackItem(tarDataIo, targetDirectory, *header); 56 57 delete header; 58 } 59 } 60 61 fprintf(stdout, "did unpack %d tar items\n", count_items_read); 62 63 if (B_OK != result) { 64 fprintf(stdout, "error occurred unpacking tar items; %s\n", 65 strerror(result)); 66 } 67 68 return result; 69 } 70 71 72 status_t 73 TarArchiveService::_EnsurePathToTarItemFile( 74 BPath& targetDirectoryPath, BString &tarItemPath) 75 { 76 if (tarItemPath.Length() == 0) 77 return B_ERROR; 78 79 BStringList components; 80 tarItemPath.Split("/", false, components); 81 82 for (int32 i = 0; i < components.CountStrings(); i++) { 83 BString component = components.StringAt(i); 84 85 if (_ValidatePathComponent(component) != B_OK) { 86 fprintf(stdout, "malformed component; [%s]\n", component.String()); 87 return B_ERROR; 88 } 89 } 90 91 BPath parentPath; 92 BPath assembledPath(targetDirectoryPath); 93 94 status_t result = assembledPath.Append(tarItemPath); 95 96 if (result == B_OK) 97 result = assembledPath.GetParent(&parentPath); 98 99 if (result == B_OK) 100 result = create_directory(parentPath.Path(), 0777); 101 102 return result; 103 } 104 105 106 status_t 107 TarArchiveService::_UnpackItem(BDataIO& tarDataIo, 108 BPath& targetDirectoryPath, 109 TarArchiveHeader& header) 110 { 111 status_t result = B_OK; 112 BString entryFileName = header.GetFileName(); 113 uint32 entryLength = header.GetLength(); 114 115 if (Logger::IsDebugEnabled()) { 116 fprintf(stdout, "will unpack item [%s] length [%" B_PRIu32 "]b\n", 117 entryFileName.String(), entryLength); 118 } 119 120 // if the path ends in "/" then it is a directory and there's no need to 121 // unpack it although if there is a length, it will need to be skipped. 122 123 if (!entryFileName.EndsWith("/") || 124 header.GetFileType() != TAR_FILE_TYPE_NORMAL) { 125 126 result = _EnsurePathToTarItemFile(targetDirectoryPath, 127 entryFileName); 128 129 if (result == B_OK) { 130 BPath targetFilePath(targetDirectoryPath); 131 targetFilePath.Append(entryFileName, false); 132 result = _UnpackItemData(tarDataIo, targetFilePath, entryLength); 133 } 134 } else { 135 off_t blocksToSkip = (entryLength / LENGTH_BLOCK); 136 137 if (entryLength % LENGTH_BLOCK > 0) 138 blocksToSkip += 1; 139 140 if (0 != blocksToSkip) { 141 uint8 buffer[LENGTH_BLOCK]; 142 143 for (uint32 i = 0; B_OK == result && i < blocksToSkip; i++) 144 tarDataIo.ReadExactly(buffer, LENGTH_BLOCK); 145 } 146 } 147 148 return result; 149 } 150 151 152 status_t 153 TarArchiveService::_UnpackItemData(BDataIO& tarDataIo, 154 BPath& targetFilePath, uint32 length) 155 { 156 uint8 buffer[LENGTH_BLOCK]; 157 size_t remainingInItem = length; 158 status_t result = B_OK; 159 BFile targetFile(targetFilePath.Path(), O_WRONLY | O_CREAT); 160 161 while (remainingInItem > 0 && 162 B_OK == result && 163 B_OK == (result = tarDataIo.ReadExactly(buffer, LENGTH_BLOCK))) { 164 165 size_t writeFromBuffer = LENGTH_BLOCK; 166 167 if (remainingInItem < LENGTH_BLOCK) 168 writeFromBuffer = remainingInItem; 169 170 result = targetFile.WriteExactly(buffer, writeFromBuffer); 171 remainingInItem -= writeFromBuffer; 172 } 173 174 if (result != B_OK) 175 fprintf(stdout, "unable to unpack item data to; [%s]\n", 176 targetFilePath.Path()); 177 178 return result; 179 } 180 181 182 status_t 183 TarArchiveService::_ValidatePathComponent(const BString& component) 184 { 185 if (component.Length() == 0) 186 return B_ERROR; 187 188 if (component == ".." || component == "." || component == "~") 189 return B_ERROR; 190 191 return B_OK; 192 } 193 194