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/FDCloser.h> 25 #include <package/hpkg/PackageContentHandler.h> 26 #include <package/hpkg/PackageDataReader.h> 27 #include <package/hpkg/PackageEntry.h> 28 #include <package/hpkg/PackageEntryAttribute.h> 29 #include <package/hpkg/PackageReader.h> 30 31 #include "BlockBufferCacheNoLock.h" 32 #include "package.h" 33 #include "StandardErrorOutput.h" 34 35 36 using namespace BPackageKit::BHPKG; 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 FDCloser fdCloser(fd); 202 203 // write data 204 status_t error; 205 const BPackageData& data = attribute->Data(); 206 if (data.IsEncodedInline()) { 207 BBufferDataReader dataReader(data.InlineData(), 208 data.CompressedSize()); 209 error = _ExtractFileData(&dataReader, data, fd); 210 } else 211 error = _ExtractFileData(&fPackageFileReader, data, fd); 212 213 if (error != B_OK) 214 return error; 215 216 return B_OK; 217 } 218 219 virtual status_t HandleEntryDone(BPackageEntry* entry) 220 { 221 if (Token* token = (Token*)entry->UserToken()) { 222 delete token; 223 entry->SetUserToken(NULL); 224 } 225 226 return B_OK; 227 } 228 229 virtual void HandleErrorOccurred() 230 { 231 fErrorOccurred = true; 232 } 233 234 private: 235 struct Token { 236 int fd; 237 238 Token() 239 : 240 fd(-1) 241 { 242 } 243 244 ~Token() 245 { 246 if (fd >= 0) 247 close(fd); 248 } 249 }; 250 251 private: 252 status_t _ExtractFileData(BDataReader* dataReader, const BPackageData& data, 253 int fd) 254 { 255 // create a BPackageDataReader 256 BPackageDataReader* reader; 257 status_t error = BPackageDataReaderFactory(&fBufferCache) 258 .CreatePackageDataReader(dataReader, data, reader); 259 if (error != B_OK) 260 return error; 261 ObjectDeleter<BPackageDataReader> readerDeleter(reader); 262 263 // write the data 264 off_t bytesRemaining = data.UncompressedSize(); 265 off_t offset = 0; 266 while (bytesRemaining > 0) { 267 // read 268 size_t toCopy = std::min((off_t)fDataBufferSize, bytesRemaining); 269 error = reader->ReadData(offset, fDataBuffer, toCopy); 270 if (error != B_OK) { 271 fprintf(stderr, "Error: Failed to read data: %s\n", 272 strerror(error)); 273 return error; 274 } 275 276 // write 277 ssize_t bytesWritten = pwrite(fd, fDataBuffer, toCopy, offset); 278 if (bytesWritten < 0) { 279 fprintf(stderr, "Error: Failed to write data: %s\n", 280 strerror(errno)); 281 return errno; 282 } 283 if ((size_t)bytesWritten != toCopy) { 284 fprintf(stderr, "Error: Failed to write all data\n"); 285 return B_ERROR; 286 } 287 288 offset += toCopy; 289 bytesRemaining -= toCopy; 290 } 291 292 return B_OK; 293 } 294 295 private: 296 BlockBufferCacheNoLock fBufferCache; 297 BFDDataReader fPackageFileReader; 298 void* fDataBuffer; 299 size_t fDataBufferSize; 300 bool fErrorOccurred; 301 }; 302 303 304 int 305 command_extract(int argc, const char* const* argv) 306 { 307 const char* changeToDirectory = NULL; 308 309 while (true) { 310 static struct option sLongOptions[] = { 311 { "help", no_argument, 0, 'h' }, 312 { 0, 0, 0, 0 } 313 }; 314 315 opterr = 0; // don't print errors 316 int c = getopt_long(argc, (char**)argv, "+C:h", sLongOptions, NULL); 317 if (c == -1) 318 break; 319 320 switch (c) { 321 case 'C': 322 changeToDirectory = optarg; 323 break; 324 325 case 'h': 326 print_usage_and_exit(false); 327 break; 328 329 default: 330 print_usage_and_exit(true); 331 break; 332 } 333 } 334 335 // One argument should remain -- the package file name. 336 if (optind + 1 != argc) 337 print_usage_and_exit(true); 338 339 const char* packageFileName = argv[optind++]; 340 341 // open package 342 StandardErrorOutput errorOutput; 343 BPackageReader packageReader(&errorOutput); 344 status_t error = packageReader.Init(packageFileName); 345 printf("Init(): %s\n", strerror(error)); 346 if (error != B_OK) 347 return 1; 348 349 // change directory, if requested 350 if (changeToDirectory != NULL) { 351 if (chdir(changeToDirectory) != 0) { 352 fprintf(stderr, "Error: Failed to change the current working " 353 "directory to \"%s\": %s\n", changeToDirectory, 354 strerror(errno)); 355 } 356 } 357 358 // extract 359 PackageContentExtractHandler handler(packageReader.PackageFileFD()); 360 error = handler.Init(); 361 if (error != B_OK) 362 return 1; 363 364 error = packageReader.ParseContent(&handler); 365 printf("ParseContent(): %s\n", strerror(error)); 366 if (error != B_OK) 367 return 1; 368 369 return 0; 370 } 371