1 /* 2 * Copyright 2003-2009, Haiku. 3 * Distributed under the terms of the MIT License. 4 * 5 * Authors: 6 * Oliver Tappe, zooey@hirschkaefer.de 7 * Adrien Destugues, pulkomandy@gmail.com 8 */ 9 10 11 #include <memory> 12 #include <new> 13 #include <syslog.h> 14 15 #include <Application.h> 16 #include <DataIO.h> 17 #include <Directory.h> 18 #include <File.h> 19 #include <FindDirectory.h> 20 #include <fs_attr.h> 21 #include <Message.h> 22 #include <Mime.h> 23 #include <Path.h> 24 #include <Resources.h> 25 #include <Roster.h> 26 27 #include <DefaultCatalog.h> 28 #include <LocaleRoster.h> 29 30 #include <cstdio> 31 32 33 using std::auto_ptr; 34 using std::min; 35 using std::max; 36 using std::pair; 37 38 39 /* 40 * This file implements the default catalog-type for the opentracker locale 41 * kit. Alternatively, this could be used as a full add-on, but currently this 42 * is provided as part of liblocale.so. 43 */ 44 45 46 static const char *kCatFolder = "catalogs"; 47 static const char *kCatExtension = ".catalog"; 48 49 const char *DefaultCatalog::kCatMimeType 50 = "locale/x-vnd.Be.locale-catalog.default"; 51 52 static int16 kCatArchiveVersion = 1; 53 // version of the catalog archive structure, bump this if you change it! 54 55 56 /* 57 * constructs a DefaultCatalog with given signature and language and reads 58 * the catalog from disk. 59 * InitCheck() will be B_OK if catalog could be loaded successfully, it will 60 * give an appropriate error-code otherwise. 61 */ 62 DefaultCatalog::DefaultCatalog(const char *signature, const char *language, 63 uint32 fingerprint) 64 : 65 BHashMapCatalog(signature, language, fingerprint) 66 { 67 // give highest priority to catalog living in sub-folder of app's folder: 68 app_info appInfo; 69 be_app->GetAppInfo(&appInfo); 70 node_ref nref; 71 nref.device = appInfo.ref.device; 72 nref.node = appInfo.ref.directory; 73 BDirectory appDir(&nref); 74 BString catalogName("locale/"); 75 catalogName << kCatFolder 76 << "/" << fSignature 77 << "/" << fLanguageName 78 << kCatExtension; 79 BPath catalogPath(&appDir, catalogName.String()); 80 status_t status = ReadFromFile(catalogPath.Path()); 81 82 if (status != B_OK) { 83 // look in common-etc folder (/boot/home/config/etc): 84 BPath commonEtcPath; 85 find_directory(B_COMMON_ETC_DIRECTORY, &commonEtcPath); 86 if (commonEtcPath.InitCheck() == B_OK) { 87 catalogName = BString(commonEtcPath.Path()) 88 << "/locale/" << kCatFolder 89 << "/" << fSignature 90 << "/" << fLanguageName 91 << kCatExtension; 92 status = ReadFromFile(catalogName.String()); 93 } 94 } 95 96 if (status != B_OK) { 97 // look in system-etc folder (/boot/beos/etc): 98 BPath systemEtcPath; 99 find_directory(B_BEOS_ETC_DIRECTORY, &systemEtcPath); 100 if (systemEtcPath.InitCheck() == B_OK) { 101 catalogName = BString(systemEtcPath.Path()) 102 << "/locale/" << kCatFolder 103 << "/" << fSignature 104 << "/" << fLanguageName 105 << kCatExtension; 106 status = ReadFromFile(catalogName.String()); 107 } 108 } 109 110 fInitCheck = status; 111 log_team(LOG_DEBUG, 112 "trying to load default-catalog(sig=%s, lang=%s) results in %s", 113 signature, language, strerror(fInitCheck)); 114 } 115 116 117 /* 118 * constructs a DefaultCatalog and reads it from the resources of the 119 * given entry-ref (which usually is an app- or add-on-file). 120 * InitCheck() will be B_OK if catalog could be loaded successfully, it will 121 * give an appropriate error-code otherwise. 122 */ 123 DefaultCatalog::DefaultCatalog(entry_ref *appOrAddOnRef) 124 : 125 BHashMapCatalog("", "", 0) 126 { 127 fInitCheck = ReadFromResource(appOrAddOnRef); 128 log_team(LOG_DEBUG, 129 "trying to load embedded catalog from resources results in %s", 130 strerror(fInitCheck)); 131 } 132 133 134 /* 135 * constructs an empty DefaultCatalog with given sig and language. 136 * This is used for editing/testing purposes. 137 * InitCheck() will always be B_OK. 138 */ 139 DefaultCatalog::DefaultCatalog(const char *path, const char *signature, 140 const char *language) 141 : 142 BHashMapCatalog(signature, language, 0), 143 fPath(path) 144 { 145 fInitCheck = B_OK; 146 } 147 148 149 DefaultCatalog::~DefaultCatalog() 150 { 151 } 152 153 154 status_t 155 DefaultCatalog::ReadFromFile(const char *path) 156 { 157 if (!path) 158 path = fPath.String(); 159 160 BFile catalogFile; 161 status_t res = catalogFile.SetTo(path, B_READ_ONLY); 162 if (res != B_OK) { 163 log_team(LOG_DEBUG, "no catalog at %s", path); 164 return B_ENTRY_NOT_FOUND; 165 } 166 167 fPath = path; 168 log_team(LOG_DEBUG, "found catalog at %s", path); 169 170 off_t sz = 0; 171 res = catalogFile.GetSize(&sz); 172 if (res != B_OK) { 173 log_team(LOG_ERR, "couldn't get size for catalog-file %s", path); 174 return res; 175 } 176 177 auto_ptr<char> buf(new(std::nothrow) char [sz]); 178 if (buf.get() == NULL) { 179 log_team(LOG_ERR, "couldn't allocate array of %d chars", sz); 180 return B_NO_MEMORY; 181 } 182 res = catalogFile.Read(buf.get(), sz); 183 if (res < B_OK) { 184 log_team(LOG_ERR, "couldn't read from catalog-file %s", path); 185 return res; 186 } 187 if (res < sz) { 188 log_team(LOG_ERR, 189 "only got %lu instead of %Lu bytes from catalog-file %s", res, sz, 190 path); 191 return res; 192 } 193 BMemoryIO memIO(buf.get(), sz); 194 res = Unflatten(&memIO); 195 196 if (res == B_OK) { 197 // some information living in member variables needs to be copied 198 // to attributes. Although these attributes should have been written 199 // when creating the catalog, we make sure that they exist there: 200 UpdateAttributes(catalogFile); 201 } 202 203 return res; 204 } 205 206 207 /* 208 * this method is not currently being used, but it may be useful in the future... 209 */ 210 status_t 211 DefaultCatalog::ReadFromAttribute(entry_ref *appOrAddOnRef) 212 { 213 BNode node; 214 status_t res = node.SetTo(appOrAddOnRef); 215 if (res != B_OK) { 216 log_team(LOG_ERR, 217 "couldn't find app or add-on (dev=%lu, dir=%Lu, name=%s)", 218 appOrAddOnRef->device, appOrAddOnRef->directory, 219 appOrAddOnRef->name); 220 return B_ENTRY_NOT_FOUND; 221 } 222 223 log_team(LOG_DEBUG, 224 "looking for embedded catalog-attribute in app/add-on" 225 "(dev=%lu, dir=%Lu, name=%s)", appOrAddOnRef->device, 226 appOrAddOnRef->directory, appOrAddOnRef->name); 227 228 attr_info attrInfo; 229 res = node.GetAttrInfo(BLocaleRoster::kEmbeddedCatAttr, &attrInfo); 230 if (res != B_OK) { 231 log_team(LOG_DEBUG, "no embedded catalog found"); 232 return B_NAME_NOT_FOUND; 233 } 234 if (attrInfo.type != B_MESSAGE_TYPE) { 235 log_team(LOG_ERR, "attribute %s has incorrect type and is ignored!", 236 BLocaleRoster::kEmbeddedCatAttr); 237 return B_BAD_TYPE; 238 } 239 240 size_t size = attrInfo.size; 241 auto_ptr<char> buf(new(std::nothrow) char [size]); 242 if (buf.get() == NULL) { 243 log_team(LOG_ERR, "couldn't allocate array of %d chars", size); 244 return B_NO_MEMORY; 245 } 246 res = node.ReadAttr(BLocaleRoster::kEmbeddedCatAttr, B_MESSAGE_TYPE, 0, 247 buf.get(), size); 248 if (res < (ssize_t)size) { 249 log_team(LOG_ERR, "unable to read embedded catalog from attribute"); 250 return res < B_OK ? res : B_BAD_DATA; 251 } 252 253 BMemoryIO memIO(buf.get(), size); 254 res = Unflatten(&memIO); 255 256 return res; 257 } 258 259 260 status_t 261 DefaultCatalog::ReadFromResource(entry_ref *appOrAddOnRef) 262 { 263 BFile file; 264 status_t res = file.SetTo(appOrAddOnRef, B_READ_ONLY); 265 if (res != B_OK) { 266 log_team(LOG_ERR, 267 "couldn't find app or add-on (dev=%lu, dir=%Lu, name=%s)", 268 appOrAddOnRef->device, appOrAddOnRef->directory, 269 appOrAddOnRef->name); 270 return B_ENTRY_NOT_FOUND; 271 } 272 273 log_team(LOG_DEBUG, 274 "looking for embedded catalog-resource in app/add-on" 275 "(dev=%lu, dir=%Lu, name=%s)", appOrAddOnRef->device, 276 appOrAddOnRef->directory, appOrAddOnRef->name); 277 278 BResources rsrc; 279 res = rsrc.SetTo(&file); 280 if (res != B_OK) { 281 log_team(LOG_DEBUG, "file has no resources"); 282 return res; 283 } 284 285 size_t sz; 286 const void *buf = rsrc.LoadResource(B_MESSAGE_TYPE, 287 BLocaleRoster::kEmbeddedCatResId, &sz); 288 if (!buf) { 289 log_team(LOG_DEBUG, "file has no catalog-resource"); 290 return B_NAME_NOT_FOUND; 291 } 292 293 BMemoryIO memIO(buf, sz); 294 res = Unflatten(&memIO); 295 296 return res; 297 } 298 299 300 status_t 301 DefaultCatalog::WriteToFile(const char *path) 302 { 303 BFile catalogFile; 304 if (path) 305 fPath = path; 306 status_t res = catalogFile.SetTo(fPath.String(), 307 B_WRITE_ONLY | B_CREATE_FILE | B_ERASE_FILE); 308 if (res != B_OK) 309 return res; 310 311 BMallocIO mallocIO; 312 mallocIO.SetBlockSize(max(fCatMap.Size() * 20, 256L)); 313 // set a largish block-size in order to avoid reallocs 314 res = Flatten(&mallocIO); 315 if (res == B_OK) { 316 ssize_t wsz; 317 wsz = catalogFile.Write(mallocIO.Buffer(), mallocIO.BufferLength()); 318 if (wsz != (ssize_t)mallocIO.BufferLength()) 319 return B_FILE_ERROR; 320 321 // set mimetype-, language- and signature-attributes: 322 UpdateAttributes(catalogFile); 323 } 324 if (res == B_OK) 325 UpdateAttributes(catalogFile); 326 return res; 327 } 328 329 330 /* 331 * this method is not currently being used, but it may be useful in the 332 * future... 333 */ 334 status_t 335 DefaultCatalog::WriteToAttribute(entry_ref *appOrAddOnRef) 336 { 337 BNode node; 338 status_t res = node.SetTo(appOrAddOnRef); 339 if (res != B_OK) 340 return res; 341 342 BMallocIO mallocIO; 343 mallocIO.SetBlockSize(max(fCatMap.Size() * 20, 256L)); 344 // set a largish block-size in order to avoid reallocs 345 res = Flatten(&mallocIO); 346 347 if (res == B_OK) { 348 ssize_t wsz; 349 wsz = node.WriteAttr(BLocaleRoster::kEmbeddedCatAttr, B_MESSAGE_TYPE, 0, 350 mallocIO.Buffer(), mallocIO.BufferLength()); 351 if (wsz < B_OK) 352 res = wsz; 353 else if (wsz != (ssize_t)mallocIO.BufferLength()) 354 res = B_ERROR; 355 } 356 return res; 357 } 358 359 360 status_t 361 DefaultCatalog::WriteToResource(entry_ref *appOrAddOnRef) 362 { 363 BFile file; 364 status_t res = file.SetTo(appOrAddOnRef, B_READ_WRITE); 365 if (res != B_OK) 366 return res; 367 368 BResources rsrc; 369 res = rsrc.SetTo(&file); 370 if (res != B_OK) 371 return res; 372 373 BMallocIO mallocIO; 374 mallocIO.SetBlockSize(max(fCatMap.Size() * 20, 256L)); 375 // set a largish block-size in order to avoid reallocs 376 res = Flatten(&mallocIO); 377 378 if (res == B_OK) { 379 res = rsrc.AddResource(B_MESSAGE_TYPE, BLocaleRoster::kEmbeddedCatResId, 380 mallocIO.Buffer(), mallocIO.BufferLength(), "embedded catalog"); 381 } 382 383 return res; 384 } 385 386 387 /* 388 * writes mimetype, language-name and signature of catalog into the 389 * catalog-file. 390 */ 391 void 392 DefaultCatalog::UpdateAttributes(BFile& catalogFile) 393 { 394 static const int bufSize = 256; 395 char buf[bufSize]; 396 uint32 temp; 397 if (catalogFile.ReadAttr("BEOS:TYPE", B_MIME_STRING_TYPE, 0, &buf, 398 bufSize) <= 0 399 || strcmp(kCatMimeType, buf) != 0) { 400 catalogFile.WriteAttr("BEOS:TYPE", B_MIME_STRING_TYPE, 0, 401 kCatMimeType, strlen(kCatMimeType)+1); 402 } 403 if (catalogFile.ReadAttr(BLocaleRoster::kCatLangAttr, B_STRING_TYPE, 0, 404 &buf, bufSize) <= 0 405 || fLanguageName != buf) { 406 catalogFile.WriteAttr(BLocaleRoster::kCatLangAttr, B_STRING_TYPE, 0, 407 fLanguageName.String(), fLanguageName.Length()+1); 408 } 409 if (catalogFile.ReadAttr(BLocaleRoster::kCatSigAttr, B_STRING_TYPE, 0, 410 &buf, bufSize) <= 0 411 || fSignature != buf) { 412 catalogFile.WriteAttr(BLocaleRoster::kCatSigAttr, B_STRING_TYPE, 0, 413 fSignature.String(), fSignature.Length()+1); 414 } 415 if (catalogFile.ReadAttr(BLocaleRoster::kCatFingerprintAttr, B_UINT32_TYPE, 416 0, &temp, sizeof(uint32)) <= 0) { 417 catalogFile.WriteAttr(BLocaleRoster::kCatFingerprintAttr, B_UINT32_TYPE, 418 0, &fFingerprint, sizeof(uint32)); 419 } 420 } 421 422 423 status_t 424 DefaultCatalog::Flatten(BDataIO *dataIO) 425 { 426 UpdateFingerprint(); 427 // make sure we have the correct fingerprint before we flatten it 428 429 status_t res; 430 BMessage archive; 431 int32 count = fCatMap.Size(); 432 res = archive.AddString("class", "DefaultCatalog"); 433 if (res == B_OK) 434 res = archive.AddInt32("c:sz", count); 435 if (res == B_OK) 436 res = archive.AddInt16("c:ver", kCatArchiveVersion); 437 if (res == B_OK) 438 res = archive.AddString("c:lang", fLanguageName.String()); 439 if (res == B_OK) 440 res = archive.AddString("c:sig", fSignature.String()); 441 if (res == B_OK) 442 res = archive.AddInt32("c:fpr", fFingerprint); 443 if (res == B_OK) 444 res = archive.Flatten(dataIO); 445 446 CatMap::Iterator iter = fCatMap.GetIterator(); 447 CatMap::Entry entry; 448 while (res == B_OK && iter.HasNext()) { 449 entry = iter.Next(); 450 archive.MakeEmpty(); 451 res = archive.AddString("c:ostr", entry.key.fString.String()); 452 if (res == B_OK) 453 res = archive.AddString("c:ctxt", entry.key.fContext.String()); 454 if (res == B_OK) 455 res = archive.AddString("c:comt", entry.key.fComment.String()); 456 if (res == B_OK) 457 res = archive.AddInt32("c:hash", entry.key.fHashVal); 458 if (res == B_OK) 459 res = archive.AddString("c:tstr", entry.value.String()); 460 if (res == B_OK) 461 res = archive.Flatten(dataIO); 462 } 463 464 return res; 465 } 466 467 468 status_t 469 DefaultCatalog::Unflatten(BDataIO *dataIO) 470 { 471 fCatMap.Clear(); 472 int32 count = 0; 473 int16 version; 474 BMessage archiveMsg; 475 status_t res = archiveMsg.Unflatten(dataIO); 476 477 if (res == B_OK) { 478 res = archiveMsg.FindInt16("c:ver", &version) 479 || archiveMsg.FindInt32("c:sz", &count); 480 } 481 if (res == B_OK) { 482 fLanguageName = archiveMsg.FindString("c:lang"); 483 fSignature = archiveMsg.FindString("c:sig"); 484 uint32 foundFingerprint = archiveMsg.FindInt32("c:fpr"); 485 486 // if a specific fingerprint has been requested and the catalog does in 487 // fact have a fingerprint, both are compared. If they mismatch, we do 488 // not accept this catalog: 489 if (foundFingerprint != 0 && fFingerprint != 0 490 && foundFingerprint != fFingerprint) { 491 log_team(LOG_INFO, "default-catalog(sig=%s, lang=%s) " 492 "has mismatching fingerprint (%ld instead of the requested %ld), " 493 "so this catalog is skipped.", 494 fSignature.String(), fLanguageName.String(), foundFingerprint, 495 fFingerprint); 496 res = B_MISMATCHED_VALUES; 497 } else 498 fFingerprint = foundFingerprint; 499 } 500 501 if (res == B_OK && count > 0) { 502 CatKey key; 503 const char *keyStr; 504 const char *keyCtx; 505 const char *keyCmt; 506 const char *translated; 507 508 // fCatMap.resize(count); 509 // There is no resize method in Haiku's HashMap to preallocate 510 // memory. 511 for (int i=0; res == B_OK && i < count; ++i) { 512 res = archiveMsg.Unflatten(dataIO); 513 if (res == B_OK) 514 res = archiveMsg.FindString("c:ostr", &keyStr); 515 if (res == B_OK) 516 res = archiveMsg.FindString("c:ctxt", &keyCtx); 517 if (res == B_OK) 518 res = archiveMsg.FindString("c:comt", &keyCmt); 519 if (res == B_OK) 520 res = archiveMsg.FindInt32("c:hash", (int32*)&key.fHashVal); 521 if (res == B_OK) 522 res = archiveMsg.FindString("c:tstr", &translated); 523 if (res == B_OK) { 524 key.fString = keyStr; 525 key.fContext = keyCtx; 526 key.fComment = keyCmt; 527 fCatMap.Put(key, translated); 528 } 529 } 530 uint32 checkFP = ComputeFingerprint(); 531 if (fFingerprint != checkFP) { 532 log_team(LOG_WARNING, "default-catalog(sig=%s, lang=%s) " 533 "has wrong fingerprint after load (%ld instead of the %ld). " 534 "The catalog data may be corrupted, so this catalog is skipped.", 535 fSignature.String(), fLanguageName.String(), checkFP, 536 fFingerprint); 537 return B_BAD_DATA; 538 } 539 } 540 return res; 541 } 542 543 544 BCatalogAddOn * 545 DefaultCatalog::Instantiate(const char *signature, const char *language, 546 uint32 fingerprint) 547 { 548 DefaultCatalog *catalog 549 = new(std::nothrow) DefaultCatalog(signature, language, fingerprint); 550 if (catalog && catalog->InitCheck() != B_OK) { 551 delete catalog; 552 return NULL; 553 } 554 return catalog; 555 } 556 557 558 BCatalogAddOn * 559 DefaultCatalog::InstantiateEmbedded(entry_ref *appOrAddOnRef) 560 { 561 DefaultCatalog *catalog = new(std::nothrow) DefaultCatalog(appOrAddOnRef); 562 if (catalog && catalog->InitCheck() != B_OK) { 563 delete catalog; 564 return NULL; 565 } 566 return catalog; 567 } 568 569 570 BCatalogAddOn * 571 DefaultCatalog::Create(const char *signature, const char *language) 572 { 573 DefaultCatalog *catalog 574 = new(std::nothrow) DefaultCatalog("", signature, language); 575 if (catalog && catalog->InitCheck() != B_OK) { 576 delete catalog; 577 return NULL; 578 } 579 return catalog; 580 } 581 582 583 const uint8 DefaultCatalog::kDefaultCatalogAddOnPriority = 1; 584 // give highest priority to our embedded catalog-add-on 585