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