1 /* 2 * Copyright 2009, Ingo Weinhold, ingo_weinhold@gmx.de. 3 * Distributed under the terms of the MIT License. 4 */ 5 6 7 #include "package.h" 8 9 #include <ctype.h> 10 #include <fcntl.h> 11 #include <errno.h> 12 #include <getopt.h> 13 #include <stdio.h> 14 #include <stdlib.h> 15 #include <string.h> 16 #include <sys/stat.h> 17 #include <unistd.h> 18 19 #include <algorithm> 20 #include <new> 21 22 #include <fs_attr.h> 23 24 #include <AutoDeleter.h> 25 26 #include "BlockBufferCache.h" 27 #include "FDCloser.h" 28 #include "package.h" 29 #include "PackageDataReader.h" 30 #include "PackageEntry.h" 31 #include "PackageEntryAttribute.h" 32 #include "PackageReader.h" 33 #include "StandardErrorOutput.h" 34 35 36 struct PackageContentExtractHandler : PackageContentHandler { 37 PackageContentExtractHandler(int packageFileFD) 38 : 39 fBufferCache(B_HPKG_DEFAULT_DATA_CHUNK_SIZE_ZLIB, 2), 40 fPackageFileReader(packageFileFD), 41 fDataBuffer(NULL), 42 fDataBufferSize(0), 43 fErrorOccurred(false) 44 { 45 } 46 47 ~PackageContentExtractHandler() 48 { 49 free(fDataBuffer); 50 } 51 52 status_t Init() 53 { 54 status_t error = fBufferCache.Init(); 55 if (error != B_OK) 56 return error; 57 58 fDataBufferSize = 64 * 1024; 59 fDataBuffer = malloc(fDataBufferSize); 60 if (fDataBuffer == NULL) 61 return B_NO_MEMORY; 62 63 return B_OK; 64 } 65 66 virtual status_t HandleEntry(PackageEntry* entry) 67 { 68 // create a token 69 Token* token = new(std::nothrow) Token; 70 if (token == NULL) 71 return B_NO_MEMORY; 72 ObjectDeleter<Token> tokenDeleter(token); 73 74 // get parent FD 75 int parentFD = AT_FDCWD; 76 if (entry->Parent() != NULL) 77 parentFD = ((Token*)entry->Parent()->UserToken())->fd; 78 79 // check whether something is in the way 80 struct stat st; 81 bool entryExists = fstatat(parentFD, entry->Name(), &st, 82 AT_SYMLINK_NOFOLLOW) == 0; 83 if (entryExists) { 84 if (S_ISREG(entry->Mode()) || S_ISLNK(entry->Mode())) { 85 // If the entry in the way is a regular file or a symlink, 86 // remove it, otherwise fail. 87 if (!S_ISREG(st.st_mode) && !S_ISLNK(st.st_mode)) { 88 fprintf(stderr, "Error: Can't create entry \"%s\", since " 89 "something is in the way\n", entry->Name()); 90 return B_FILE_EXISTS; 91 } 92 93 if (unlinkat(parentFD, entry->Name(), 0) != 0) { 94 fprintf(stderr, "Error: Failed to unlink entry \"%s\": %s\n", 95 entry->Name(), strerror(errno)); 96 return errno; 97 } 98 99 entryExists = false; 100 } else if (S_ISDIR(entry->Mode())) { 101 // If the entry in the way is a directory, merge, otherwise 102 // fail. 103 if (!S_ISDIR(st.st_mode)) { 104 fprintf(stderr, "Error: Can't create directory \"%s\", " 105 "since something is in the way\n", entry->Name()); 106 return B_FILE_EXISTS; 107 } 108 } 109 } 110 111 // create the entry 112 int fd = -1; 113 if (S_ISREG(entry->Mode())) { 114 // create the file 115 fd = openat(parentFD, entry->Name(), O_RDWR | O_CREAT | O_EXCL, 116 entry->Mode() & ALLPERMS); 117 if (fd < 0) { 118 fprintf(stderr, "Error: Failed to create file \"%s\": %s\n", 119 entry->Name(), strerror(errno)); 120 return errno; 121 } 122 123 // write data 124 status_t error; 125 const PackageData& data = entry->Data(); 126 if (data.IsEncodedInline()) { 127 BufferDataReader dataReader(data.InlineData(), 128 data.CompressedSize()); 129 error = _ExtractFileData(&dataReader, data, fd); 130 } else 131 error = _ExtractFileData(&fPackageFileReader, data, fd); 132 133 if (error != B_OK) 134 return error; 135 } else if (S_ISLNK(entry->Mode())) { 136 // create the symlink 137 const char* symlinkPath = entry->SymlinkPath(); 138 if (symlinkat(symlinkPath != NULL ? symlinkPath : "", parentFD, 139 entry->Name()) != 0) { 140 fprintf(stderr, "Error: Failed to create symlink \"%s\": %s\n", 141 entry->Name(), strerror(errno)); 142 return errno; 143 } 144 // TODO: Set symlink permissions? 145 } else if (S_ISDIR(entry->Mode())) { 146 // create the directory, if necessary 147 if (!entryExists 148 && mkdirat(parentFD, entry->Name(), entry->Mode() & ALLPERMS) 149 != 0) { 150 fprintf(stderr, "Error: Failed to create directory \"%s\": " 151 "%s\n", entry->Name(), strerror(errno)); 152 return errno; 153 } 154 } else { 155 fprintf(stderr, "Error: Invalid file type for entry \"%s\"\n", 156 entry->Name()); 157 return B_BAD_DATA; 158 } 159 160 // If not done yet (symlink, dir), open the node -- we need the FD. 161 if (fd < 0) { 162 fd = openat(parentFD, entry->Name(), O_RDONLY | O_NOTRAVERSE); 163 if (fd < 0) { 164 fprintf(stderr, "Error: Failed to open entry \"%s\": %s\n", 165 entry->Name(), strerror(errno)); 166 return errno; 167 } 168 } 169 token->fd = fd; 170 171 // set the file times 172 if (!entryExists) { 173 timespec times[2] = {entry->AccessTime(), entry->ModifiedTime()}; 174 futimens(fd, times); 175 176 // set user/group 177 // TODO:... 178 } 179 180 entry->SetUserToken(tokenDeleter.Detach()); 181 return B_OK; 182 } 183 184 virtual status_t HandleEntryAttribute(PackageEntry* entry, 185 PackageEntryAttribute* attribute) 186 { 187 int entryFD = ((Token*)entry->UserToken())->fd; 188 189 // create the attribute 190 int fd = fs_fopen_attr(entryFD, attribute->Name(), attribute->Type(), 191 O_WRONLY | O_CREAT | O_TRUNC); 192 if (fd < 0) { 193 fprintf(stderr, "Error: Failed to create attribute \"%s\" of " 194 "file \"%s\": %s\n", attribute->Name(), entry->Name(), 195 strerror(errno)); 196 return errno; 197 } 198 FDCloser fdCloser(fd); 199 200 // write data 201 status_t error; 202 const PackageData& data = attribute->Data(); 203 if (data.IsEncodedInline()) { 204 BufferDataReader dataReader(data.InlineData(), 205 data.CompressedSize()); 206 error = _ExtractFileData(&dataReader, data, fd); 207 } else 208 error = _ExtractFileData(&fPackageFileReader, data, fd); 209 210 if (error != B_OK) 211 return error; 212 213 return B_OK; 214 } 215 216 virtual status_t HandleEntryDone(PackageEntry* entry) 217 { 218 if (Token* token = (Token*)entry->UserToken()) { 219 delete token; 220 entry->SetUserToken(NULL); 221 } 222 223 return B_OK; 224 } 225 226 virtual void HandleErrorOccurred() 227 { 228 fErrorOccurred = true; 229 } 230 231 private: 232 struct Token { 233 int fd; 234 235 Token() 236 : 237 fd(-1) 238 { 239 } 240 241 ~Token() 242 { 243 if (fd >= 0) 244 close(fd); 245 } 246 }; 247 248 private: 249 status_t _ExtractFileData(DataReader* dataReader, const PackageData& data, 250 int fd) 251 { 252 // create a PackageDataReader 253 PackageDataReader* reader; 254 status_t error = PackageDataReaderFactory(&fBufferCache) 255 .CreatePackageDataReader(dataReader, data, reader); 256 if (error != B_OK) 257 return error; 258 ObjectDeleter<PackageDataReader> readerDeleter(reader); 259 260 // write the data 261 off_t bytesRemaining = data.UncompressedSize(); 262 off_t offset = 0; 263 while (bytesRemaining > 0) { 264 // read 265 size_t toCopy = std::min((off_t)fDataBufferSize, bytesRemaining); 266 error = reader->ReadData(offset, fDataBuffer, toCopy); 267 if (error != B_OK) { 268 fprintf(stderr, "Error: Failed to read data: %s\n", 269 strerror(error)); 270 return error; 271 } 272 273 // write 274 ssize_t bytesWritten = pwrite(fd, fDataBuffer, toCopy, offset); 275 if (bytesWritten < 0) { 276 fprintf(stderr, "Error: Failed to write data: %s\n", 277 strerror(errno)); 278 return errno; 279 } 280 if ((size_t)bytesWritten != toCopy) { 281 fprintf(stderr, "Error: Failed to write all data\n"); 282 return B_ERROR; 283 } 284 285 offset += toCopy; 286 bytesRemaining -= toCopy; 287 } 288 289 return B_OK; 290 } 291 292 private: 293 BlockBufferCacheNoLock fBufferCache; 294 FDDataReader fPackageFileReader; 295 void* fDataBuffer; 296 size_t fDataBufferSize; 297 bool fErrorOccurred; 298 }; 299 300 301 int 302 command_extract(int argc, const char* const* argv) 303 { 304 const char* changeToDirectory = NULL; 305 306 while (true) { 307 static struct option sLongOptions[] = { 308 { "help", no_argument, 0, 'h' }, 309 { 0, 0, 0, 0 } 310 }; 311 312 opterr = 0; // don't print errors 313 int c = getopt_long(argc, (char**)argv, "+C:h", sLongOptions, NULL); 314 if (c == -1) 315 break; 316 317 switch (c) { 318 case 'C': 319 changeToDirectory = optarg; 320 break; 321 322 case 'h': 323 print_usage_and_exit(false); 324 break; 325 326 default: 327 print_usage_and_exit(true); 328 break; 329 } 330 } 331 332 // One argument should remain -- the package file name. 333 if (optind + 1 != argc) 334 print_usage_and_exit(true); 335 336 const char* packageFileName = argv[optind++]; 337 338 // open package 339 StandardErrorOutput errorOutput; 340 PackageReader packageReader(&errorOutput); 341 status_t error = packageReader.Init(packageFileName); 342 printf("Init(): %s\n", strerror(error)); 343 if (error != B_OK) 344 return 1; 345 346 // change directory, if requested 347 if (changeToDirectory != NULL) { 348 if (chdir(changeToDirectory) != 0) { 349 fprintf(stderr, "Error: Failed to change the current working " 350 "directory to \"%s\": %s\n", changeToDirectory, 351 strerror(errno)); 352 } 353 } 354 355 // extract 356 PackageContentExtractHandler handler(packageReader.PackageFileFD()); 357 error = handler.Init(); 358 if (error != B_OK) 359 return 1; 360 361 error = packageReader.ParseContent(&handler); 362 printf("ParseContent(): %s\n", strerror(error)); 363 if (error != B_OK) 364 return 1; 365 366 return 0; 367 } 368