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