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