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