1 /* 2 * Copyright 2009, 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 close(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 = pwrite(fd, fDataBuffer, toCopy, offset); 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\n"); 289 return B_ERROR; 290 } 291 292 offset += toCopy; 293 bytesRemaining -= toCopy; 294 } 295 296 return B_OK; 297 } 298 299 private: 300 BBlockBufferCacheNoLock fBufferCache; 301 BFDDataReader fPackageFileReader; 302 void* fDataBuffer; 303 size_t fDataBufferSize; 304 bool fErrorOccurred; 305 }; 306 307 308 int 309 command_extract(int argc, const char* const* argv) 310 { 311 const char* changeToDirectory = NULL; 312 313 while (true) { 314 static struct option sLongOptions[] = { 315 { "help", no_argument, 0, 'h' }, 316 { 0, 0, 0, 0 } 317 }; 318 319 opterr = 0; // don't print errors 320 int c = getopt_long(argc, (char**)argv, "+C:h", sLongOptions, NULL); 321 if (c == -1) 322 break; 323 324 switch (c) { 325 case 'C': 326 changeToDirectory = optarg; 327 break; 328 329 case 'h': 330 print_usage_and_exit(false); 331 break; 332 333 default: 334 print_usage_and_exit(true); 335 break; 336 } 337 } 338 339 // One argument should remain -- the package file name. 340 if (optind + 1 != argc) 341 print_usage_and_exit(true); 342 343 const char* packageFileName = argv[optind++]; 344 345 // open package 346 StandardErrorOutput errorOutput; 347 BPackageReader packageReader(&errorOutput); 348 status_t error = packageReader.Init(packageFileName); 349 printf("Init(): %s\n", strerror(error)); 350 if (error != B_OK) 351 return 1; 352 353 // change directory, if requested 354 if (changeToDirectory != NULL) { 355 if (chdir(changeToDirectory) != 0) { 356 fprintf(stderr, "Error: Failed to change the current working " 357 "directory to \"%s\": %s\n", changeToDirectory, 358 strerror(errno)); 359 } 360 } 361 362 // extract 363 PackageContentExtractHandler handler(packageReader.PackageFileFD()); 364 error = handler.Init(); 365 if (error != B_OK) 366 return 1; 367 368 error = packageReader.ParseContent(&handler); 369 printf("ParseContent(): %s\n", strerror(error)); 370 if (error != B_OK) 371 return 1; 372 373 return 0; 374 } 375