1/* 2 * Copyright (C) 2008 Nokia Corporation. 3 * Copyright (C) 2008 Zeeshan Ali (Khattak) <zeeshanak@gnome.org>. 4 * Copyright (C) 2010 Collabora Ltd. 5 * 6 * This library is free software: you can redistribute it and/or modify 7 * it under the terms of the GNU Lesser General Public License as published by 8 * the Free Software Foundation, either version 2.1 of the License, or 9 * (at your option) any later version. 10 * 11 * This library is distributed in the hope that it will be useful, 12 * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 * GNU Lesser General Public License for more details. 15 * 16 * You should have received a copy of the GNU Lesser General Public License 17 * along with this library. If not, see <http://www.gnu.org/licenses/>. 18 * 19 * Authors: Zeeshan Ali (Khattak) <zeeshanak@gnome.org> 20 * Travis Reitter <travis.reitter@collabora.co.uk> 21 * 22 * This file was originally part of Rygel. 23 */ 24 25using Gee; 26using GLib; 27 28extern const string G_LOG_DOMAIN; 29 30/** 31 * Responsible for backend loading. 32 * 33 * The BackendStore manages the set of available Folks backends. The 34 * {@link BackendStore.load_backends} function loads all compatible and enabled 35 * backends and the {@link BackendStore.backend_available} signal notifies when 36 * these backends are ready. 37 */ 38public class Folks.BackendStore : Object { 39 [CCode (has_target = false)] 40 private delegate void ModuleInitFunc (BackendStore store); 41 [CCode (has_target = false)] 42 private delegate void ModuleFinalizeFunc (BackendStore store); 43 44 /* this contains all backends, regardless of enabled or prepared state */ 45 private HashMap<string,Backend> _backend_hash; 46 /* if null, all backends are allowed */ 47 private SmallSet<string>? _backends_allowed; 48 /* if null, no backends are disabled */ 49 private SmallSet<string>? _backends_disabled; 50 private HashMap<string, Backend> _prepared_backends; 51 private Map<string, Backend> _prepared_backends_ro; 52 private File _config_file; 53 private GLib.KeyFile _backends_key_file; 54 private HashMap<string,unowned Module> _modules; 55 private static weak BackendStore? _instance = null; 56 private bool _is_prepared = false; 57 private Debug _debug; 58 59 /** 60 * This keyword in the keyfile acts as a wildcard for all backends not already 61 * listed in the same file. 62 * 63 * This is particularly useful for tests which want to ensure they're only 64 * operating with backends they were designed for (and thus may not be able to 65 * enumerate entries for). 66 * 67 * To avoid conflicts, backends must not use this as a name. 68 * 69 * @since 0.4.0 70 */ 71 public static string KEY_FILE_GROUP_ALL_OTHERS = "all-others"; 72 73 /** 74 * Emitted when a backend has been added to the BackendStore. 75 * 76 * This will not be emitted until after {@link BackendStore.load_backends} 77 * has been called. 78 * 79 * {@link Backend}s referenced in this signal are also included in 80 * {@link BackendStore.enabled_backends}. 81 * 82 * @param backend the new {@link Backend} 83 */ 84 public signal void backend_available (Backend backend); 85 86 /** 87 * The list of backends visible to this store which have not been explicitly 88 * disabled. 89 * 90 * This list will be empty before {@link BackendStore.load_backends} has been 91 * called. 92 * 93 * The backends in this list have been prepared and are ready to use. 94 * 95 * @since 0.5.1 96 */ 97 public Map<string, Backend> enabled_backends 98 { 99 /* Return a read-only view of the map */ 100 get { return this._prepared_backends_ro; } 101 102 private set {} 103 } 104 105 /** 106 * Whether {@link BackendStore.prepare} has successfully completed for this 107 * store. 108 * 109 * @since 0.3.0 110 */ 111 public bool is_prepared 112 { 113 get { return this._is_prepared; } 114 115 private set {} 116 } 117 118 /** 119 * Create a new BackendStore. 120 */ 121 public static BackendStore dup () 122 { 123 if (BackendStore._instance == null) 124 { 125 /* use an intermediate variable to force a strong reference */ 126 var new_instance = new BackendStore (); 127 BackendStore._instance = new_instance; 128 129 return new_instance; 130 } 131 132 return (!) BackendStore._instance; 133 } 134 135 private BackendStore () 136 { 137 Object (); 138 } 139 140 construct 141 { 142 /* Treat this as a library init function */ 143 var debug_no_colour = Environment.get_variable ("FOLKS_DEBUG_NO_COLOUR"); 144 var debug_no_color = Environment.get_variable ("FOLKS_DEBUG_NO_COLOR"); 145 this._debug = 146 Debug.dup_with_flags (Environment.get_variable ("G_MESSAGES_DEBUG"), 147 (debug_no_colour == null || debug_no_colour == "0") && 148 (debug_no_color == null || debug_no_color == "0")); 149 150 /* register the core debug messages */ 151 this._debug._register_domain (G_LOG_DOMAIN); 152 153 this._debug.print_status.connect (this._debug_print_status); 154 155 this._modules = new HashMap<string,unowned Module> (); 156 this._backend_hash = new HashMap<string,Backend> (); 157 this._prepared_backends = new HashMap<string,Backend> (); 158 this._prepared_backends_ro = this._prepared_backends.read_only_view; 159 } 160 161 ~BackendStore () 162 { 163 /* Unprepare all existing backends. */ 164 var iter = this._prepared_backends.map_iterator (); 165 while (iter.next () == true) 166 { 167 var backend = iter.get_value (); 168 backend.unprepare.begin (); 169 } 170 171 this._prepared_backends.clear (); 172 173 /* Finalize all the loaded modules that have finalize functions */ 174 foreach (var module in this._modules.values) 175 { 176 void* func; 177 if (module.symbol ("module_finalize", out func)) 178 { 179 ModuleFinalizeFunc module_finalize = (ModuleFinalizeFunc) func; 180 module_finalize (this); 181 } 182 } 183 184 this._modules.clear (); 185 186 /* Disconnect from the debug handler */ 187 this._debug.print_status.disconnect (this._debug_print_status); 188 189 /* manually clear the singleton instance */ 190 BackendStore._instance = null; 191 } 192 193 private void _debug_print_status (Debug debug) 194 { 195 const string domain = Debug.STATUS_LOG_DOMAIN; 196 const LogLevelFlags level = LogLevelFlags.LEVEL_INFO; 197 198 debug.print_heading (domain, level, "BackendStore (%p)", this); 199 debug.print_line (domain, level, "%u Backends:", 200 this._backend_hash.size); 201 202 debug.indent (); 203 204 foreach (var backend in this._backend_hash.values) 205 { 206 debug.print_heading (domain, level, "Backend (%p)", backend); 207 debug.print_key_value_pairs (domain, level, 208 "Ref. count", this.ref_count.to_string (), 209 "Name", backend.name, 210 "Prepared?", backend.is_prepared ? "yes" : "no", 211 "Quiescent?", backend.is_quiescent ? "yes" : "no" 212 ); 213 debug.print_line (domain, level, "%u PersonaStores:", 214 backend.persona_stores.size); 215 216 debug.indent (); 217 218 foreach (var persona_store in backend.persona_stores.values) 219 { 220 string? trust_level = null; 221 222 switch (persona_store.trust_level) 223 { 224 case PersonaStoreTrust.NONE: 225 trust_level = "none"; 226 break; 227 case PersonaStoreTrust.PARTIAL: 228 trust_level = "partial"; 229 break; 230 case PersonaStoreTrust.FULL: 231 trust_level = "full"; 232 break; 233 default: 234 assert_not_reached (); 235 } 236 237 var writeable_props = string.joinv (",", 238 persona_store.always_writeable_properties); 239 240 debug.print_heading (domain, level, "PersonaStore (%p)", 241 persona_store); 242 debug.print_key_value_pairs (domain, level, 243 "Ref. count", this.ref_count.to_string (), 244 "ID", persona_store.id, 245 "Prepared?", persona_store.is_prepared ? "yes" : "no", 246 "Is primary store?", 247 persona_store.is_primary_store ? "yes" : "no", 248 "Always writeable properties", writeable_props, 249 "Quiescent?", persona_store.is_quiescent ? "yes" : "no", 250 "Trust level", trust_level, 251 "Persona count", persona_store.personas.size.to_string () 252 ); 253 } 254 255 debug.unindent (); 256 } 257 258 debug.unindent (); 259 260 /* Finish with a blank line. The format string must be non-empty. */ 261 debug.print_line (domain, level, "%s", ""); 262 } 263 264 /** 265 * Prepare the BackendStore for use. 266 * 267 * This must only ever be called before {@link BackendStore.load_backends} is 268 * called for the first time. If it isn't called explicitly, 269 * {@link BackendStore.load_backends} will call it. 270 * 271 * This method is safe to call multiple times concurrently (e.g. an 272 * asynchronous call may begin between a subsequent asynchronous call 273 * beginning and finishing). 274 * 275 * @since 0.3.0 276 */ 277 public async void prepare () 278 { 279 Internal.profiling_start ("preparing BackendStore"); 280 281 /* (re-)load the list of disabled backends */ 282 yield this._load_disabled_backend_names (); 283 284 if (this._is_prepared == false) 285 { 286 this._is_prepared = true; 287 this.notify_property ("is-prepared"); 288 } 289 290 Internal.profiling_end ("preparing BackendStore"); 291 } 292 293 /** 294 * Find, load, and prepare all backends which are not disabled. 295 * 296 * Backends will be searched for in the path given by the 297 * ``FOLKS_BACKEND_PATH`` environment variable, if it's set. If it's not set, 298 * backends will be searched for in a path set at compilation time. 299 * 300 * This method is not safe to call multiple times concurrently. 301 * 302 * @throws GLib.Error currently unused 303 */ 304 public async void load_backends () throws GLib.Error 305 { 306 assert (Module.supported()); 307 308 Internal.profiling_start ("loading backends in BackendStore"); 309 310 yield this.prepare (); 311 312 /* unload backends that have been disabled since they were loaded */ 313 foreach (var backend_existing in this._backend_hash.values) 314 { 315 yield this._backend_unload_if_needed (backend_existing); 316 } 317 318 Internal.profiling_point ("unloaded backends in BackendStore"); 319 320 string? _path = Environment.get_variable ("FOLKS_BACKEND_PATH"); 321 string path; 322 323 if (_path == null) 324 { 325 path = BuildConf.BACKEND_DIR; 326 327 debug ("Using built-in backend dir '%s' (override with " + 328 "environment variable FOLKS_BACKEND_PATH)", path); 329 } 330 else 331 { 332 path = (!) _path; 333 334 debug ("Using environment variable FOLKS_BACKEND_PATH = " + 335 "'%s' to look for backends", path); 336 } 337 338 var modules = new HashMap<string, File> (); 339 var path_split = path.split (":"); 340 foreach (unowned string subpath in path_split) 341 { 342 var file = File.new_for_path (subpath); 343 344 bool is_file; 345 bool is_dir; 346 yield BackendStore._get_file_info (file, out is_file, out is_dir); 347 if (is_file) 348 { 349 modules.set (subpath, file); 350 } 351 else if (is_dir) 352 { 353 var cur_modules = yield this._get_modules_from_dir (file); 354 if (cur_modules != null) 355 { 356 foreach (var entry in ((!) cur_modules).entries) 357 { 358 modules.set (entry.key, entry.value); 359 } 360 } 361 } 362 else 363 { 364 critical ("FOLKS_BACKEND_PATH component '%s' is not a regular " + 365 "file or directory; ignoring...", 366 subpath); 367 assert_not_reached (); 368 } 369 } 370 371 Internal.profiling_point ("found modules in BackendStore"); 372 373 /* this will load any new modules found in the backends dir and will 374 * prepare and unprepare backends such that they match the state in the 375 * backend store key file */ 376 foreach (var module in modules.values) 377 this._load_module_from_file (module); 378 379 Internal.profiling_point ("loaded modules in BackendStore"); 380 381 /* this is populated indirectly from _load_module_from_file(), above */ 382 var backends_remaining = 1; 383 foreach (var backend in this._backend_hash.values) 384 { 385 backends_remaining++; 386 this._backend_load_if_needed.begin (backend, (o, r) => 387 { 388 this._backend_load_if_needed.end (r); 389 backends_remaining--; 390 391 if (backends_remaining == 0) 392 { 393 this.load_backends.callback (); 394 } 395 }); 396 } 397 backends_remaining--; 398 if (backends_remaining > 0) 399 { 400 yield; 401 } 402 403 Internal.profiling_end ("loading backends in BackendStore"); 404 } 405 406 /* This method is not safe to call multiple times concurrently, since there's 407 * a race in updating this._prepared_backends. */ 408 private async void _backend_load_if_needed (Backend backend) 409 { 410 if (this._backend_is_enabled (backend.name)) 411 { 412 if (!this._prepared_backends.has_key (backend.name)) 413 { 414 try 415 { 416 yield backend.prepare (); 417 418 debug ("New backend '%s' prepared", backend.name); 419 this._prepared_backends.set (backend.name, backend); 420 this.backend_available (backend); 421 } 422 catch (GLib.Error e) 423 { 424 if (e is DBusError.SERVICE_UNKNOWN) 425 { 426 /* Don’t warn if a D-Bus service is unknown; it probably 427 * means the backend is deliberately not running and the 428 * user is running folks from git, so hasn’t appropriately 429 * enabled/disabled backends from building. */ 430 debug ("Error preparing Backend '%s': %s", 431 backend.name, e.message); 432 } 433 else 434 { 435 warning ("Error preparing Backend '%s': %s", 436 backend.name, e.message); 437 } 438 } 439 } 440 } 441 } 442 443 /* This method is not safe to call multiple times concurrently, since there's 444 * a race in updating this._prepared_backends. */ 445 private async bool _backend_unload_if_needed (Backend backend) 446 { 447 var unloaded = false; 448 449 if (!this._backend_is_enabled (backend.name)) 450 { 451 Backend? backend_existing = this._backend_hash.get (backend.name); 452 if (backend_existing != null) 453 { 454 try 455 { 456 yield ((!) backend_existing).unprepare (); 457 } 458 catch (GLib.Error e) 459 { 460 warning ("Error unpreparing Backend '%s': %s", backend.name, 461 e.message); 462 } 463 464 this._prepared_backends.unset (((!) backend_existing).name); 465 466 unloaded = true; 467 } 468 } 469 470 return unloaded; 471 } 472 473 /** 474 * Add a new {@link Backend} to the BackendStore. 475 * 476 * @param backend the {@link Backend} to add 477 */ 478 public void add_backend (Backend backend) 479 { 480 /* Purge any other backend with the same name; re-add if enabled */ 481 Backend? backend_existing = this._backend_hash.get (backend.name); 482 if (backend_existing != null && backend_existing != backend) 483 { 484 ((!) backend_existing).unprepare.begin (); 485 this._prepared_backends.unset (((!) backend_existing).name); 486 } 487 488 this._debug._register_domain (backend.name); 489 490 this._backend_hash.set (backend.name, backend); 491 } 492 493 private bool _backend_is_enabled (string name) 494 { 495 var all_others_enabled = true; 496 497 if (this._backends_allowed != null && 498 !(name in (!) this._backends_allowed)) 499 return false; 500 501 if (this._backends_disabled != null && 502 name in (!) this._backends_disabled) 503 return false; 504 505 try 506 { 507 all_others_enabled = this._backends_key_file.get_boolean ( 508 BackendStore.KEY_FILE_GROUP_ALL_OTHERS, "enabled"); 509 } 510 catch (KeyFileError e) 511 { 512 if (!(e is KeyFileError.GROUP_NOT_FOUND) && 513 !(e is KeyFileError.KEY_NOT_FOUND)) 514 { 515 warning ("Couldn't determine whether to enable or disable " + 516 "backends not listed in backend key file. Defaulting to %s.", 517 all_others_enabled ? "enabled" : "disabled"); 518 } 519 else 520 { 521 debug ("No catch-all entry in the backend key file. %s " + 522 "unlisted backends.", 523 all_others_enabled ? "Enabling" : "Disabling"); 524 } 525 526 /* fall back to the default in case of any level of failure */ 527 } 528 529 var enabled = true; 530 try 531 { 532 enabled = this._backends_key_file.get_boolean (name, "enabled"); 533 } 534 catch (KeyFileError e) 535 { 536 /* if there's no entry for this backend, use the default set above */ 537 if ((e is KeyFileError.GROUP_NOT_FOUND) || 538 (e is KeyFileError.KEY_NOT_FOUND)) 539 { 540 debug ("Found no entry for backend '%s'.enabled in backend " + 541 "keyfile. %s according to '%s' setting.", 542 name, 543 all_others_enabled ? "Enabling" : "Disabling", 544 BackendStore.KEY_FILE_GROUP_ALL_OTHERS); 545 enabled = all_others_enabled; 546 } 547 else if (!(e is KeyFileError.GROUP_NOT_FOUND) && 548 !(e is KeyFileError.KEY_NOT_FOUND)) 549 { 550 warning ("Couldn't check enabled state of backend '%s': %s\n" + 551 "Disabling backend.", 552 name, e.message); 553 enabled = false; 554 } 555 } 556 557 return enabled; 558 } 559 560 /** 561 * Get a backend from the store by name. If a backend is returned, its 562 * reference count is increased. 563 * 564 * @param name the backend name to retrieve 565 * @return the backend, or ``null`` if none could be found 566 * 567 * @since 0.3.5 568 */ 569 public Backend? dup_backend_by_name (string name) 570 { 571 return this._backend_hash.get (name); 572 } 573 574 /** 575 * List the currently loaded backends. 576 * 577 * @return a list of the backends currently in the BackendStore 578 */ 579 public Collection<Backend> list_backends () 580 { 581 return this._backend_hash.values.read_only_view; 582 } 583 584 /** 585 * Enable a backend. 586 * 587 * Mark a backend as enabled, such that the BackendStore will always attempt 588 * to load it when {@link BackendStore.load_backends} is called. This will 589 * not load the backend if it's not currently loaded. 590 * 591 * This method is safe to call multiple times concurrently (e.g. an 592 * asynchronous call may begin after a previous asynchronous call for the same 593 * backend name has begun and before it has finished). 594 * 595 * If the backend is disallowed by the FOLKS_BACKENDS_ALLOWED 596 * and/or FOLKS_BACKENDS_DISABLED environment variables, this method 597 * will store the fact that it should be enabled in future, but will 598 * not enable it during this application run. 599 * 600 * @param name the name of the backend to enable 601 * @since 0.3.2 602 */ 603 public async void enable_backend (string name) 604 { 605 this._backends_key_file.set_boolean (name, "enabled", true); 606 yield this._save_key_file (); 607 } 608 609 /** 610 * Disable a backend. 611 * 612 * Mark a backend as disabled, such that it won't be loaded even when the 613 * client application is restarted. This will not remove the backend if it's 614 * already loaded. 615 * 616 * This method is safe to call multiple times concurrently (e.g. an 617 * asynchronous call may begin after a previous asynchronous call for the same 618 * backend name has begun and before it has finished). 619 * 620 * @param name the name of the backend to disable 621 * @since 0.3.2 622 */ 623 public async void disable_backend (string name) 624 { 625 this._backends_key_file.set_boolean (name, "enabled", false); 626 yield this._save_key_file (); 627 } 628 629 /* This method is safe to call multiple times concurrently. */ 630 private async HashMap<string, File>? _get_modules_from_dir (File dir) 631 { 632 debug ("Searching for modules in folder '%s' ..", dir.get_path ()); 633 634 var attributes = 635 FileAttribute.STANDARD_NAME + "," + 636 FileAttribute.STANDARD_TYPE + "," + 637 FileAttribute.STANDARD_IS_SYMLINK + "," + 638 FileAttribute.STANDARD_SYMLINK_TARGET + "," + 639 FileAttribute.STANDARD_CONTENT_TYPE; 640 641 GLib.List<FileInfo> infos; 642 try 643 { 644 FileEnumerator enumerator = 645 yield dir.enumerate_children_async (attributes, 646 FileQueryInfoFlags.NONE, Priority.DEFAULT, null); 647 648 infos = yield enumerator.next_files_async (int.MAX, 649 Priority.DEFAULT, null); 650 } 651 catch (Error error) 652 { 653 /* Translators: the first parameter is a folder path and the second 654 * is an error message. */ 655 critical (_("Error listing contents of folder ‘%s’: %s"), 656 dir.get_path (), error.message); 657 658 return null; 659 } 660 661 var modules_final = new HashMap<string, File> (); 662 663 string? _path = Environment.get_variable ("FOLKS_BACKEND_PATH"); 664 foreach (var info in infos) 665 { 666 var file = dir.get_child (info.get_name ()); 667 668 /* Handle symlinks by dereferencing them. If we look at two symlinks 669 * with the same target, we don’t end up loading that backend twice 670 * due to hashing the backend’s absolute path in @modules_final. 671 * 672 * We can’t just ignore symlinks due to the way Tinycorelinux installs 673 * software: /usr/local/lib/folks/41/backends/bluez/bluez.so is a 674 * symlink to 675 * /tmp/tcloop/folks/usr/local/lib/folks/41/backends/bluez/bluez.so, 676 * a loopback squashfs mount of the folks file system. Ignoring 677 * symlinks means we would never load backends in that environment. */ 678 if (info.get_is_symlink ()) 679 { 680 debug ("Handling symlink ‘%s’ to ‘%s’.", 681 file.get_path (), info.get_symlink_target ()); 682 683 var old_file = file; 684 file = dir.resolve_relative_path (info.get_symlink_target ()); 685 686 try 687 { 688 info = 689 yield file.query_info_async (attributes, 690 FileQueryInfoFlags.NONE); 691 } 692 catch (Error error) 693 { 694 /* Translators: the first parameter is a folder path and the second 695 * is an error message. */ 696 warning (_("Error querying info for target ‘%s’ of symlink ‘%s’: %s"), 697 file.get_path (), old_file.get_path (), error.message); 698 699 continue; 700 } 701 } 702 703 /* Handle proper files. */ 704 var file_type = info.get_file_type (); 705 unowned string content_type = info.get_content_type (); 706 707 string? mime = ContentType.get_mime_type (content_type); 708 709 if (file_type == FileType.DIRECTORY) 710 { 711 var modules = yield this._get_modules_from_dir (file); 712 if (modules != null) 713 { 714 foreach (var entry in ((!) modules).entries) 715 { 716 modules_final.set (entry.key, entry.value); 717 } 718 } 719 } 720 else if (mime == "application/x-sharedlib") 721 { 722 var path = file.get_path (); 723 if (path != null) 724 { 725 modules_final.set ((!) path, file); 726 } 727 } 728 else if (mime == null) 729 { 730 warning ( 731 "The content type of '%s' could not be determined. Have you installed shared-mime-info?", 732 file.get_path ()); 733 } 734 /* 735 * We should have only .la .so and sub-directories, except if FOLKS_BACKEND_PATH is set. 736 * Then we will run into all kinds of files. 737 */ 738 else if (_path == null && 739 mime != "application/x-sharedlib" && 740 mime != "application/x-shared-library-la" && 741 mime != "inode/directory") 742 { 743 warning ("The content type of '%s' appears to be '%s' which looks suspicious. Have you installed shared-mime-info?", file.get_path (), mime); 744 } 745 } 746 747 debug ("Finished searching for modules in folder '%s'", 748 dir.get_path ()); 749 750 return modules_final; 751 } 752 753 private void _load_module_from_file (File file) 754 { 755 var _file_path = file.get_path (); 756 if (_file_path == null) 757 { 758 return; 759 } 760 var file_path = (!) _file_path; 761 762 if (this._modules.has_key (file_path)) 763 return; 764 765 var _module = Module.open (file_path, ModuleFlags.BIND_LOCAL); 766 if (_module == null) 767 { 768 warning ("Failed to load module from path '%s': %s", 769 file_path, Module.error ()); 770 771 return; 772 } 773 unowned Module module = (!) _module; 774 775 void* function; 776 777 /* this causes the module to call add_backend() for its backends (adding 778 * them to the backend hash); any backends that already existed will be 779 * removed if they've since been disabled */ 780 if (!module.symbol("module_init", out function)) 781 { 782 warning ("Failed to find entry point function '%s' in '%s': %s", 783 "module_init", 784 file_path, 785 Module.error ()); 786 787 return; 788 } 789 790 ModuleInitFunc module_init = (ModuleInitFunc) function; 791 assert (module_init != null); 792 793 this._modules.set (file_path, module); 794 795 /* We don't want our modules to ever unload */ 796 module.make_resident (); 797 798 module_init (this); 799 800 debug ("Loaded module source: '%s'", module.name ()); 801 } 802 803 /* This method is safe to call multiple times concurrently. */ 804 private async static void _get_file_info (File file, 805 out bool is_file, 806 out bool is_dir) 807 { 808 FileInfo file_info; 809 is_file = false; 810 is_dir = false; 811 812 try 813 { 814 /* Query for the MIME type; if the file doesn't exist, we'll get an 815 * appropriate error back, so this also checks for existence. */ 816 file_info = yield file.query_info_async (FileAttribute.STANDARD_TYPE, 817 FileQueryInfoFlags.NONE, Priority.DEFAULT, null); 818 } 819 catch (Error error) 820 { 821 if (error is IOError.NOT_FOUND) 822 { 823 /* Translators: the parameter is a filename. */ 824 critical (_("File or directory ‘%s’ does not exist."), 825 file.get_path ()); 826 } 827 else 828 { 829 /* Translators: the parameter is a filename. */ 830 critical (_("Failed to get content type for ‘%s’."), 831 file.get_path ()); 832 } 833 834 return; 835 } 836 837 is_file = (file_info.get_file_type () == FileType.REGULAR); 838 is_dir = (file_info.get_file_type () == FileType.DIRECTORY); 839 } 840 841 /* This method is safe to call multiple times concurrently. */ 842 private async void _load_disabled_backend_names () 843 { 844 /* If set, this is a list of allowed backends. No others can be enabled, 845 * even if the keyfile says they ought to be. 846 * The default is equivalent to "all", which allows any backend. 847 * 848 * Regression tests and benchmarks can use this to restrict themselves 849 * to a small set of backends for which they have done the necessary 850 * setup/configuration/sandboxing. */ 851 var envvar = Environment.get_variable ("FOLKS_BACKENDS_ALLOWED"); 852 853 if (envvar != null) 854 { 855 /* Allow space, comma or colon separation, consistent with 856 * g_parse_debug_string(). */ 857 var tokens = envvar.split_set (" ,:"); 858 859 this._backends_allowed = new SmallSet<string> (); 860 861 foreach (unowned string s in tokens) 862 { 863 if (s == "all") 864 { 865 this._backends_allowed = null; 866 break; 867 } 868 869 if (s != "") 870 this._backends_allowed.add (s); 871 } 872 873 if (this._backends_allowed != null) 874 { 875 debug ("Backends limited by FOLKS_BACKENDS_ALLOWED:"); 876 877 foreach (unowned string s in tokens) 878 debug ("Backend '%s' is allowed", s); 879 880 debug ("All other backends disabled by FOLKS_BACKENDS_ALLOWED"); 881 } 882 } 883 884 /* If set, this is a list of disallowed backends. 885 * They are not enabled, even if the keyfile says they ought to be. */ 886 envvar = Environment.get_variable ("FOLKS_BACKENDS_DISABLED"); 887 888 if (envvar != null) 889 { 890 var tokens = envvar.split_set (" ,:"); 891 892 this._backends_disabled = new SmallSet<string> (); 893 894 foreach (unowned string s in tokens) 895 { 896 if (s != "") 897 { 898 debug ("Backend '%s' disabled by FOLKS_BACKENDS_DISABLED", s); 899 this._backends_disabled.add (s); 900 } 901 } 902 } 903 904 File file; 905 unowned string? path = Environment.get_variable ( 906 "FOLKS_BACKEND_STORE_KEY_FILE_PATH"); 907 if (path == null) 908 { 909 file = File.new_for_path (Environment.get_user_data_dir ()); 910 file = file.get_child ("folks"); 911 file = file.get_child ("backends.ini"); 912 913 debug ("Using built-in backends key file '%s' (override with " + 914 "environment variable FOLKS_BACKEND_STORE_KEY_FILE_PATH)", 915 file.get_path ()); 916 } 917 else 918 { 919 file = File.new_for_path ((!) path); 920 debug ("Using environment variable " + 921 "FOLKS_BACKEND_STORE_KEY_FILE_PATH = '%s' to load the backends " + 922 "key file.", (!) path); 923 } 924 925 this._config_file = file; 926 927 /* Load the disabled backends file */ 928 var key_file = new GLib.KeyFile (); 929 try 930 { 931 uint8[] contents; 932 933 yield file.load_contents_async (null, out contents, null); 934 unowned string contents_s = (string) contents; 935 936 if (contents_s.length > 0) 937 { 938 key_file.load_from_data (contents_s, 939 contents_s.length, KeyFileFlags.KEEP_COMMENTS); 940 } 941 } 942 catch (Error e1) 943 { 944 if (!(e1 is IOError.NOT_FOUND)) 945 { 946 warning ("The backends key file '%s' could not be loaded: %s", 947 file.get_path (), e1.message); 948 return; 949 } 950 } 951 finally 952 { 953 /* Update the key file in memory, whether the new one is empty or 954 * full. */ 955 this._backends_key_file = (owned) key_file; 956 } 957 } 958 959 /* This method is safe to call multiple times concurrently. */ 960 private async void _save_key_file () 961 { 962 var key_file_data = this._backends_key_file.to_data (); 963 964 debug ("Saving backend key file '%s'.", this._config_file.get_path ()); 965 966 try 967 { 968 /* Note: We have to use key_file_data.size () here to get its length 969 * in _bytes_ rather than _characters_. bgo#628930. 970 * In Vala >= 0.11, string.size() has been deprecated in favour of 971 * string.length (which now returns the byte length, whereas in 972 * Vala <= 0.10, it returned the character length). FIXME: We need to 973 * take this into account until we depend explicitly on 974 * Vala >= 0.11. */ 975 yield this._config_file.replace_contents_async (key_file_data.data, 976 null, false, FileCreateFlags.PRIVATE, 977 null, null); 978 } 979 catch (Error e) 980 { 981 warning ("Could not write updated backend key file '%s': %s", 982 this._config_file.get_path (), e.message); 983 } 984 } 985} 986