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, "LocaleKit DefaultCatalog: no catalog at %s", path); 164 return B_ENTRY_NOT_FOUND; 165 } 166 167 fPath = path; 168 log_team(LOG_DEBUG, "LocaleKit DefaultCatalog: 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, "LocaleKit DefaultCatalog: couldn't get size for " 174 "catalog-file %s", path); 175 return res; 176 } 177 178 auto_ptr<char> buf(new(std::nothrow) char [sz]); 179 if (buf.get() == NULL) { 180 log_team(LOG_ERR, "LocaleKit DefaultCatalog: couldn't allocate array " 181 "of %d chars", sz); 182 return B_NO_MEMORY; 183 } 184 res = catalogFile.Read(buf.get(), sz); 185 if (res < B_OK) { 186 log_team(LOG_ERR, "LocaleKit DefaultCatalog: couldn't read from " 187 "catalog-file %s", path); 188 return res; 189 } 190 if (res < sz) { 191 log_team(LOG_ERR, 192 "LocaleKit DefaultCatalog: only got %lu instead of %Lu bytes from " 193 "catalog-file %s", res, sz, path); 194 return res; 195 } 196 BMemoryIO memIO(buf.get(), sz); 197 res = Unflatten(&memIO); 198 199 if (res == B_OK) { 200 // some information living in member variables needs to be copied 201 // to attributes. Although these attributes should have been written 202 // when creating the catalog, we make sure that they exist there: 203 UpdateAttributes(catalogFile); 204 } 205 206 return res; 207 } 208 209 210 /* 211 * this method is not currently being used, but it may be useful in the future... 212 */ 213 status_t 214 DefaultCatalog::ReadFromAttribute(entry_ref *appOrAddOnRef) 215 { 216 BNode node; 217 status_t res = node.SetTo(appOrAddOnRef); 218 if (res != B_OK) { 219 log_team(LOG_ERR, 220 "couldn't find app or add-on (dev=%lu, dir=%Lu, name=%s)", 221 appOrAddOnRef->device, appOrAddOnRef->directory, 222 appOrAddOnRef->name); 223 return B_ENTRY_NOT_FOUND; 224 } 225 226 log_team(LOG_DEBUG, 227 "looking for embedded catalog-attribute in app/add-on" 228 "(dev=%lu, dir=%Lu, name=%s)", appOrAddOnRef->device, 229 appOrAddOnRef->directory, appOrAddOnRef->name); 230 231 attr_info attrInfo; 232 res = node.GetAttrInfo(BLocaleRoster::kEmbeddedCatAttr, &attrInfo); 233 if (res != B_OK) { 234 log_team(LOG_DEBUG, "no embedded catalog found"); 235 return B_NAME_NOT_FOUND; 236 } 237 if (attrInfo.type != B_MESSAGE_TYPE) { 238 log_team(LOG_ERR, "attribute %s has incorrect type and is ignored!", 239 BLocaleRoster::kEmbeddedCatAttr); 240 return B_BAD_TYPE; 241 } 242 243 size_t size = attrInfo.size; 244 auto_ptr<char> buf(new(std::nothrow) char [size]); 245 if (buf.get() == NULL) { 246 log_team(LOG_ERR, "couldn't allocate array of %d chars", size); 247 return B_NO_MEMORY; 248 } 249 res = node.ReadAttr(BLocaleRoster::kEmbeddedCatAttr, B_MESSAGE_TYPE, 0, 250 buf.get(), size); 251 if (res < (ssize_t)size) { 252 log_team(LOG_ERR, "unable to read embedded catalog from attribute"); 253 return res < B_OK ? res : B_BAD_DATA; 254 } 255 256 BMemoryIO memIO(buf.get(), size); 257 res = Unflatten(&memIO); 258 259 return res; 260 } 261 262 263 status_t 264 DefaultCatalog::ReadFromResource(entry_ref *appOrAddOnRef) 265 { 266 BFile file; 267 status_t res = file.SetTo(appOrAddOnRef, B_READ_ONLY); 268 if (res != B_OK) { 269 log_team(LOG_ERR, 270 "couldn't find app or add-on (dev=%lu, dir=%Lu, name=%s)", 271 appOrAddOnRef->device, appOrAddOnRef->directory, 272 appOrAddOnRef->name); 273 return B_ENTRY_NOT_FOUND; 274 } 275 276 log_team(LOG_DEBUG, 277 "looking for embedded catalog-resource in app/add-on" 278 "(dev=%lu, dir=%Lu, name=%s)", appOrAddOnRef->device, 279 appOrAddOnRef->directory, appOrAddOnRef->name); 280 281 BResources rsrc; 282 res = rsrc.SetTo(&file); 283 if (res != B_OK) { 284 log_team(LOG_DEBUG, "file has no resources"); 285 return res; 286 } 287 288 size_t sz; 289 const void *buf = rsrc.LoadResource(B_MESSAGE_TYPE, 290 BLocaleRoster::kEmbeddedCatResId, &sz); 291 if (!buf) { 292 log_team(LOG_DEBUG, "file has no catalog-resource"); 293 return B_NAME_NOT_FOUND; 294 } 295 296 BMemoryIO memIO(buf, sz); 297 res = Unflatten(&memIO); 298 299 return res; 300 } 301 302 303 status_t 304 DefaultCatalog::WriteToFile(const char *path) 305 { 306 BFile catalogFile; 307 if (path) 308 fPath = path; 309 status_t res = catalogFile.SetTo(fPath.String(), 310 B_WRITE_ONLY | B_CREATE_FILE | B_ERASE_FILE); 311 if (res != B_OK) 312 return res; 313 314 BMallocIO mallocIO; 315 mallocIO.SetBlockSize(max(fCatMap.Size() * 20, 256L)); 316 // set a largish block-size in order to avoid reallocs 317 res = Flatten(&mallocIO); 318 if (res == B_OK) { 319 ssize_t wsz; 320 wsz = catalogFile.Write(mallocIO.Buffer(), mallocIO.BufferLength()); 321 if (wsz != (ssize_t)mallocIO.BufferLength()) 322 return B_FILE_ERROR; 323 324 // set mimetype-, language- and signature-attributes: 325 UpdateAttributes(catalogFile); 326 } 327 if (res == B_OK) 328 UpdateAttributes(catalogFile); 329 return res; 330 } 331 332 333 /* 334 * this method is not currently being used, but it may be useful in the 335 * future... 336 */ 337 status_t 338 DefaultCatalog::WriteToAttribute(entry_ref *appOrAddOnRef) 339 { 340 BNode node; 341 status_t res = node.SetTo(appOrAddOnRef); 342 if (res != B_OK) 343 return res; 344 345 BMallocIO mallocIO; 346 mallocIO.SetBlockSize(max(fCatMap.Size() * 20, 256L)); 347 // set a largish block-size in order to avoid reallocs 348 res = Flatten(&mallocIO); 349 350 if (res == B_OK) { 351 ssize_t wsz; 352 wsz = node.WriteAttr(BLocaleRoster::kEmbeddedCatAttr, B_MESSAGE_TYPE, 0, 353 mallocIO.Buffer(), mallocIO.BufferLength()); 354 if (wsz < B_OK) 355 res = wsz; 356 else if (wsz != (ssize_t)mallocIO.BufferLength()) 357 res = B_ERROR; 358 } 359 return res; 360 } 361 362 363 status_t 364 DefaultCatalog::WriteToResource(entry_ref *appOrAddOnRef) 365 { 366 BFile file; 367 status_t res = file.SetTo(appOrAddOnRef, B_READ_WRITE); 368 if (res != B_OK) 369 return res; 370 371 BResources rsrc; 372 res = rsrc.SetTo(&file); 373 if (res != B_OK) 374 return res; 375 376 BMallocIO mallocIO; 377 mallocIO.SetBlockSize(max(fCatMap.Size() * 20, 256L)); 378 // set a largish block-size in order to avoid reallocs 379 res = Flatten(&mallocIO); 380 381 if (res == B_OK) { 382 res = rsrc.AddResource(B_MESSAGE_TYPE, BLocaleRoster::kEmbeddedCatResId, 383 mallocIO.Buffer(), mallocIO.BufferLength(), "embedded catalog"); 384 } 385 386 return res; 387 } 388 389 390 /* 391 * writes mimetype, language-name and signature of catalog into the 392 * catalog-file. 393 */ 394 void 395 DefaultCatalog::UpdateAttributes(BFile& catalogFile) 396 { 397 static const int bufSize = 256; 398 char buf[bufSize]; 399 uint32 temp; 400 if (catalogFile.ReadAttr("BEOS:TYPE", B_MIME_STRING_TYPE, 0, &buf, 401 bufSize) <= 0 402 || strcmp(kCatMimeType, buf) != 0) { 403 catalogFile.WriteAttr("BEOS:TYPE", B_MIME_STRING_TYPE, 0, 404 kCatMimeType, strlen(kCatMimeType)+1); 405 } 406 if (catalogFile.ReadAttr(BLocaleRoster::kCatLangAttr, B_STRING_TYPE, 0, 407 &buf, bufSize) <= 0 408 || fLanguageName != buf) { 409 catalogFile.WriteAttr(BLocaleRoster::kCatLangAttr, B_STRING_TYPE, 0, 410 fLanguageName.String(), fLanguageName.Length()+1); 411 } 412 if (catalogFile.ReadAttr(BLocaleRoster::kCatSigAttr, B_STRING_TYPE, 0, 413 &buf, bufSize) <= 0 414 || fSignature != buf) { 415 catalogFile.WriteAttr(BLocaleRoster::kCatSigAttr, B_STRING_TYPE, 0, 416 fSignature.String(), fSignature.Length()+1); 417 } 418 if (catalogFile.ReadAttr(BLocaleRoster::kCatFingerprintAttr, B_UINT32_TYPE, 419 0, &temp, sizeof(uint32)) <= 0) { 420 catalogFile.WriteAttr(BLocaleRoster::kCatFingerprintAttr, B_UINT32_TYPE, 421 0, &fFingerprint, sizeof(uint32)); 422 } 423 } 424 425 426 status_t 427 DefaultCatalog::Flatten(BDataIO *dataIO) 428 { 429 UpdateFingerprint(); 430 // make sure we have the correct fingerprint before we flatten it 431 432 status_t res; 433 BMessage archive; 434 int32 count = fCatMap.Size(); 435 res = archive.AddString("class", "DefaultCatalog"); 436 if (res == B_OK) 437 res = archive.AddInt32("c:sz", count); 438 if (res == B_OK) 439 res = archive.AddInt16("c:ver", kCatArchiveVersion); 440 if (res == B_OK) 441 res = archive.AddString("c:lang", fLanguageName.String()); 442 if (res == B_OK) 443 res = archive.AddString("c:sig", fSignature.String()); 444 if (res == B_OK) 445 res = archive.AddInt32("c:fpr", fFingerprint); 446 if (res == B_OK) 447 res = archive.Flatten(dataIO); 448 449 CatMap::Iterator iter = fCatMap.GetIterator(); 450 CatMap::Entry entry; 451 while (res == B_OK && iter.HasNext()) { 452 entry = iter.Next(); 453 archive.MakeEmpty(); 454 res = archive.AddString("c:ostr", entry.key.fString.String()); 455 if (res == B_OK) 456 res = archive.AddString("c:ctxt", entry.key.fContext.String()); 457 if (res == B_OK) 458 res = archive.AddString("c:comt", entry.key.fComment.String()); 459 if (res == B_OK) 460 res = archive.AddInt32("c:hash", entry.key.fHashVal); 461 if (res == B_OK) 462 res = archive.AddString("c:tstr", entry.value.String()); 463 if (res == B_OK) 464 res = archive.Flatten(dataIO); 465 } 466 467 return res; 468 } 469 470 471 status_t 472 DefaultCatalog::Unflatten(BDataIO *dataIO) 473 { 474 fCatMap.Clear(); 475 int32 count = 0; 476 int16 version; 477 BMessage archiveMsg; 478 status_t res = archiveMsg.Unflatten(dataIO); 479 480 if (res == B_OK) { 481 res = archiveMsg.FindInt16("c:ver", &version) 482 || archiveMsg.FindInt32("c:sz", &count); 483 } 484 if (res == B_OK) { 485 fLanguageName = archiveMsg.FindString("c:lang"); 486 fSignature = archiveMsg.FindString("c:sig"); 487 uint32 foundFingerprint = archiveMsg.FindInt32("c:fpr"); 488 489 // if a specific fingerprint has been requested and the catalog does in 490 // fact have a fingerprint, both are compared. If they mismatch, we do 491 // not accept this catalog: 492 if (foundFingerprint != 0 && fFingerprint != 0 493 && foundFingerprint != fFingerprint) { 494 log_team(LOG_INFO, "default-catalog(sig=%s, lang=%s) " 495 "has mismatching fingerprint (%ld instead of the requested %ld), " 496 "so this catalog is skipped.", 497 fSignature.String(), fLanguageName.String(), foundFingerprint, 498 fFingerprint); 499 res = B_MISMATCHED_VALUES; 500 } else 501 fFingerprint = foundFingerprint; 502 } 503 504 if (res == B_OK && count > 0) { 505 CatKey key; 506 const char *keyStr; 507 const char *keyCtx; 508 const char *keyCmt; 509 const char *translated; 510 511 // fCatMap.resize(count); 512 // There is no resize method in Haiku's HashMap to preallocate 513 // memory. 514 for (int i=0; res == B_OK && i < count; ++i) { 515 res = archiveMsg.Unflatten(dataIO); 516 if (res == B_OK) 517 res = archiveMsg.FindString("c:ostr", &keyStr); 518 if (res == B_OK) 519 res = archiveMsg.FindString("c:ctxt", &keyCtx); 520 if (res == B_OK) 521 res = archiveMsg.FindString("c:comt", &keyCmt); 522 if (res == B_OK) 523 res = archiveMsg.FindInt32("c:hash", (int32*)&key.fHashVal); 524 if (res == B_OK) 525 res = archiveMsg.FindString("c:tstr", &translated); 526 if (res == B_OK) { 527 key.fString = keyStr; 528 key.fContext = keyCtx; 529 key.fComment = keyCmt; 530 fCatMap.Put(key, translated); 531 } 532 } 533 uint32 checkFP = ComputeFingerprint(); 534 if (fFingerprint != checkFP) { 535 log_team(LOG_WARNING, "default-catalog(sig=%s, lang=%s) " 536 "has wrong fingerprint after load (%ld instead of the %ld). " 537 "The catalog data may be corrupted, so this catalog is skipped.", 538 fSignature.String(), fLanguageName.String(), checkFP, 539 fFingerprint); 540 return B_BAD_DATA; 541 } 542 } 543 return res; 544 } 545 546 547 BCatalogAddOn * 548 DefaultCatalog::Instantiate(const char *signature, const char *language, 549 uint32 fingerprint) 550 { 551 DefaultCatalog *catalog 552 = new(std::nothrow) DefaultCatalog(signature, language, fingerprint); 553 if (catalog && catalog->InitCheck() != B_OK) { 554 delete catalog; 555 return NULL; 556 } 557 return catalog; 558 } 559 560 561 BCatalogAddOn * 562 DefaultCatalog::InstantiateEmbedded(entry_ref *appOrAddOnRef) 563 { 564 DefaultCatalog *catalog = new(std::nothrow) DefaultCatalog(appOrAddOnRef); 565 if (catalog && catalog->InitCheck() != B_OK) { 566 delete catalog; 567 return NULL; 568 } 569 return catalog; 570 } 571 572 573 BCatalogAddOn * 574 DefaultCatalog::Create(const char *signature, const char *language) 575 { 576 DefaultCatalog *catalog 577 = new(std::nothrow) DefaultCatalog("", signature, language); 578 if (catalog && catalog->InitCheck() != B_OK) { 579 delete catalog; 580 return NULL; 581 } 582 return catalog; 583 } 584 585 586 const uint8 DefaultCatalog::kDefaultCatalogAddOnPriority = 1; 587 // give highest priority to our embedded catalog-add-on 588