1 /* 2 * Copyright 2011-2013, Oliver Tappe <zooey@hirschkaefer.de> 3 * Distributed under the terms of the MIT License. 4 */ 5 6 7 #include <dirent.h> 8 #include <errno.h> 9 #include <getopt.h> 10 #include <stdio.h> 11 #include <stdlib.h> 12 #include <string.h> 13 14 #include <map> 15 16 #include <Entry.h> 17 #include <ObjectList.h> 18 #include <Path.h> 19 #include <String.h> 20 21 #include <package/hpkg/HPKGDefs.h> 22 #include <package/hpkg/PackageInfoAttributeValue.h> 23 #include <package/hpkg/RepositoryContentHandler.h> 24 #include <package/hpkg/RepositoryReader.h> 25 #include <package/hpkg/RepositoryWriter.h> 26 #include <package/hpkg/StandardErrorOutput.h> 27 #include <package/PackageInfo.h> 28 #include <package/PackageInfoContentHandler.h> 29 #include <package/RepositoryInfo.h> 30 31 #include "package_repo.h" 32 33 34 using BPackageKit::BHPKG::BRepositoryWriterListener; 35 using BPackageKit::BHPKG::BRepositoryWriter; 36 using namespace BPackageKit::BHPKG; 37 using namespace BPackageKit; 38 39 40 static bool sTrustFilenames = false; 41 42 43 bool operator< (const BPackageInfo & left, const BPackageInfo & right) 44 { 45 if (sTrustFilenames) 46 return left.FileName().Compare(right.FileName()) < 0; 47 48 if (left.Name() != right.Name()) 49 return left.Name() < right.Name(); 50 51 return left.Version().Compare(right.Version()) < 0; 52 } 53 54 55 namespace 56 { 57 58 59 typedef std::map<BPackageInfo, bool> PackageInfos; 60 61 62 status_t 63 parsePackageListFile(const char* packageListFileName, 64 BObjectList<BString>* packageFileNames) 65 { 66 FILE* packageListFile = fopen(packageListFileName, "r"); 67 if (packageListFile == NULL) { 68 printf("Error: Unable to open %s\n", packageListFileName); 69 return B_ENTRY_NOT_FOUND; 70 } 71 char buffer[128]; 72 while (fgets(buffer, sizeof(buffer), packageListFile) != NULL) { 73 BString* packageFileName = new(std::nothrow) BString(buffer); 74 if (packageFileName == NULL) { 75 printf("Error: Out of memory when reading from %s\n", 76 packageListFileName); 77 fclose(packageListFile); 78 return B_NO_MEMORY; 79 } 80 packageFileName->Trim(); 81 packageFileNames->AddItem(packageFileName); 82 } 83 fclose(packageListFile); 84 return B_OK; 85 } 86 87 88 struct PackageInfosCollector : BRepositoryContentHandler { 89 PackageInfosCollector(PackageInfos& packageInfos, 90 BHPKG::BErrorOutput* errorOutput) 91 : 92 fPackageInfos(packageInfos), 93 fErrorOutput(errorOutput), 94 fRepositoryInfo(), 95 fPackageInfo(), 96 fPackageInfoContentHandler(fPackageInfo, fErrorOutput) 97 { 98 } 99 100 virtual status_t HandlePackage(const char* packageName) 101 { 102 fPackageInfo.Clear(); 103 return B_OK; 104 } 105 106 virtual status_t HandlePackageAttribute( 107 const BPackageInfoAttributeValue& value) 108 { 109 return fPackageInfoContentHandler.HandlePackageAttribute(value); 110 } 111 112 virtual status_t HandlePackageDone(const char* packageName) 113 { 114 if (fPackageInfo.InitCheck() != B_OK) { 115 const BString& name = fPackageInfo.Name(); 116 printf("Error: package-info in repository for '%s' is incomplete\n", 117 name.IsEmpty() ? "<unknown-package>" : name.String()); 118 return B_BAD_DATA; 119 } 120 121 if (sTrustFilenames) { 122 // Cache the canonical name to avoid repeated fallbacks 123 fPackageInfo.SetFileName(fPackageInfo.CanonicalFileName()); 124 } 125 126 fPackageInfos[fPackageInfo] = false; 127 return B_OK; 128 } 129 130 virtual status_t HandleRepositoryInfo(const BRepositoryInfo& repositoryInfo) 131 { 132 fRepositoryInfo = repositoryInfo; 133 return B_OK; 134 } 135 136 virtual void HandleErrorOccurred() 137 { 138 } 139 140 const BRepositoryInfo& RepositoryInfo() const 141 { 142 return fRepositoryInfo; 143 } 144 145 private: 146 PackageInfos& fPackageInfos; 147 BHPKG::BErrorOutput* fErrorOutput; 148 BRepositoryInfo fRepositoryInfo; 149 BPackageInfo fPackageInfo; 150 BPackageInfoContentHandler fPackageInfoContentHandler; 151 }; 152 153 154 class RepositoryWriterListener : public BRepositoryWriterListener { 155 public: 156 RepositoryWriterListener(bool verbose, bool quiet) 157 : fVerbose(verbose), fQuiet(quiet) 158 { 159 } 160 161 virtual void PrintErrorVarArgs(const char* format, va_list args) 162 { 163 vfprintf(stderr, format, args); 164 } 165 166 virtual void OnPackageAdded(const BPackageInfo& packageInfo) 167 { 168 } 169 170 virtual void OnRepositoryInfoSectionDone(uint32 uncompressedSize) 171 { 172 if (fQuiet || !fVerbose) 173 return; 174 175 printf("----- Repository Info Section --------------------\n"); 176 printf("repository info size: %10" B_PRIu32 " (uncompressed)\n", 177 uncompressedSize); 178 } 179 180 virtual void OnPackageAttributesSectionDone(uint32 stringCount, 181 uint32 uncompressedSize) 182 { 183 if (fQuiet || !fVerbose) 184 return; 185 186 printf("----- Package Attribute Section -------------------\n"); 187 printf("string count: %10" B_PRIu32 "\n", stringCount); 188 printf("package attributes size: %10" B_PRIu32 " (uncompressed)\n", 189 uncompressedSize); 190 } 191 192 virtual void OnRepositoryDone(uint32 headerSize, uint32 repositoryInfoSize, 193 uint32 licenseCount, uint32 packageCount, uint32 packageAttributesSize, 194 uint64 totalSize) 195 { 196 if (fQuiet || !fVerbose) 197 return; 198 199 printf("----- Package Repository Info -----\n"); 200 if (fVerbose) 201 printf("embedded license count %10" B_PRIu32 "\n", licenseCount); 202 printf("package count %10" B_PRIu32 "\n", packageCount); 203 printf("-----------------------------------\n"); 204 printf("header size: %10" B_PRIu32 "\n", headerSize); 205 printf("repository header size: %10" B_PRIu32 "\n", 206 repositoryInfoSize); 207 printf("package attributes size: %10" B_PRIu32 "\n", 208 packageAttributesSize); 209 printf("total size: %10" B_PRIu64 "\n", totalSize); 210 printf("-----------------------------------\n"); 211 } 212 213 private: 214 bool fVerbose; 215 bool fQuiet; 216 }; 217 218 219 } // anonymous namespace 220 221 222 int 223 command_update(int argc, const char* const* argv) 224 { 225 const char* changeToDirectory = NULL; 226 bool quiet = false; 227 bool verbose = false; 228 229 while (true) { 230 static struct option sLongOptions[] = { 231 { "help", no_argument, 0, 'h' }, 232 { "quiet", no_argument, 0, 'q' }, 233 { "verbose", no_argument, 0, 'v' }, 234 { "trust-filenames", no_argument, 0, 't' }, 235 { 0, 0, 0, 0 } 236 }; 237 238 opterr = 0; // don't print errors 239 int c = getopt_long(argc, (char**)argv, "+C:hqvt", sLongOptions, NULL); 240 if (c == -1) 241 break; 242 243 switch (c) { 244 case 'C': 245 changeToDirectory = optarg; 246 break; 247 248 case 'h': 249 print_usage_and_exit(false); 250 break; 251 252 case 'q': 253 quiet = true; 254 break; 255 256 case 'v': 257 verbose = true; 258 break; 259 260 case 't': 261 sTrustFilenames = true; 262 break; 263 264 default: 265 print_usage_and_exit(true); 266 break; 267 } 268 } 269 270 // The remaining three arguments are the source and target repository file 271 // plus the package list file. 272 if (optind + 3 != argc) 273 print_usage_and_exit(true); 274 275 const char* sourceRepositoryFileName = argv[optind++]; 276 const char* targetRepositoryFileName = argv[optind++]; 277 const char* packageListFileName = argv[optind++]; 278 279 BStandardErrorOutput errorOutput; 280 RepositoryWriterListener listener(verbose, quiet); 281 282 BEntry sourceRepositoryEntry(sourceRepositoryFileName); 283 if (!sourceRepositoryEntry.Exists()) { 284 listener.PrintError( 285 "Error: given source repository file '%s' doesn't exist!\n", 286 sourceRepositoryFileName); 287 return 1; 288 } 289 // determine path for 'repo.info' file from given new repository file 290 BString repositoryInfoFileName(targetRepositoryFileName); 291 repositoryInfoFileName.Append(".info"); 292 BEntry repositoryInfoEntry(repositoryInfoFileName.String()); 293 BRepositoryInfo repositoryInfo(repositoryInfoEntry); 294 status_t result = repositoryInfo.InitCheck(); 295 if (result != B_OK) { 296 listener.PrintError( 297 "Error: can't parse/read repository-info file %s : %s\n", 298 repositoryInfoFileName.String(), strerror(result)); 299 return 1; 300 } 301 302 // open source repository 303 BRepositoryReader repositoryReader(&errorOutput); 304 result = repositoryReader.Init(sourceRepositoryFileName); 305 if (result != B_OK) { 306 listener.PrintError( 307 "Error: can't read from old repository file : %s\n", 308 strerror(result)); 309 return 1; 310 } 311 312 // collect package infos from source repository 313 PackageInfos packageInfos; 314 PackageInfosCollector packageInfosCollector(packageInfos, &errorOutput); 315 result = repositoryReader.ParseContent(&packageInfosCollector); 316 if (result != B_OK) { 317 listener.PrintError( 318 "Error: couldn't fetch package infos from old repository : %s\n", 319 strerror(result)); 320 return 1; 321 } 322 323 // create new repository 324 BRepositoryWriter repositoryWriter(&listener, &repositoryInfo); 325 BString tempRepositoryFileName(targetRepositoryFileName); 326 tempRepositoryFileName += ".___new___"; 327 if ((result = repositoryWriter.Init(tempRepositoryFileName.String())) 328 != B_OK) { 329 listener.PrintError("Error: can't initialize repository-writer : %s\n", 330 strerror(result)); 331 return 1; 332 } 333 334 BEntry tempRepositoryFile(tempRepositoryFileName.String()); 335 BPath targetRepositoryFilePath(targetRepositoryFileName); 336 337 BObjectList<BString> packageNames(100, true); 338 if ((result = parsePackageListFile(packageListFileName, &packageNames)) 339 != B_OK) { 340 listener.PrintError( 341 "Error: Failed to read package-list-file \"%s\": %s\n", 342 packageListFileName, strerror(result)); 343 return 1; 344 } 345 346 // change directory, if requested 347 if (changeToDirectory != NULL) { 348 if (chdir(changeToDirectory) != 0) { 349 listener.PrintError( 350 "Error: Failed to change the current working directory to " 351 "\"%s\": %s\n", changeToDirectory, strerror(errno)); 352 return 1; 353 } 354 } 355 356 // add all given package files 357 for (int i = 0; i < packageNames.CountItems(); ++i) { 358 BPackageInfo packageInfo; 359 360 if (sTrustFilenames) 361 packageInfo.SetFileName(*packageNames.ItemAt(i)); 362 else { 363 if ((result = packageInfo.ReadFromPackageFile( 364 packageNames.ItemAt(i)->String())) != B_OK) { 365 listener.PrintError( 366 "Error: Failed to read package-info from \"%s\": %s\n", 367 packageNames.ItemAt(i)->String(), strerror(result)); 368 return 1; 369 } 370 } 371 372 PackageInfos::iterator infoIter = packageInfos.find(packageInfo); 373 if (infoIter != packageInfos.end()) { 374 infoIter->second = true; 375 if ((result = repositoryWriter.AddPackageInfo(infoIter->first)) 376 != B_OK) 377 return 1; 378 if (verbose) { 379 printf("keeping '%s-%s'\n", infoIter->first.Name().String(), 380 infoIter->first.Version().ToString().String()); 381 } 382 } else { 383 BEntry entry(packageNames.ItemAt(i)->String()); 384 if ((result = repositoryWriter.AddPackage(entry)) != B_OK) 385 return 1; 386 if (!quiet) { 387 printf("added '%s' ...\n", 388 packageNames.ItemAt(i)->String()); 389 } 390 } 391 } 392 393 // tell about packages dropped from repository 394 PackageInfos::const_iterator infoIter; 395 for (infoIter = packageInfos.begin(); infoIter != packageInfos.end(); 396 ++infoIter) { 397 if (!infoIter->second) { 398 printf("dropped '%s-%s'\n", infoIter->first.Name().String(), 399 infoIter->first.Version().ToString().String()); 400 } 401 } 402 403 404 // write the repository 405 result = repositoryWriter.Finish(); 406 if (result != B_OK) 407 return 1; 408 409 result = tempRepositoryFile.Rename(targetRepositoryFilePath.Leaf(), true); 410 if (result != B_OK) { 411 printf("Error: unable to rename repository %s to %s - %s\n", 412 tempRepositoryFileName.String(), targetRepositoryFileName, 413 strerror(result)); 414 return 1; 415 } 416 417 if (verbose) { 418 printf("\nsuccessfully created repository '%s'\n", 419 targetRepositoryFileName); 420 } 421 422 return 0; 423 } 424