1 /* Copyright (C) 2005-2008 Thomas Bopp, Thorsten Hampel, Robert Hinn
3 * This program is free software; you can redistribute it and/or modify
4 * it under the terms of the GNU General Public License as published by
5 * the Free Software Foundation; either version 2 of the License, or
6 * (at your option) any later version.
8 * This program is distributed in the hope that it will be useful,
9 * but WITHOUT ANY WARRANTY; without even the implied warranty of
10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 * GNU General Public License for more details.
13 * You should have received a copy of the GNU General Public License
14 * along with this program; if not, write to the Free Software
15 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
19 #include <attributes.h>
26 #include <exception.h>
28 #include <configure.h>
33 Thread.Queue saveQueue = Thread.Queue();
36 //#define DEBUG_PERSISTENCE 1
38 #ifdef DEBUG_PERSISTENCE
39 #define PDEBUG(s, args...) werror("persistence: "+s+"\n", args)
41 #define PDEBUG(s, args...)
44 #define PROXY "/kernel/proxy.pike"
46 mapping config = ([ ]);
48 mapping namespaces = ([ ]);
49 mapping namespace_types = ([ ]);
50 mapping namespace_configs = ([ ]);
52 mapping pending_users = ([ ]);
53 mapping pending_groups = ([ ]);
54 mapping pending_objects = ([ ]);
55 array pending_synchronizations = ({ });
56 bool dont_create_users = false; // used by lookup_internal()
58 array restricted_users = ({ "root", "service", "postman", "guest" });
60 array restricted_groups = ({ "steam", "admin", "coder", "privgroups",
61 "wikigroups", "help", "everyone" });
65 array whitelist_users;
66 array whitelist_groups;
71 int store_content_in_database;
72 int store_content_in_filesystem;
74 object object_cache = ObjectCache();
77 class ObjectCacheEntry {
86 int time_synchronized;
88 int `<(object o) { return time_accessed < o->time_accessed; }
89 int `>(object o) { return time_accessed > o->time_accessed; }
90 int `==(object o) { return time_accessed == o->time_accessed; }
95 mapping by_id = ([ ]);
96 mapping by_proxy = ([ ]);
97 mapping by_identifier = ([ ]);
98 int synchronize_frequency = 30;
101 * Get the synchronization frequency. Objects in the cache will synchronize
102 * with data in the persistence layers on their next access when at least
103 * this number of seconds have passed.
105 * @return the synchronization frequency (number of seconds), 0 means that
106 * objects will synchronize on every access (e.g. lookup).
108 int get_synchronize_frequency () {
109 return synchronize_frequency;
113 * Set the synchronization frequency. Objects in the cache will synchronize
114 * with data in the persistence layers on their next access when at least
115 * this number of seconds have passed.
117 * @param nr_seconds the number of seconds after which an object is
118 * considered to need synchronization with the persistence layers. If you
119 * set this to 0, then objects will synchronize on each cache access
120 * (e.g. each lookup), which can be quite often and cause delays.
122 void set_synchronize_frequency ( int nr_seconds ) {
123 synchronize_frequency = nr_seconds;
127 * Finds a cached object and returns its cache entry.
128 * This does not touch the object (meaning that it won't update any of
131 * @param obj the object (or proxy) to look for in the cache
132 * @return the ObjectCacheEntry of the cached object, or 0 if no object
135 object find_by_proxy ( object obj ) {
139 * Finds a cached object by its object id and returns its cache entry.
140 * This does not touch the object (meaning that it won't update any of
143 * @param id the object id of the object to look for in the cache
144 * @return the ObjectCacheEntry of the cached object, or 0 if no object
147 object find_by_id ( int id ) {
152 * Finds a cached object by its identifier. Since multiple objects might
153 * have the same identifier, an array of cache entries will be returned,
154 * even if there is only one result. If no object is found by that
155 * identifier, then 0 will be returned.
156 * This does not touch the object (meaning that it won't update any of
159 * @param id the object id of the object to look for in the cache
160 * @return the ObjectCacheEntry of the cached object, or 0 if no object
163 array find_by_identifier ( string identifier ) {
164 return by_identifier[ identifier ];
168 * Fetches an object in the object cache. If the object was already cached,
169 * then its "accessed" time is updated, otherwise it will be cached.
170 * If the objects previous access time is older that the synchronization
171 * frequency allows, then the object will synchronize with data in the
172 * persistence layers. An object that wasn't cached before will always
173 * synchronize with the persistence layers by default.
175 * @param obj the object (or proxy) to touch (or cache)
176 * @param dont_synchronize if true (or != 0) then do not synchronize the
177 * object with the persistence layers, even if its cache age would
179 * @return the cache entry (type ObjectCacheEntry) of the cached object, or 0
180 * if the object could not be cached (e.g. has no get_identifier() method).
182 object fetch ( object obj, bool dont_synchronize ) {
183 if ( !objectp(obj) ) return 0;
184 // check whether object already is cached:
185 if ( objectp(cache_entry) ) {
186 if ( !dont_synchronize &&
187 (time() - cache_entry->time_synchronized >= synchronize_frequency) ) {
188 synchronize_object( obj );
189 // check whether the object has been deleted by synchronization:
190 if ( obj->status() == PSTAT_DELETED ) {
194 cache_entry->time_synchronized = time();
196 cache_entry->time_accessed = time();
199 // need to add object to cache:
200 if ( !functionp(obj->get_object_class) ||
201 !functionp(obj->get_object_id) ||
202 !functionp(obj->get_identifier) )
204 int obj_class = obj->get_object_class();
205 int obj_id = obj->get_object_id();
206 string identifier = obj->get_identifier();
208 cache_entry = ObjectCacheEntry();
209 cache_entry->obj_class = obj_class;
210 cache_entry->obj_id = obj_id;
211 cache_entry->identifier = identifier;
212 cache_entry->time_accessed = time();
214 by_id[ obj_id ] = cache_entry;
215 if ( arrayp(by_identifier[identifier]) )
216 by_identifier[ identifier ] += ({ cache_entry });
218 by_identifier[ identifier ] = ({ cache_entry });
220 if ( !dont_synchronize ) {
221 synchronize_object( obj );
222 cache_entry->time_synchronized = time();
229 * Drops a cache entry. If the object wasn't cached then nothing will happen.
230 * Note: the object will only be removed from the object cache, it will
231 * not be dropped from memory.
233 * @param obj the object (or proxy) to remove from the cache
234 * @return 1 if the object was found in the cache and dropped, 0 if the
235 * object was not found in the cache
237 int drop ( object obj ) {
240 object cache_entry = find_by_proxy( obj );
241 if ( !objectp(cache_entry) )
243 m_delete( by_proxy, cache_entry->proxy );
244 m_delete( by_id, cache_entry->obj_id );
245 array identifier_entry = by_identifier[ cache_entry->identifier ];
246 if ( arrayp(identifier_entry) &&
247 search( identifier_entry, cache_entry ) >= 0 ) {
248 if ( sizeof( identifier_entry ) <= 1 )
249 m_delete( by_identifier, cache_entry->identifier );
251 by_identifier[ cache_entry->identifier ] -= ({ cache_entry });
253 //TODO: really drop the object from memory (and document this)
259 int uncache_object ( object obj ) {
260 if ( !objectp( obj ) ) return 0;
261 // drop from persistence layer caches:
262 foreach( indices(namespaces), mixed idx ) {
263 object handler = namespaces[idx];
264 if ( functionp( handler["uncache_object"] ) ) {
265 mixed err = catch( handler->uncache_object( obj ) );
267 FATAL( "Error while uncaching object %O from namespace %O: %s\n%O\n",
268 obj, idx, err[0], err[1] );
271 // drop from object cache:
272 int res = object_cache->drop( obj );
273 PDEBUG( (res ? "uncached" : "could not uncache") + " object %O", obj );
279 * Drops a user from any caches, so that it will receive fresh data on the
280 * next lookup. This can be useful if you know that user data has changed in
281 * one of the persistence layers and you want the user object to update its
282 * data accordingly (before the regular update after the cache times out).
284 * @param identifier the identifier (user's login name) of the user that shall
285 * be dropped from cache
286 * @return 1 if the user was dropped from the object cache,
287 * 0 if it wasn't found in the object cache (regardless of this return value,
288 * the user might have been dropped from any persistence layer caches)
290 int uncache_user ( string identifier ) {
291 // drop from persistence layer caches:
292 foreach( indices(namespaces), mixed idx ) {
293 object handler = namespaces[idx];
294 if ( functionp( handler["uncache_user"] ) ) {
295 mixed err = catch( handler->uncache_user( identifier ) );
297 FATAL( "Error while uncaching user %O from namespace %O: %s\n%O\n",
298 identifier, idx, err[0], err[1] );
301 // drop from object cache:
302 array cache_entries = object_cache->find_by_identifier( identifier );
303 if ( !arrayp(cache_entries) ) return 0;
304 foreach ( cache_entries, object entry ) {
305 if ( entry->obj_class & CLASS_USER )
306 return uncache_object( entry->proxy );
312 * Drops a group from any caches, so that it will receive fresh data on the
313 * next lookup. This can be useful if you know that group data has changed in
314 * one of the persistence layers and you want the group object to update its
315 * data accordingly (before the regular update after the cache times out).
317 * @param identifier the identifier (full group name with parent groups
318 * separated by ".") of the group that shall be dropped from cache
319 * @return 1 if the group was dropped from the object cache,
320 * 0 if it wasn't found in the object cache (regardless of this return value,
321 * the group might have been dropped from any persistence layer caches)
323 int uncache_group ( string identifier ) {
324 // drop from persistence layer caches:
325 foreach( indices(namespaces), mixed idx ) {
326 object handler = namespaces[idx];
327 if ( functionp( handler["uncache_group"] ) ) {
328 mixed err = catch( handler->uncache_group( identifier ) );
330 FATAL( "Error while uncaching group %O from namespace %O: %s\n%O\n",
331 identifier, idx, err[0], err[1] );
334 // drop from object cache:
335 array cache_entries = object_cache->find_by_identifier( identifier );
336 if ( !arrayp(cache_entries) ) return 0;
337 foreach ( cache_entries, object entry ) {
338 if ( entry->obj_class & CLASS_GROUP )
339 return uncache_object( entry->proxy );
344 int is_storing_content_in_database () {
345 return store_content_in_database;
348 int is_storing_content_in_filesystem () {
349 return store_content_in_filesystem;
355 return saveQueue->size();
361 thread_create(save_demon);
367 mixed get_config () {
368 if ( !GROUP("admin")->is_member( this_user() ) )
369 THROW( "Only administrators may query the persistence config!", E_ACCESS );
374 bool get_dont_create_exits () {
375 return Config.bool_value(config["dont-create-exits"]);
379 string safe_lower_case ( string s ) {
380 if ( !stringp(s) ) return s;
381 if ( xml.utf8_check( s ) )
382 return string_to_utf8( lower_case( utf8_to_string( s ) ) );
384 return lower_case( s );
389 * Sends an email to the maintainers of namespaces.
391 * @param namespace a namespace id or a list of namespace ids whose
392 * maintainers to send the mail to
393 * @param subject the subject of the mail
394 * @param message the message body
395 * @param args arguments to the message body (works with the message body
396 * like sprintf or write)
398 void mail_maintainer ( array|int namespace, string subject, string message, mixed ... args ) {
400 if ( arrayp(namespace) ) namespaces = namespace;
401 else if ( intp(namespace) ) namespaces = ({ namespace });
404 PDEBUG("Mailing maintainers of %O", namespace);
406 string title = "(" + BRAND_NAME + ") " + subject;
407 string body = sprintf( message, @args ) + "\n\n"
408 + "* Server: " + _Server->get_server_name() + "\n"
409 + "* Time: " + ctime(time()) + "\n";
410 foreach ( namespaces, int nid ) {
411 string namespace_type = search( namespace_types, nid );
412 string maintainer = config["maintainer"];
413 if ( mappingp(namespace_configs[nid]) )
414 maintainer = namespace_configs[nid]["maintainer"];
415 if ( !stringp(maintainer) )
416 maintainer = config["maintainer"];
417 if ( !stringp(maintainer) || sizeof(maintainer)<1 )
418 return; // no maintainer specified, don't send a mail
419 array maintainers = Config.array_value( maintainer );
420 if ( !arrayp(maintainers) || sizeof(maintainers)<1 )
422 foreach ( maintainers, string maintainer ) {
423 object user = USER( maintainer );
424 if ( !objectp(user) ) continue;
425 PDEBUG("* Mailing maintainer %s of namespace %O\n", user->get_identifier(), nid );
426 user->mail( body + sprintf("* Persistence layer type: %O", namespace_type), title, 0, "text/plain" );
432 /** Prepends the namespace part to a given object id. May only be
433 * called by namespaces (persistence layers).
434 * @param small_oid the small object id (without namespace information)
435 * @return the new (complete) object id (or 0 on error)
437 int make_object_id (int small_oid)
439 if ( ! is_namespace( CALLER ) ) {
440 werror( "Persistence: make_object_id(): Caller is not a namespace: %O\n", CALLER );
443 if ( CALLER == __Database ) return small_oid & 0x7fffffff; // 32bit, first bit must be zero
444 int nid = search( values(namespaces), CALLER );
446 werror( "Persistence: make_object_id(): Caller is not registered as a namespace: %O\n", CALLER );
449 nid = indices(namespaces)[nid];
450 int oid_length = (int)ceil( log( (float)small_oid ) / log( 2.0 ) );
451 if ( (oid_length % 8) != 0 ) oid_length = oid_length + 8 - (oid_length % 8);
452 oid_length /= 8; // byte length, not bit length
453 if ( oid_length > 0xfff ) {
454 werror( "Persistence: make_object_id(): oid too long (%d bytes)\n", oid_length );
457 // build namespace part: 1[3bit-reserved][16bit-nid][12bit-oid_length]
458 nid = ((0x80000000 | (nid & 0xffff)) << 12) | oid_length;
459 // shift left to make room for oid:
460 nid = nid << oid_length * 8;
461 // cut object id to oid_length bits:
462 nid = nid | small_oid;
468 * @return the namespace id of the persistence layer
470 int register( string type_name, object handler )
472 if ( !stringp(type_name) || sizeof(type_name)<1 ) {
473 werror( "Namespace tried to register without a valid type name: %O\n", handler );
476 if ( !objectp(handler) ) {
477 werror( "Namespace '%s' tried to register, but it is not an object: %O\n", type_name, handler );
480 if ( !objectp( __Database ) ) {
481 if ( !objectp( master()->get_constant("_Database") ) ) {
482 werror( "Namespace tried to register, but database hasn't registered, yet!\n");
486 __Database = master()->get_constant("_Database");
487 namespaces[0] = __Database;
488 namespace_types[type_name] = 0;
492 if ( handler != __Database ) {
493 nid = search( values(namespaces), handler );
494 if ( nid >= 0 ) // already registered
499 foreach( indices(namespaces), int tmp_nid )
500 if ( nid <= tmp_nid ) nid = tmp_nid+1;
501 namespaces[nid] = handler;
503 if ( !zero_type(namespace_types[type_name]) )
504 werror( "Warning: namespace type '%s' already registered for layer %O\n", type_name, namespace_types[type_name] );
505 namespace_types[type_name] = nid;
508 if ( arrayp(config["layer"]) ) {
509 foreach ( config["layer"], mixed layer ) {
510 if ( !mappingp(layer) ) continue;
511 if ( !stringp(layer["type"]) ) continue;
512 if ( layer["type"] == type_name ) {
513 namespace_configs[ nid ] = layer;
519 if ( nid != 0 && objectp(GROUP("admin")) ) {
520 GROUP("admin")->unlock_attribute("namespaces");
521 GROUP("admin")->set_attribute("namespaces", namespaces);
522 GROUP("admin")->set_attribute("namespace_types", namespace_types);
523 GROUP("admin")->lock_attribute("namespaces");
540 config = Config.read_config_file( _Server.get_config_dir()+"/persistence.cfg", "persistence" );
541 if ( !mappingp(config) ) {
543 MESSAGE( "No persistence.cfg config file." );
545 PDEBUG( "persistence config is %O", config );
546 if ( arrayp(Config.array_value(config["restricted-users"])) ) {
547 restricted_users = ({ });
548 foreach ( Config.array_value( config["restricted-users"] ), string user )
549 restricted_users += ({ lower_case( user ) });
551 PDEBUG( "restricted users: %O", restricted_users );
552 if ( arrayp(Config.array_value(config["restricted-groups"])) ) {
553 restricted_groups = ({ });
554 foreach ( Config.array_value( config["restricted-groups"] ), string group )
555 restricted_groups += ({ lower_case( group ) });
557 PDEBUG( "restricted groups: %O", restricted_groups );
558 if ( arrayp(Config.array_value(config["whitelist-users"])) ) {
559 PDEBUG( "overriding whitelisted users by persistence.cfg" );
560 whitelist_users = ({ });
561 foreach ( Config.array_value( config["whitelist-users"] ), string user )
562 whitelist_users += ({ lower_case( user ) });
564 if ( arrayp(Config.array_value(config["whitelist-groups"])) ) {
565 PDEBUG( "overriding whitelisted groups by persistence.cfg" );
566 whitelist_groups = ({ });
567 foreach ( Config.array_value( config["whitelist-groups"] ), string group )
568 whitelist_groups += ({ lower_case( group ) });
571 store_content_in_database = 1;
572 if ( mappingp(config["content"]) ) {
573 mixed db_content = config["content"]["database"];
574 if ( stringp(db_content) ) {
575 switch ( lower_case(db_content) ) {
580 store_content_in_database = 0;
581 MESSAGE( "Database content storage has been disabled" );
587 store_content_in_filesystem = 0;
588 if ( mappingp(config["content"]) && mappingp(config["content"]["filesystem"]) ) {
589 mapping server_config = Config.read_config_file( CONFIG_DIR + "/steam.cfg" );
590 string content_path = server_config["sandbox"];
591 if ( !stringp(content_path) || content_path == "" ) content_path = STEAM_DIR + "/tmp/content";
592 else content_path += "/content";
594 // check whether to use sandbox directly for content storage:
595 if ( stringp(config["content"]["filesystem"]["sandbox"]) &&
596 Config.bool_value( config["content"]["filesystem"]["sandbox"] ) ) {
597 MESSAGE( "Sandbox content/ subdirectory will be used as content filesystem." );
598 store_content_in_filesystem = 1;
600 // check whether to mount a filesystem for content storage:
601 else if ( stringp(config["content"]["filesystem"]["mount"] ) ) {
602 mixed content_dir = get_dir( content_path );
603 if ( arrayp(content_dir) && sizeof(content_dir) > 0 ) {
604 MESSAGE( "Content filesystem already mounted on %s", content_path );
605 store_content_in_filesystem = 1;
608 string tmp_content_path;
609 mixed err = catch( tmp_content_path = ContentFilesystem.mount() );
612 else if ( stringp(tmp_content_path) ) {
613 MESSAGE( "Content filesystem mounted on %s", content_path );
614 content_path = tmp_content_path;
615 store_content_in_filesystem = 1;
617 // If tmp_content_path is no string but no exception occurred during mount,
618 // then filesystem content storage was not enabled.
624 if ( store_content_in_database )
625 MESSAGE( "Content will be stored in and read from database." );
627 if ( store_content_in_filesystem )
628 MESSAGE( "Content will be stored in and read from filesystem." );
631 if ( !store_content_in_database && !store_content_in_filesystem )
632 steam_error( "Neither database nor filesystem content storage enabled!" );
637 root_user = USER("root");
638 if ( objectp(GROUP("admin")) ) {
639 GROUP("admin")->unlock_attribute("namespaces");
640 GROUP("admin")->set_attribute("namespaces",namespaces);
641 GROUP("admin")->set_attribute("namespace_types",namespace_types);
642 GROUP("admin")->lock_attribute("namespaces");
643 if ( !arrayp(Config.array_value(config["whitelist-users"])) )
644 whitelist_users = GROUP("admin")->query_attribute( "whitelist_users" );
645 PDEBUG( "whitelisted users: %O", whitelist_users );
646 if ( !arrayp(Config.array_value(config["whitelist-groups"])) )
647 whitelist_groups = GROUP("admin")->query_attribute( "whitelist_groups" );
648 PDEBUG( "whitelisted groups: %O", whitelist_groups );
650 else FATAL( "Could not get \"Admin\" group to store persistence namespace ids and whitelists." );
657 * @return array { nid, oid_length(bytes), oid }
659 array split_object_id ( object|int p )
664 if ( intp(p) ) oid = p;
665 else if ( objectp(p) ) oid = p->get_object_id();
667 werror("Persistence: split_object_id(): param is not int or object: %O\n", p);
670 if ( oid < 0 ) return UNDEFINED;
671 else if ( oid == 0 ) return ({ 0, 0, 0 });
672 if ( (oid & 0x80000000) == 0 ) return ({ 0, 4, oid & 0xffffffff });
673 oid_length = (((int)(log( (float)oid ) / log( 2.0 ))) - 31);
674 nid = oid >> oid_length;
675 oid = oid & ((1 << oid_length) - 1);
676 return ({ nid, oid_length/8, oid });
679 object get_namespace(object|int p)
681 mixed nid = split_object_id( p );
682 if ( !arrayp(nid) ) return UNDEFINED;
683 return namespaces[ nid[0] ];
687 array get_users_allowed () {
688 if ( !GROUP("admin")->is_member( this_user() ) )
689 THROW( "Only administrators may list allowed users!", E_ACCESS );
690 return whitelist_users;
694 bool user_allowed ( string user )
696 // restricted users are system users and should always be allowed:
697 if ( user_restricted( user ) ) return true;
699 if ( arrayp(whitelist_users) && sizeof(whitelist_users) > 0 )
700 return search( whitelist_users, safe_lower_case( user ) ) >= 0;
705 void add_user_allowed ( string user )
707 if ( !GROUP("admin")->is_member( this_user() ) )
708 THROW( "Only administrators may add allowed users!", E_ACCESS );
709 if ( arrayp(Config.array_value(config["whitelist-users"])) )
710 return; // overridden by config file
711 if ( !arrayp(whitelist_users) ) whitelist_users = ({ });
712 user = safe_lower_case( user );
713 if ( search( whitelist_users, user ) < 0 ) whitelist_users += ({ user });
714 GROUP("admin")->unlock_attribute( "whitelist_users" );
715 GROUP("admin")->set_attribute( "whitelist_users", whitelist_users );
716 GROUP("admin")->lock_attribute( "whitelist_users" );
720 void remove_user_allowed ( string user )
722 if ( !GROUP("admin")->is_member( this_user() ) )
723 THROW( "Only administrators may remove allowed users!", E_ACCESS );
724 if ( arrayp(Config.array_value(config["whitelist-users"])) )
725 return; // overridden by config file
726 if ( !arrayp(whitelist_users) ) return;
727 user = safe_lower_case( user );
728 whitelist_users -= ({ user });
729 GROUP("admin")->unlock_attribute( "whitelist_users" );
730 GROUP("admin")->set_attribute( "whitelist_users", whitelist_users );
731 GROUP("admin")->lock_attribute( "whitelist_users" );
735 array get_groups_allowed () {
736 if ( !GROUP("admin")->is_member( this_user() ) )
737 THROW( "Only administrators may list allowed groups!", E_ACCESS );
738 return whitelist_groups;
742 bool group_allowed ( string group )
744 if ( group_restricted( group ) ) return true;
745 if ( arrayp(whitelist_groups) && sizeof(whitelist_groups) > 0 )
746 return search( whitelist_groups, safe_lower_case( group ) ) >= 0;
751 void add_group_allowed ( string group )
753 if ( !GROUP("admin")->is_member( this_user() ) )
754 THROW( "Only administrators may add allowed groups!", E_ACCESS );
755 if ( arrayp(Config.array_value(config["whitelist-groups"])) )
756 return; // overridden by config file
757 if ( !arrayp(whitelist_groups) ) whitelist_groups = ({ });
758 group = safe_lower_case( group );
759 if ( search( whitelist_groups, group ) < 0 ) whitelist_groups += ({ group });
760 GROUP("admin")->unlock_attribute( "whitelist_groups" );
761 GROUP("admin")->set_attribute( "whitelist_groups", whitelist_groups );
762 GROUP("admin")->lock_attribute( "whitelist_groups" );
766 void remove_group_allowed ( string group )
768 if ( !GROUP("admin")->is_member( this_user() ) )
769 THROW( "Only administrators may remove allowed groups!", E_ACCESS );
770 if ( arrayp(Config.array_value(config["whitelist-groups"])) )
771 return; // overridden by config file
772 if ( !arrayp(whitelist_groups) ) return;
773 group = safe_lower_case( group );
774 whitelist_groups -= ({ group });
775 GROUP("admin")->unlock_attribute( "whitelist_groups" );
776 GROUP("admin")->set_attribute( "whitelist_groups", whitelist_groups );
777 GROUP("admin")->lock_attribute( "whitelist_groups" );
781 bool user_restricted ( string user )
783 if ( !stringp(user) ) return false;
784 // since no users and groups of the same name may exist, check both:
785 if ( search( restricted_users | restricted_groups, safe_lower_case( user ) ) >= 0 )
790 bool group_restricted ( string group )
792 if ( !stringp(group) ) return false;
793 // since no users and groups of the same name may exist, check both:
794 if ( search( restricted_groups | restricted_users, safe_lower_case( group ) ) >= 0 )
800 * creates a new persistent sTeam object.
802 * @param string prog (the class to clone)
803 * @return proxy and id for object
804 * note that proxy creation implies creation of associated object.
805 * @see kernel.proxy.create, register_user
807 mixed new_object(mixed id)
810 string prog_name = master()->describe_program(object_program(CALLER));
812 foreach(indices(namespaces), mixed idx ) {
815 object handler = namespaces[idx];
816 if ( !functionp(handler->new_object) )
818 if ( res = handler->new_object(id, obj, prog_name) ) {
822 //TODO: use new_object(id,obj,program) api in database.pike, too
823 return namespaces[0]->new_object(obj, prog_name);
826 mapping get_namespaces()
831 int get_namespace_id (object ns)
833 mixed index = search( values(namespaces), ns );
834 if ( !intp(index) ) return -1;
835 return indices(namespaces)[index];
838 int is_namespace(object ns)
844 void set_proxy_status(object p, int status)
846 if ( is_namespace(CALLER) )
847 p->set_status(status);
850 bool delete_object(object p)
853 object_cache->drop(p);
855 object nid = get_namespace(p);
856 if ( !objectp(nid) ) {
857 werror( "delete_object: invalid namespace for %O\n", p->get_object_id() );
860 if ( !functionp(nid->delete_object) )
862 return nid->delete_object(p);
867 * Synchronizes the object with data in the persistence layers.
868 * Right now, this only reads data from the persistence layers and doesn't
869 * write back changes.
870 * @TODO update data in persistence layers if the object has changed
872 * @return true if data in the object has changed, false otherwise
874 bool synchronize_object ( object obj ) {
875 if ( !objectp(obj) || !functionp(obj->get_object_class) )
877 // prevent cyclic recursion (e.g. through cache lookups):
878 if ( search( pending_synchronizations, obj ) >= 0 )
880 pending_synchronizations += ({ obj });
881 // don't create users while synchronizing a user (this prevents
882 // creation of other members of the user's groups by lookup):
883 if ( obj->get_object_class() & CLASS_USER )
884 dont_create_users = true;
885 bool result = synchronize_object_internal( obj );
886 if ( obj->get_object_class() & CLASS_USER )
887 dont_create_users = false;
888 pending_synchronizations -= ({ obj });
894 * Used internally by synchronize_object() to prevent cyclic recursions.
895 * @see synchronize_object
898 bool synchronize_object_internal ( object obj ) {
899 if ( !objectp(obj) || !functionp(obj->status) )
901 int obj_status = obj->status();
902 if ( (obj_status != PSTAT_SAVE_OK) && (obj_status != PSTAT_SAVE_PENDING) )
904 if ( !functionp(obj->get_object_class) || !functionp(obj->get_identifier) )
906 int obj_class = obj->get_object_class();
907 string obj_class_name = "<unknown>";
908 if ( objectp(_Server->get_factory( obj_class )) )
909 obj_class_name = _Server->get_factory( obj_class )->get_class_name();
910 string identifier = obj->get_identifier();
911 if ( obj_class & CLASS_USER ) {
912 if ( user_restricted( identifier ) ) return false;
914 else if ( obj_class & CLASS_GROUP ) {
915 if ( group_restricted( identifier ) ) return false;
918 //TODO: right now only users and groups can be synchronized with persistence layers.
919 // we will need a mechanism for objects that are restricted to local
920 // persistence, or better yet, for objects that can explicitly be
921 // synchronized with data from persistence layers...
925 // prevent cyclic recursion (e.g. through cache lookups):
926 pending_synchronizations += ({ obj });
928 bool changes = false;
929 int obj_namespace = get_namespace_id( get_namespace( obj ) );
930 array source_namespaces = ({ });
931 array ignore_namespaces = ({ });
932 mapping data = ([ ]);
933 mapping nonpersistent_attributes = ([ ]);
934 array collected_users = ({ });
935 array collected_groups = ({ });
936 foreach(indices(namespaces), int nid ) {
937 if ( nid == obj_namespace ) continue; // don't load data from object's ns
938 object handler = namespaces[nid];
939 if ( functionp(handler->supported_classes) &&
940 (( handler->supported_classes() & obj_class ) == 0) ) {
941 ignore_namespaces += ({ nid });
944 if ( functionp(handler->load_data) ) {
945 mixed res = handler->load_data( obj );
946 if ( intp(res) && res < 0 ) {
947 // an error occurred, we cannot determine whether the object has data
949 ignore_namespaces += ({ nid });
951 else if ( mappingp(res) && (res["class"] == obj_class_name) ) {
952 if ( !stringp(res["class"]) ) {
953 PDEBUG("%s : no class entry in data from %s", identifier,
954 handler->get_identifier());
957 if ( res["class"] != obj_class_name ) {
958 PDEBUG("%s : wrong class '%s' data from %s for object of class '%s'",
959 identifier, res["class"], handler->get_identifier(),
964 if ( mappingp(res["nonpersistent-attributes"]) ) {
965 foreach ( indices(res["nonpersistent-attributes"]), mixed nonattr) {
966 nonpersistent_attributes[ nonattr ] = handler;
969 if ( arrayp(data["users"]) ) {
970 foreach( data["users"], string uname ) {
971 uname = lower_case( uname );
972 if ( search( collected_users, uname ) < 0 )
973 collected_users += ({ uname });
976 if ( arrayp(data["groups"]) ) {
977 foreach( data["groups"], string gname ) {
978 gname = lower_case( gname );
979 if ( search( collected_groups, gname ) < 0 )
980 collected_groups += ({ gname });
983 source_namespaces += ({ nid });
988 // check if a user has been suspended:
989 if ( obj_class & CLASS_USER ) {
990 if ( !zero_type(data["suspend"]) ) {
991 if ( data["suspend"] ) {
992 object auth = get_module( "auth" );
993 if ( ! auth->is_user_suspended( obj ) ) {
994 auth->suspend_user( obj, true );
995 PDEBUG( "suspended user %s", identifier );
999 object auth = get_module( "auth" );
1000 if ( auth->is_user_suspended( obj ) ) {
1001 auth->suspend_user( obj, false );
1002 PDEBUG( "unsuspended user %s", identifier );
1008 // check appearance, disappearance or reappearance of object in namespaces:
1009 array old_namespaces = obj->query_attribute( OBJ_NAMESPACES );
1010 if ( !arrayp(old_namespaces) ) old_namespaces = ({ });
1011 array ex_namespaces = obj->query_attribute( OBJ_EX_NAMESPACES );
1012 if ( !arrayp(ex_namespaces) ) ex_namespaces = ({ });
1014 string obj_type = "object";
1015 if ( obj_class & CLASS_USER ) obj_type = "user";
1016 else if ( obj_class & CLASS_GROUP ) obj_type = "group";
1018 //TODO: check whether the object has appeared and wasn't in the namespace before (the action must be configurable, too), also in lookup!!!
1020 // check whether the object has been removed from namespaces:
1021 array diff_namespaces = old_namespaces - source_namespaces - ex_namespaces
1022 - ignore_namespaces;
1023 if ( arrayp(diff_namespaces) && sizeof( diff_namespaces ) > 0 ) {
1024 foreach ( diff_namespaces, int nid ) {
1025 array actions = Config.array_value( config[obj_type+"-disappeared"] );
1026 if ( mappingp(namespace_configs[nid]) ) {
1027 array tmp = Config.array_value(
1028 namespace_configs[nid][obj_type+"-disappeared"] );
1029 if ( arrayp(tmp) && sizeof(tmp)>0 ) actions = tmp;
1031 if ( !arrayp(actions) ) actions = ({ });
1032 // only users can be deactivated:
1033 if ( (obj_class & CLASS_USER) == 0 ) actions -= ({ "deactivate" });
1034 actions = reverse( sort( actions ) ); // make sure "warn" comes first
1035 foreach ( actions, string action ) {
1038 mail_maintainer( nid, obj_type + " disappeared",
1039 "The %s '%s' has disappeared from %O. The following actions"
1040 + "have been taken: %O", obj_type, identifier,
1041 namespaces[nid]->get_identifier(), actions );
1044 PDEBUG( "%s '%s' disappeared from namespace %O, deleting.",
1045 obj_type, identifier, nid );
1047 int del_res = get_factory( CLASS_OBJECT )->delete_for_me( obj );
1048 PDEBUG( "deleted %s '%s' : %O", obj_type, identifier, del_res );
1051 FATAL( "Could not delete disappeared %s '%s': %s\n%O",
1052 obj_type, identifier, err[0], err[1] );
1057 // only users can be deactivated:
1058 if ( (obj_class & CLASS_USER) == 0 ) break;
1060 PDEBUG( "%s '%s' disappeared from namespace %O, suspending.",
1061 obj_type, identifier, nid );
1062 get_module("auth")->suspend_user( obj, true );
1066 ex_namespaces = obj->query_attribute( OBJ_EX_NAMESPACES );
1067 if ( !arrayp(ex_namespaces) ) ex_namespaces = ({ });
1068 ex_namespaces += ({ nid });
1069 obj->set_attribute( OBJ_EX_NAMESPACES, ex_namespaces );
1072 // check whether the object reappears in new namespaces:
1073 ex_namespaces = obj->query_attribute( OBJ_EX_NAMESPACES );
1074 if ( !arrayp(ex_namespaces) ) ex_namespaces = ({ });
1075 diff_namespaces = ex_namespaces & source_namespaces;
1076 if ( arrayp(diff_namespaces) && sizeof(diff_namespaces) > 0 ) {
1077 foreach ( diff_namespaces, mixed nid ) {
1078 array actions = Config.array_value( config[obj_type+"-reappeared"] );
1079 if ( mappingp(namespace_configs[nid]) ) {
1080 array tmp = Config.array_value(
1081 namespace_configs[nid][obj_type+"-reappeared"] );
1082 if ( arrayp(tmp) && sizeof(tmp)>0 ) actions = tmp;
1084 if ( !arrayp(actions) ) actions = ({ });
1085 // only users can be reactivated:
1086 if ( (obj_class & CLASS_USER) == 0 ) actions -= ({ "reactivate" });
1087 actions = reverse( sort( actions ) ); // make sure "warn" comes first
1088 foreach ( actions, string action ) {
1091 mail_maintainer( nid, obj_type + " reappeared",
1092 "The %s '%s' has reappeared from %O. The following actions"
1093 + "have been taken: %O", obj_type, identifier,
1094 namespaces[nid]->get_identifier(), actions );
1097 case "unsuspend" : {
1099 PDEBUG( "%s '%s' reappeared in namespace %O, unsuspending.",
1100 obj_type, identifier, nid );
1101 get_module("auth")->suspend_user( obj, false );
1104 PDEBUG( "%s '%s' reappeared in namespace %O, deleting.",
1105 obj_type, identifier, nid );
1110 ex_namespaces = obj->query_attribute( OBJ_EX_NAMESPACES );
1111 if ( !arrayp(ex_namespaces) ) ex_namespaces = ({ });
1112 ex_namespaces -= ({ nid });
1113 obj->set_attribute( OBJ_EX_NAMESPACES, ex_namespaces );
1119 mixed attributes = data["attributes"];
1120 if ( mappingp(attributes) && sizeof(attributes) > 0 ) {
1121 // namespaces are treated separately:
1122 m_delete( attributes, OBJ_NAMESPACES );
1123 m_delete( attributes, OBJ_EX_NAMESPACES );
1124 mixed old_attributes = obj->query_attributes();
1125 foreach( indices( attributes ), mixed key ) {
1126 if ( attributes[key] == old_attributes[key] )
1127 m_delete( attributes, key );
1129 obj->set_attributes( attributes );
1131 if ( obj_class & CLASS_USER ) {
1132 mixed new_email = old_attributes[ USER_EMAIL ];
1133 mixed old_email = old_attributes[ USER_EMAIL ];
1134 object fwmod = get_module("forward");
1135 if ( stringp(new_email) && new_email != old_email && objectp(fwmod) ) {
1136 PDEBUG("updating forwards for %s (USER_EMAIL changed)", identifier);
1137 if ( stringp(old_email) ) {
1138 PDEBUG( "deleting old forward for %s : %s", identifier, old_email );
1139 fwmod->delete_forward( obj, old_email );
1141 fwmod->add_forward( obj, new_email );
1142 PDEBUG( "adding new forward for %s : %s", identifier, new_email );
1148 // handle non-persistent attributes:
1149 if ( mappingp(nonpersistent_attributes) &&
1150 sizeof(nonpersistent_attributes) > 0 ) {
1151 foreach ( indices(nonpersistent_attributes), mixed nonattr ) {
1152 mixed nonattr_handler = nonpersistent_attributes[ nonattr ];
1153 if ( objectp(nonattr_handler) &&
1154 functionp(nonattr_handler->query_attribute) ) {
1159 // sync user options:
1160 if ( obj_class & CLASS_USER ) {
1161 PDEBUG("syncing user %s %O", identifier, obj );
1164 if ( stringp(data["password"]) && sizeof(data["password"]) > 0 ) {
1166 call_storage_handler( obj->restore_user_data, data["password"],
1171 // sync group membership:
1172 if ( arrayp(data["groups"]) ) {
1173 array groups = data["groups"];
1174 int crc32 = Gz.crc32( sort(groups) * "," );
1175 mixed old_crc32 = obj->query_attribute( USER_NAMESPACE_GROUPS_CRC );
1176 if ( zero_type(old_crc32) )
1177 old_crc32 = crc32 + 1; // assume different crc32 by default
1178 // only sync if groups seem to have changed:
1179 if ( crc32 != old_crc32 ) {
1180 PDEBUG( "synchronizing groups of user %s : %O", identifier, groups );
1181 foreach ( obj->get_groups(), object group ) {
1182 string group_name = group->get_group_name();
1183 if ( search( groups, group_name ) >= 0 ) {
1184 groups -= ({ group_name }); // already member of group
1186 else if ( lower_case( group_name ) != "steam" ) {
1187 array group_namespaces = group->query_attribute( OBJ_NAMESPACES );
1188 if ( ! arrayp(group_namespaces) || sizeof(group_namespaces) < 1 )
1190 group_namespaces -= source_namespaces;
1191 // only remove user from group if they're from the same namespaces:
1192 if ( sizeof(group_namespaces) < 1 ) {
1193 remove_user_from_group( obj, group );
1197 foreach ( groups, string group_name ) {
1198 object group = lookup_group( group_name );
1199 add_user_to_group( obj, group );
1201 obj->set_attribute( USER_NAMESPACE_GROUPS_CRC, crc32 );
1204 PDEBUG( "crc32 check: user %s groups don't seem to have changed: %O",
1205 identifier, (groups * ",") );
1210 // sync active group:
1211 if ( stringp(data["active_group"]) && sizeof(data["active_group"]) > 0 ) {
1212 object active_group = lookup_group( data["active_group"] );
1213 if ( objectp(active_group) &&
1214 (obj->get_active_group() != active_group) ) {
1215 if ( !active_group->is_member( obj ) )
1216 active_group->add_member( obj );
1217 if ( active_group->is_member( obj ) ) {
1218 obj->set_active_group( active_group );
1225 // sync group options:
1226 else if ( obj_class & CLASS_GROUP ) {
1227 PDEBUG("syncing group %s %O", identifier, obj );
1230 if ( stringp(data["parentgroup"]) ) {
1231 PDEBUG( "Group %s : name %O, parent %O\n",
1232 identifier, data["name"], data["parentgroup"] );
1233 object parent = obj->get_parent();
1234 if ( !objectp(parent) ||
1235 lower_case(parent->get_group_name()) != lower_case(data["parentgroup"]) ) {
1236 parent = lookup_group( data["parentgroup"] );
1237 if ( !objectp(parent) )
1238 FATAL( "Could not find parent group %s for group %s",
1239 data["parentgroup"], obj->get_group_name() );
1241 PDEBUG("moving group %O to parent group %O", obj, parent);
1242 get_factory( CLASS_GROUP )->move_group( obj, parent );
1247 // sync group memberships:
1248 mixed users = data["users"];
1249 if ( !arrayp(users) ) users = ({ });
1250 int crc32 = Gz.crc32( sort(users) * "," );
1251 mixed old_crc32 = obj->query_attribute( GROUP_NAMESPACE_USERS_CRC );
1252 if ( zero_type(old_crc32) )
1253 old_crc32 = crc32 + 1; // assume different crc32 by default
1254 // only sync if users seem to have changed:
1255 if ( crc32 != old_crc32 ) {
1256 PDEBUG( "synchronizing members of group %s : %O", identifier, users );
1257 foreach ( obj->get_members(), object user ) {
1258 if ( !(user->get_object_class() & CLASS_GROUP) ) continue;
1259 string user_name = user->get_user_name();
1260 if ( search( users, user_name ) >= 0 ) {
1261 users -= ({ user_name }); // already member of group
1264 array user_namespaces = user->query_attribute( OBJ_NAMESPACES );
1265 if ( ! arrayp(user_namespaces) || sizeof(user_namespaces) < 1 )
1267 array tmp_namespaces = copy_value( source_namespaces );
1268 tmp_namespaces -= user_namespaces;
1269 // only remove user from group if they're from the same namespaces:
1270 if ( sizeof(tmp_namespaces) < 1 )
1271 remove_user_from_group( user, obj );
1274 foreach ( users, string user_name ) {
1275 object user = lookup_user( user_name );
1276 add_user_to_group( user, obj );
1278 obj->set_attribute( GROUP_NAMESPACE_USERS_CRC, crc32 );
1280 else PDEBUG( "crc32 check: group %s members don't seem to have " +
1281 "changed: %O", identifier, users );
1284 mixed subgroups = data["groups"];
1285 if ( !arrayp(subgroups) ) subgroups = ({ });
1286 crc32 = Gz.crc32( sort(subgroups) * "," );
1287 old_crc32 = crc32 + 1; // assume different crc32 by default
1288 if ( !zero_type(obj->query_attribute( GROUP_NAMESPACE_GROUPS_CRC )) )
1289 old_crc32 = obj->query_attribute( GROUP_NAMESPACE_GROUPS_CRC );
1290 // only sync if sub groups seem to have changed:
1291 if ( crc32 != old_crc32 ) {
1292 PDEBUG( "synchronizing sub groups of group %s", identifier );
1293 foreach ( obj->get_sub_groups(), object subgroup ) {
1294 if ( !(subgroup->get_object_class() & CLASS_GROUP) ) continue;
1295 string subgroup_name = subgroup->query_attribute( OBJ_NAME );
1296 if ( !stringp(subgroup_name) ) continue;
1297 subgroup_name = lower_case( subgroup_name );
1298 if ( search( subgroups, subgroup_name ) >= 0 ) {
1299 subgroups -= ({ subgroup_name }); // already sub group of group
1302 array subgroup_namespaces = subgroup->query_attribute( OBJ_NAMESPACES );
1303 if ( ! arrayp(subgroup_namespaces) || sizeof(subgroup_namespaces) < 1 )
1305 array tmp_namespaces = copy_value( source_namespaces );
1306 tmp_namespaces -= subgroup_namespaces;
1307 // only remove user from group if they're from the same namespaces:
1308 if ( sizeof(tmp_namespaces) < 1 ) {
1309 object privgroups = GROUP( "Groups" );
1310 if ( objectp(privgroups) ) {
1311 PDEBUG( "removing sub group %s of group %s (moving to Groups)", subgroup_name, identifier );
1312 get_factory( CLASS_GROUP )->move_group( subgroup, privgroups );
1314 // what if we cannot move the group to privgroups?
1318 foreach ( subgroups, string subgroup_name ) {
1319 object subgroup = lookup_group( subgroup_name );
1320 PDEBUG( "adding sub group %s to group %s", subgroup_name, identifier );
1321 if ( objectp(subgroup) )
1322 get_factory( CLASS_GROUP )->move_group( subgroup, obj );
1324 obj->set_attribute( GROUP_NAMESPACE_GROUPS_CRC, crc32 );
1326 else PDEBUG( "crc32 check: group %s sub groups don't seem to have " +
1327 "changed: %O", identifier, subgroups );
1330 // store namespaces that had data:
1332 if ( functionp(obj->set_attribute) )
1333 obj->set_attribute( OBJ_NAMESPACES, source_namespaces );
1336 werror( "persistence: synchronize_object() error when trying to store " +
1337 "source namespaces in %O: %O\n%O\n%O\n",
1338 obj->get_identifier(), obj, err[0], err[1] );
1341 //TODO: update data in the persistence layers if the object has changed
1347 int|object load_object(object proxy, int|object iOID)
1349 if ( object_program(CALLER) != (program)PROXY )
1350 error("Security Violation - caller not a proxy object !");
1352 object nid = get_namespace(iOID);
1353 if ( !objectp(nid) ) {
1354 werror("Namespace is not an object: %O (proxy: %O)\n", iOID, proxy );
1357 if ( !functionp(nid->load_object) ) return UNDEFINED;
1358 mixed obj = nid->load_object(proxy, iOID);
1359 if ( !objectp(obj) ) return obj;
1361 if ( objectp(cache_entry) )
1362 cache_entry->time_loaded = time();
1364 PDEBUG( "could not cache object %O : %O", obj->get_identifier(), obj );
1369 mapping get_storage_handlers(object o)
1376 m = o->get_data_storage();
1379 FATAL("Error calling get_data_storage in %O ", o);
1380 FATAL("Error: %O\n%O", err[0], err[1]);
1386 mixed call_storage_handler(function f, mixed ... params)
1388 mixed res = namespaces[0]->call_storage_handler(f, @params);
1393 int check_read_attribute(object user, string attribute)
1395 mixed err = catch(user->check_read_attribute(attribute,
1396 geteuid()||this_user()));
1398 if ( sizeof(err) == 3)
1400 FATAL("Error while checking for readable attribute %O of %O\n%O:%O",
1401 attribute, user, err[0], err[1]);
1407 int check_read_user(object user, mapping terms)
1409 // check if user data are still ok ?!
1410 array attribute = ({ });
1411 foreach ( indices(terms), string key) {
1414 attribute += ({ USER_FIRSTNAME });
1417 attribute += ({ USER_LASTNAME });
1420 attribute += ({ OBJ_NAME });
1423 attribute += ({ USER_EMAIL });
1427 if (sizeof(attribute)>0) {
1428 foreach(attribute, string a)
1429 if ( check_read_attribute(user, a) == 0 )
1438 int get_content_size ( int content_id ) {
1442 if ( store_content_in_filesystem ) {
1443 content_size = Stdio.file_size( _Server->get_sandbox_path() + "/content/" +
1444 ContentFilesystem.content_id_to_path( content_id ) );
1445 if ( content_size >= 0 ) return content_size;
1451 if ( store_content_in_database ) {
1452 object oHandle = __Database->new_db_file_handle( content_id, "r" );
1453 content_size = oHandle->sizeof();
1454 destruct( oHandle );
1459 return content_size;
1463 string get_content ( int content_id, int|void length ) {
1467 if ( store_content_in_filesystem ) {
1469 content = Stdio.read_file( _Server->get_sandbox_path() + "/content/" +
1470 ContentFilesystem.content_id_to_path( content_id ), 0, length );
1472 content = Stdio.read_file( _Server->get_sandbox_path() + "/content/" +
1473 ContentFilesystem.content_id_to_path( content_id ) );
1474 if ( stringp(content) )
1481 if ( store_content_in_database ) {
1482 object db_handle = __Database->new_db_file_handle( content_id, "r" );
1483 content = db_handle->read( length );
1484 destruct( db_handle );
1492 int set_content ( string content ) {
1493 object oHandle = __Database->new_db_file_handle( 0, "wct" );
1494 int content_id = oHandle->dbContID();
1497 if ( store_content_in_filesystem ) {
1498 string path = _Server->get_sandbox_path() + "/content/" +
1499 ContentFilesystem.content_id_to_path( content_id );
1500 Stdio.mkdirhier( dirname( path ) );
1501 Stdio.write_file( path, content );
1507 if ( store_content_in_database )
1508 oHandle->write_now( content );
1511 destruct( oHandle );
1516 void delete_content ( int content_id ) {
1518 if ( store_content_in_filesystem )
1519 rm( _Server->get_sandbox_path() + "/content/" +
1520 ContentFilesystem.content_id_to_path( content_id ) );
1523 if ( store_content_in_database ) {
1524 object oHandle = __Database->new_db_file_handle( content_id, "wct" );
1533 * Open a file, return a file object or zero, called from get_content_file
1536 * @return an open Stdio.File
1538 object open_content_file(int content_id, string mode, void|mapping vars, void|string client)
1541 if ( store_content_in_filesystem ) {
1543 return Stdio.File( _Server->get_sandbox_path() + "/content/" +
1544 ContentFilesystem.content_id_to_path( content_id ), mode );
1546 if ( err ) PDEBUG( "Couldn't open content file: %s\n", err[0] );
1554 array search_user_names ( mapping terms, bool any, string|void wildcard ) {
1555 array users = ({ });
1556 foreach ( indices(namespaces), mixed idx ) {
1557 object handler = namespaces[idx];
1558 if ( (handler->supported_classes() & CLASS_USER) == 0 )
1560 if ( functionp(handler->search_users) ) {
1561 array a = handler->search_users( terms, any, wildcard );
1562 if ( !arrayp(a) || sizeof(a) < 1 ) continue;
1566 return Array.uniq( users );
1571 * Searches for users in the persistence layers.
1573 * @param terms a mapping ([ attrib : value ]) where attrib can be "firstname",
1574 * "lastname", "login" or "email" and value is the text ot search for in the
1575 * attribute. If the values contain wildcards, specify the wildcard character
1576 * in the wildcard param.
1577 * @param any true: return all users that match at least one of the terms
1578 * ("Or"), false: return all users that match all of the terms ("and").
1579 * @param wildcard a string containing the wildcard used in the search term
1580 * values, or 0 (or unspecified) if no wildcards are used
1581 * @return an array of matching user objects
1583 array lookup_users( mapping terms, bool any, string|void wildcard ) {
1584 array users = ({ });
1585 //foreach ( indices(namespaces), mixed idx ) {
1586 for ( mixed idx = 0; idx == 0; idx ++ ) { // only search in database!!!
1587 object handler = namespaces[idx];
1588 if ( (handler->supported_classes() & CLASS_USER) == 0 )
1590 if ( functionp(handler->search_users) ) {
1591 array a = handler->search_users( terms, any, wildcard );
1592 if ( arrayp(a) && sizeof(a) > 0 ) {
1593 foreach ( a, string name ) {
1594 object user = lookup_user( name );
1595 if ( objectp(user) &&
1596 search( users, user ) < 0 &&
1597 check_read_user(user, terms))
1598 users += ({ user });
1607 array search_group_names ( mapping terms, bool any, string|void wildcard ) {
1608 array groups = ({ });
1609 foreach ( indices(namespaces), mixed idx ) {
1610 object handler = namespaces[idx];
1611 if ( (handler->supported_classes() & CLASS_GROUP) == 0 )
1613 if ( functionp(handler->search_groups) ) {
1614 array a = handler->search_groups( terms, any, wildcard );
1615 if ( !arrayp(a) || sizeof(a) < 1 ) continue;
1619 return Array.uniq( groups );
1624 * Searches for groups in the persistence layers.
1626 * @param terms a mapping ([ attrib : value ]) where attrib can be "name"
1627 * and value is the text ot search for in the attribute.
1628 * If the values contain wildcards, specify the wildcard character in the
1630 * @param any true: return all groups that match at least one of the terms
1631 * ("or"), false: return all groups that match all of the terms ("and").
1632 * @param wildcard a string containing the wildcard used in the search term
1633 * values, or 0 (or unspecified) if no wildcards are used
1634 * @return an array of matching group objects
1636 array lookup_groups ( mapping terms, bool any, string|void wildcard ) {
1637 array groups = ({ });
1638 //foreach ( indices(namespaces), mixed idx ) {
1639 for ( mixed idx = 0; idx == 0; idx ++ ) { // only search in database!!!
1640 object handler = namespaces[idx];
1641 if ( (handler->supported_classes() & CLASS_GROUP) == 0 )
1643 if ( functionp(handler->search_groups) ) {
1644 array a = handler->search_groups( terms, any, wildcard );
1645 if ( arrayp(a) && sizeof(a) > 0 ) {
1646 foreach ( a, string name ) {
1647 object group = lookup_group( name );
1648 if ( objectp(group) && search( groups, group ) < 0 )
1649 groups += ({ group });
1658 object lookup ( string identifier ) {
1659 if ( !stringp(identifier) || sizeof(identifier) < 1 ) return 0;
1660 if ( has_index( pending_objects, identifier ) ) {
1661 PDEBUG("pending object %s : %O", identifier, pending_objects[identifier]);
1662 return pending_objects[identifier];
1664 return lookup_internal( identifier, CLASS_OBJECT );
1668 object lookup_user ( string identifier, void|string password ) {
1669 if ( !stringp(identifier) || sizeof(identifier) < 1 ) return 0;
1670 if ( has_index( pending_users, identifier ) ) {
1671 PDEBUG("pending user %s : %O", identifier, pending_users[identifier]);
1672 return pending_users[identifier];
1674 if ( user_restricted( identifier ) )
1675 return namespaces[0]->lookup_user( identifier );
1676 return lookup_internal( identifier, CLASS_USER, password );
1680 object lookup_group ( string identifier ) {
1681 if ( !stringp(identifier) || sizeof(identifier) < 1 ) return 0;
1682 if ( has_index( pending_groups, identifier ) ) {
1683 PDEBUG("pending group %s : %O", identifier, pending_groups[identifier]);
1684 return pending_groups[identifier];
1686 if ( group_restricted( identifier ) )
1687 return namespaces[0]->lookup_group( identifier );
1688 return lookup_internal( identifier, CLASS_GROUP );
1693 object lookup_internal ( string identifier, int obj_class, void|string password ) {
1694 // check whether object is cached:
1695 array cache_entries = object_cache->find_by_identifier( identifier );
1696 if ( arrayp(cache_entries) ) {
1697 foreach ( cache_entries, object entry ) {
1698 if ( entry->obj_class & obj_class ) {
1699 //TODO: check whether object is "fresh", otherwise don't use cache
1700 object_cache->fetch( entry->proxy );
1701 return entry->proxy;
1706 // find object in persistence layers:
1708 foreach(indices(namespaces), mixed idx ) {
1709 object handler = namespaces[idx];
1710 if ( (handler->supported_classes() & obj_class) == 0 )
1712 string lookup_func = "lookup";
1713 string lookup_data_func = "lookup_data";
1714 if ( obj_class & CLASS_USER ) {
1715 lookup_func = "lookup_user";
1716 lookup_data_func = "lookup_user_data";
1718 else if ( obj_class & CLASS_GROUP ) {
1719 lookup_func = "lookup_group";
1720 lookup_data_func = "lookup_group_data";
1722 if ( functionp(handler[lookup_func]) ) {
1723 mixed tmp_res = handler[lookup_func]( identifier );
1724 if ( objectp(tmp_res) ) {
1725 // only proxies are cached, not the objects themselves:
1726 // do a cache fetch so that the data can be updated if out of date:
1727 object_cache->fetch( res );
1731 else if ( functionp( handler[lookup_data_func] ) ) {
1732 if ( !res ) res = handler[lookup_data_func]( identifier );
1734 else PDEBUG( "No lookup methods in layer %O (%O)", idx, handler->get_identifier() );
1737 mixed requested_identifier = identifier;
1738 if ( mappingp(res) && stringp(res["name"]) && sizeof(res["name"]) != 0 ) {
1739 if ( stringp(res["parentgroup"]) && sizeof(res["parentgroup"]) != 0 )
1740 identifier = res["parentgroup"] + "." + res["name"];
1742 identifier = res["name"];
1744 // if we found only partial data, then we need to create a new object:
1745 //TODO: make this configurable! You might not want to automatically create new objects in all layers!
1746 if ( dont_create_users && (obj_class == CLASS_USER) )
1748 if ( (obj_class == CLASS_USER) && !user_allowed( identifier ) ) {
1749 PDEBUG("lookup: user not allowed: %O", identifier);
1752 if ( (obj_class == CLASS_GROUP) && !group_allowed( identifier ) ) {
1753 PDEBUG("lookup: group not allowed: %O", identifier);
1756 if ( mappingp(res) ) {
1757 PDEBUG( "lookup: found data for %O, creating object %O",
1758 requested_identifier, identifier );
1760 if ( !stringp(res["class"]) ||
1761 !objectp(factory = get_factory(res["class"])) ) {
1762 werror( "persistence: cannot create object for %s : invalid class: %O\n",
1763 identifier, res["class"] );
1766 mapping factory_params = ([ "name":identifier ]) | res;
1767 if ( (factory_params["class"] == CLASS_NAME_USER) &&
1768 !stringp(factory_params["pw"]) &&
1769 !stringp(factory_params["pw:crypt"]) )
1770 factory_params["pw:crypt"] = "{NOPASSWORD}";
1771 if ( (factory_params["class"] == CLASS_NAME_GROUP) &&
1772 stringp(factory_params["parentgroup"]) ) {
1773 factory_params["parentgroup"] = lookup_group( factory_params["parentgroup"] );
1774 if ( !objectp( factory_params["parentgroup"] ) )
1777 if ( obj_class & CLASS_USER ) pending_users[identifier] = 0;
1778 else if ( obj_class & CLASS_GROUP ) pending_groups[identifier] = 0;
1779 else pending_objects[identifier] = 0;
1780 object obj = factory->execute( factory_params );
1781 if ( obj_class & CLASS_USER ) m_delete( pending_users, identifier );
1782 else if ( obj_class & CLASS_GROUP ) m_delete( pending_groups, identifier );
1783 else m_delete( pending_objects, identifier );
1784 if ( !objectp(obj) ) {
1785 werror( "persistence: failed to create object for %s\n", identifier );
1788 if ( obj_class & CLASS_USER )
1789 obj->activate_user( factory->get_activation() );
1790 if ( mappingp(res["attributes"]) )
1791 obj->set_attributes( res["attributes"] );
1792 // synchronize object for the first time:
1793 object_cache->fetch( obj );
1797 if ( !objectp(res) )
1798 res = namespaces[0]->lookup(identifier);
1800 object_cache->fetch( res );
1808 void add_user_to_group ( object user, object group )
1810 if ( !objectp(user) || !objectp(group) )
1813 PDEBUG( "adding user %s to group %s", user->get_identifier(),
1814 group->get_identifier() );
1815 group->add_member( user );
1817 object user_workroom = user->query_attribute(USER_WORKROOM);
1818 object group_workroom = group->query_attribute(GROUP_WORKROOM);
1819 if ( !objectp(user_workroom) || !objectp(group_workroom) )
1822 // add exit to the group workroom if it doesn't exist:
1823 if ( get_dont_create_exits() )
1824 return; // configured not to create exits
1825 foreach ( user_workroom->get_inventory_by_class(CLASS_EXIT), object gate ) {
1826 if ( gate->get_exit() == group_workroom )
1827 return; // exit already exists
1829 object exit_factory = get_factory(CLASS_EXIT);
1830 if ( !objectp(exit_factory) ) {
1831 werror( "Persistence: Could not get ExitFactory.\n" );
1834 object gate = exit_factory->execute( ([
1835 "name":group_workroom->get_identifier(),
1836 "exit_to":group_workroom
1838 if ( objectp(gate) )
1839 gate->move( user_workroom );
1841 werror( "Persistence: Could not move exit to group workroom to users workarea:\nUser: %O, Group: %O\n", user, group );
1848 void remove_user_from_group ( object user, object group )
1850 if ( !objectp(user) || !objectp(group) )
1853 PDEBUG( "removing user %s from group %s", user->get_identifier(),
1854 group->get_identifier() );
1855 group->remove_member( user ); // not member of group anymore
1857 // check whether an exit to the group workroom needs to be removed:
1858 object user_workroom = user->query_attribute(USER_WORKROOM);
1859 object group_workroom = group->query_attribute(GROUP_WORKROOM);
1860 if ( !objectp(user_workroom) || !objectp(group_workroom) )
1862 foreach ( user_workroom->get_inventory_by_class(CLASS_EXIT), object gate ) {
1863 if ( gate->get_exit() == group_workroom )
1871 final object find_object(int|string iOID)
1874 if ( stringp(iOID) )
1875 return namespaces[0]->find_object(iOID);
1876 namespace = get_namespace(iOID);
1877 if ( !objectp(namespace) || !functionp(namespace->find_object) )
1879 return namespace->find_object(iOID);
1883 // requires saving an object
1884 void require_save(void|string ident, void|string index)
1886 object nid = get_namespace(proxy);
1887 if ( !objectp(nid) )
1888 werror( "require_save: invalid namespace\n" );
1889 if ( !functionp(nid->require_save) )
1891 nid->require_save(proxy, ident, index);
1897 save_object(object proxy, void|string ident, void|string index)
1903 int change_object_class(object proxy, string newClass)
1905 if ( !_Server->is_a_factory(CALLER) )
1906 steam_error("Illegal call to Persistence.change_object_class !");
1907 object nid = get_namespace(proxy);
1908 if ( !objectp(nid) )
1909 steam_error("Persistence.change_object_class failed: no namespace found!");
1910 if ( !functionp(nid->change_object_class) ) {
1911 FATAL("No function change_object_class in %O\n", nid);
1914 return nid->change_object_class(proxy, newClass);
1918 void user_renamed ( object user, string old_name, string new_name ) {
1919 uncache_object( user );
1920 array errors = ({ });
1921 foreach ( indices(namespaces), mixed idx ) {
1922 if ( idx == 0 ) continue;
1923 object handler = namespaces[idx];
1924 if ( !functionp(handler->user_renamed) ) continue;
1925 handler->user_renamed( user, old_name, new_name );
1930 void group_renamed ( object group, string old_name, string new_name ) {
1931 uncache_object( group );
1932 foreach ( indices(namespaces), mixed idx ) {
1933 if ( idx == 0 ) continue;
1934 object handler = namespaces[idx];
1935 if ( !functionp(handler->group_renamed) ) continue;
1936 handler->group_renamed( group, old_name, new_name );
1941 string get_identifier() { return "PersistenceManager"; }
1942 string describe() { return "PersistenceManager"; }
1943 string _sprintf() { return "PersistenceManager"; }