1 /*
2  * CDDL HEADER START
3  *
4  * The contents of this file are subject to the terms of the
5  * Common Development and Distribution License (the "License").
6  * You may not use this file except in compliance with the License.
7  *
8  * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
9  * or http://www.opensolaris.org/os/licensing.
10  * See the License for the specific language governing permissions
11  * and limitations under the License.
12  *
13  * When distributing Covered Code, include this CDDL HEADER in each
14  * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
15  * If applicable, add the following below this CDDL HEADER, with the
16  * fields enclosed by brackets "[]" replaced with your own identifying
17  * information: Portions Copyright [yyyy] [name of copyright owner]
18  *
19  * CDDL HEADER END
20  */
21 /*
22  * Copyright 2006 Sun Microsystems, Inc.  All rights reserved.
23  * Use is subject to license terms.
24  */
25 
26 #pragma ident	"%Z%%M%	%I%	%E% SMI"
27 
28 /*
29  * DESCRIPTION:	Contains functions relating to the creation and manipulation
30  *		of map_ctrl structures. These are used to hold information
31  *		specific to one NIS map.
32  *
33  *		Because each of these contains a significant amount of state
34  *		information about an individual map they are created (on the
35  *		heap) when a map is opened and destroyed when it is closed.
36  *		The overhead of doing this is less than maintaining a pool
37  *		of map_ctrls.
38  *
39  *		If two processes access the same map two map_ctrls will be
40  *		created with similar contents (but differing DBM pointers).
41  *		Both will have the same hash value so when one is locked
42  *		access to the other will also be prevented.
43  */
44 
45 #include <unistd.h>
46 #include <stdlib.h>
47 #include <syslog.h>
48 #include <ndbm.h>
49 #include <string.h>
50 #include <sys/param.h>
51 #include "ypsym.h"
52 #include "ypdefs.h"
53 #include "shim.h"
54 #include "yptol.h"
55 #include "../ldap_util.h"
56 
57 extern int hash(char *s);
58 extern bool_t add_map_domain_to_list(char *domain, char ***map_list);
59 
60 /*
61  * Static variables for locking mechanism in
62  * N2L mode.
63  *  map_id_list: hash table for map lists
64  *  max_map: max number of maps in map_id_list
65  *      it is also used as the map ID for
66  *      unknown maps, see get_map_id()
67  *      in usr/src/cmd/ypcmd/shared/lockmap.c
68  */
69 static map_id_elt_t *map_id_list[MAXHASH];
70 static int max_map = 0;
71 
72 /* Switch on parts of ypdefs.h */
73 USE_DBM
74 USE_YPDBPATH
75 
76 /*
77  * FUNCTION: 	create_map_ctrl();
78  *
79  * DESCRIPTION: Create and a new map_ctrl in a non opened state.
80  *
81  * INPUTS:	Fully qualified map name
82  *
83  * OUTPUTS:	Pointer to map_ctrl
84  *		NULL on failure.
85  *
86  */
87 map_ctrl *
88 create_map_ctrl(char *name)
89 {
90 	char *myself = "create_map_ctrl";
91 	map_ctrl *map;
92 
93 	map = (map_ctrl *)am(myself, sizeof (map_ctrl));
94 	if (NULL == map) {
95 		logmsg(MSG_NOTIMECHECK, LOG_ERR, "Could not alloc map_ctrl");
96 		return (NULL);
97 	}
98 
99 	/* Clear new map (in case we have to free it) */
100 	map->entries = NULL;
101 	map->hash_val = 0;
102 	map->map_name = NULL;
103 	map->domain = NULL;
104 	map->map_path = NULL;
105 	map->ttl = NULL;
106 	map->ttl_path = NULL;
107 	map->trad_map_path = NULL;
108 	map->key_data.dptr = NULL;
109 	map->open_mode = 0;
110 	map->open_flags = 0;
111 
112 	/*
113 	 * Initialize the fields of the map_ctrl. By doing this once here we
114 	 * can save a lot of work as map entries are accessed.
115 	 */
116 	if (SUCCESS != map_ctrl_init(map, name)) {
117 		logmsg(MSG_NOTIMECHECK, LOG_ERR,
118 				"Could not initialize map_ctrl for %s", name);
119 		free_map_ctrl(map);
120 		return (NULL);
121 	}
122 
123 	return (map);
124 }
125 
126 /*
127  * FUNCTION :	map_ctrl_init()
128  *
129  * DESCRIPTION:	Initializes the fields of a map_ctrl structure.
130  *
131  *		By doing this once (when the map_ctrl is created) we avoid
132  *		numerous other function having to repeat this string
133  *		manipulation.
134  *
135  * GIVEN :	Pointer to the structure
136  *		Fully qualified name of the map
137  *
138  * RETURNS :	SUCCESS = map_ctrl fully set up.
139  *		FAILURE = map_ctrl not set up CALLER MUST FREE.
140  */
141 suc_code
142 map_ctrl_init(map_ctrl *map, char *name)
143 {
144 	char *myself = "map_ctrl_init";
145 	char *p, *q;
146 
147 	/* Save map path for future reference */
148 	map->map_path = (char *)strdup(name);
149 	if (NULL ==  map->map_path) {
150 		logmsg(MSG_NOMEM, LOG_ERR,
151 				"Could not duplicate map path %s", map);
152 		return (FAILURE);
153 	}
154 
155 	/* Work out map's unqualified name from path */
156 	p = strrchr(name, SEP_CHAR);
157 	if (NULL == p) {
158 		/* Must be at least a domain and name */
159 		logmsg(MSG_NOTIMECHECK, LOG_ERR,
160 			"Could not find separator in map path %s", map);
161 		return (FAILURE);
162 	}
163 	q = p + 1;
164 
165 	/* Check for and remove N2L prefix */
166 	if (yptol_mode) {
167 		/*
168 		 * Check for and remove N2L prefix. If not found not a problem
169 		 * we open some old style maps during DIT initialization.
170 		 */
171 		if (0 == strncmp(q, NTOL_PREFIX, strlen(NTOL_PREFIX)))
172 			q += strlen(NTOL_PREFIX);
173 	} else {
174 		if (0 == strncmp(q, NTOL_PREFIX, strlen(NTOL_PREFIX)))
175 			logmsg(MSG_NOTIMECHECK, LOG_ERR,
176 				"Working in non N2L mode and path %s "
177 				"contains N2L prefix", name);
178 	}
179 
180 	/* Save unqualified map name */
181 	map->map_name = strdup(q);
182 	if (NULL == map->map_name) {
183 		logmsg(MSG_NOMEM, LOG_ERR,
184 				"Could not duplicate map name %s", q);
185 		return (FAILURE);
186 	}
187 
188 	/* Work out map's domain name from path */
189 	for (q = p-1; (SEP_CHAR != *q) && (q > name); q--);
190 
191 	if (q <= name) {
192 		/* Didn't find separator */
193 		logmsg(MSG_NOTIMECHECK, LOG_ERR,
194 				"Could not find domain in map path %s", name);
195 		return (FAILURE);
196 	}
197 
198 	map->domain = (char *)am(myself, p - q);
199 	if (NULL == map->domain) {
200 		logmsg(MSG_NOMEM, LOG_ERR,
201 			"Could not alloc memory for domain in path %s", name);
202 		return (FAILURE);
203 	}
204 	(void) strncpy(map->domain, q + 1, p-q-1);
205 	map->domain[p-q-1] = '\0';
206 
207 	/* Work out extra names required by N2L */
208 	if (yptol_mode) {
209 		/*
210 		 * Work out what old style NIS path would have been. This is
211 		 * used to check for date of DBM file so add the DBM
212 		 * extension.
213 		 */
214 		map->trad_map_path = (char *)am(myself, strlen(map->map_name) +
215 					+ strlen(dbm_pag) + (p - name) + 2);
216 		if (NULL == map->trad_map_path) {
217 			logmsg(MSG_NOMEM, LOG_ERR,
218 				"Could not alocate memory for "
219 				"traditional map path derived from %s", name);
220 			return (FAILURE);
221 		}
222 
223 		strncpy(map->trad_map_path, name, p - name + 1);
224 		map->trad_map_path[p - name + 1] = '\0';
225 		strcat(map->trad_map_path, map->map_name);
226 		strcat(map->trad_map_path, dbm_pag);
227 
228 		/* Generate qualified TTL file name */
229 		map->ttl_path = (char *)am(myself, strlen(map->map_path) +
230 						strlen(TTL_POSTFIX) + 1);
231 		if (NULL == map->ttl_path) {
232 			logmsg(MSG_NOMEM, LOG_ERR,
233 				"Could not alocate memory for "
234 				"ttl path derived from %s", name);
235 			return (FAILURE);
236 		}
237 
238 		strcpy(map->ttl_path, map->map_path);
239 		strcat(map->ttl_path, TTL_POSTFIX);
240 	}
241 
242 	/* Work out hash value */
243 	map->hash_val = hash(name);
244 
245 	/* Set up magic number */
246 	map->magic = MAP_MAGIC;
247 
248 	/* Null out pointers */
249 	map->entries = NULL;
250 	map->ttl = NULL;
251 
252 	/* No key data yet */
253 	map->key_data.dptr = NULL;
254 	map->key_data.dsize = 0;
255 
256 	return (SUCCESS);
257 }
258 
259 /*
260  * FUNCTION: 	get_map_crtl();
261  *
262  * DESCRIPTION: Find an existing map_ctrl for a map of a given DBM * (i.e.
263  *		handle) . If none exists return an error.
264  *
265  * INPUTS:	Map handle
266  *
267  * OUTPUTS:	Pointer to map_ctrl
268  *		NULL on failure.
269  *
270  */
271 map_ctrl *
272 get_map_ctrl(DBM *db)
273 {
274 	/* Check that this really is a map_ctrl not a DBM */
275 	if (((map_ctrl *)db)->magic != MAP_MAGIC) {
276 		logmsg(MSG_NOTIMECHECK, LOG_ERR,
277 				"SHIM called with DBM ptr not map_crtl ptr");
278 		return (NULL);
279 	}
280 
281 	/* Since this is an opaque pointer just cast it */
282 	return ((map_ctrl *)db);
283 }
284 
285 /*
286  * FUNCTION:	dup_map_ctrl()
287  *
288  * DESCRIPTION:	Duplicates a map_ctrl structure
289  *
290  * GIVEN :	Map_ctrl to duplicate
291  *
292  * RETURNS :	Pointer to a new malloced map_ctrl. CALLER MUST FREE
293  *		NULL on failure.
294  */
295 map_ctrl *
296 dup_map_ctrl(map_ctrl *old_map)
297 {
298 	map_ctrl *new_map;
299 
300 	/*
301 	 * Could save a little bit of time by duplicating the static parts
302 	 * of the old map but on balance it is cleaner to just make a new one
303 	 * from scratch
304 	 */
305 	new_map = create_map_ctrl(old_map->map_path);
306 
307 	if (NULL == new_map)
308 		return (NULL);
309 
310 	/* If old map had open handles duplicate them */
311 	if (NULL != old_map->entries) {
312 		new_map->open_flags = old_map->open_flags;
313 		new_map->open_mode = old_map->open_mode;
314 		if (FAILURE == open_yptol_files(new_map)) {
315 			free_map_ctrl(new_map);
316 			return (NULL);
317 		}
318 	}
319 
320 	return (new_map);
321 }
322 
323 /*
324  * FUNCTION: 	free_map_crtl();
325  *
326  * DESCRIPTION: Free contents of a map_ctr structure and closed any open
327  *		DBM files.
328  *
329  * INPUTS:	Pointer to pointer to a map_ctrl.
330  *
331  * OUTPUTS:	Nothing
332  *
333  */
334 void
335 free_map_ctrl(map_ctrl *map)
336 {
337 
338 	if (NULL != map->entries) {
339 		dbm_close(map->entries);
340 		map->entries = NULL;
341 	}
342 
343 	if (NULL != map->map_name) {
344 		sfree(map->map_name);
345 		map->map_name = NULL;
346 	}
347 
348 	if (NULL != map->map_path) {
349 		sfree(map->map_path);
350 		map->map_path = NULL;
351 	}
352 
353 	if (NULL != map->domain) {
354 		sfree(map->domain);
355 		map->domain = NULL;
356 	}
357 
358 	if (yptol_mode) {
359 		if (NULL != map->ttl) {
360 			dbm_close(map->ttl);
361 			map->ttl = NULL;
362 		}
363 
364 		if (NULL != map->trad_map_path) {
365 			sfree(map->trad_map_path);
366 			map->trad_map_path = NULL;
367 		}
368 
369 		if (NULL != map->ttl_path) {
370 			sfree(map->ttl_path);
371 			map->ttl_path = NULL;
372 		}
373 
374 		if (NULL != map->key_data.dptr) {
375 			sfree(map->key_data.dptr);
376 			map->key_data.dptr = NULL;
377 			map->key_data.dsize = 0;
378 		}
379 	}
380 
381 	map->magic = 0;
382 
383 	/* Since map_ctrls are now always in malloced memory */
384 	sfree(map);
385 
386 }
387 
388 /*
389  * FUNCTION :	get_map_name()
390  *
391  * DESCRIPTION:	Get the name of a map from its map_ctrl. This could be done
392  *		as a simple dereference but this function hides the internal
393  *		implementation of map_ctrl from higher layers.
394  *
395  * GIVEN :	A map_ctrl pointer
396  *
397  * RETURNS :	A pointer to the map_ctrl. Higher levels treat this as an
398  *		opaque DBM pointer.
399  *		NULL on failure.
400  */
401 char *
402 get_map_name(DBM *db)
403 {
404 	map_ctrl *map = (map_ctrl *)db;
405 
406 	if (NULL == map)
407 		return (NULL);
408 
409 	return (map->map_name);
410 }
411 
412 /*
413  * FUNCTION :	set_key_data()
414  *
415  * DESCRIPTION:	Sets up the key data freeing any that already exists.
416  *
417  * GIVEN :	Pointer to the map_ctrl to set up.
418  *		Datum containing the key. The dptr of this will be set to
419  *		point to the key data.
420  *
421  * RETURNS :	Nothing
422  */
423 void
424 set_key_data(map_ctrl *map, datum *data)
425 {
426 	char *myself = "set_key_data";
427 
428 	/*
429 	 * Free up any existing key data. Because each dbm file can only have
430 	 * one enumeration going at a time this is safe.
431 	 */
432 	if (NULL != map->key_data.dptr) {
433 		sfree(map->key_data.dptr);
434 		map->key_data.dptr = NULL;
435 		map->key_data.dsize = 0;
436 	}
437 
438 	/* If nothing in key just return */
439 	if (NULL == data->dptr)
440 		return;
441 
442 	/* Something is in the key so must duplicate out of static memory */
443 	map->key_data.dptr = (char *)am(myself, data->dsize);
444 	if (NULL == map->key_data.dptr) {
445 		logmsg(MSG_NOMEM, LOG_ERR, "Cannot alloc memory for key data");
446 	} else {
447 		memcpy(map->key_data.dptr, data->dptr, data->dsize);
448 		map->key_data.dsize = data->dsize;
449 	}
450 
451 	/* Set datum to point to malloced version of the data */
452 	data->dptr = map->key_data.dptr;
453 
454 	return;
455 
456 }
457 
458 /*
459  * FUNCTION :	open_yptol_files()
460  *
461  * DESCRIPTION:	Opens both yptol files for a map. This is called both when a
462  *		map is opened and when it is reopened as a result of an update
463  *		operation. Must be called with map locked.
464  *
465  * GIVEN :	Initialized map_ctrl
466  *
467  * RETURNS :	SUCCESS = Maps opened
468  *		FAILURE = Maps not opened (and mess tidied up)
469  */
470 suc_code
471 open_yptol_files(map_ctrl *map)
472 {
473 
474 	/* Open entries map */
475 	map->entries = dbm_open(map->map_path, map->open_flags, map->open_mode);
476 
477 	if (NULL == map->entries) {
478 		/* Maybe we were asked to open a non-existent map. No problem */
479 		return (FAILURE);
480 	}
481 
482 	if (yptol_mode) {
483 		/* Open TTLs map. Must always be writable */
484 		map->ttl = dbm_open(map->ttl_path, O_RDWR | O_CREAT, 0644);
485 		if (NULL == map->ttl) {
486 			logmsg(MSG_NOTIMECHECK, LOG_ERR,
487 				"Cannot open TTL file %s", map->ttl_path);
488 			dbm_close(map->entries);
489 			map->entries = NULL;
490 			return (FAILURE);
491 		}
492 	}
493 
494 	return (SUCCESS);
495 }
496 
497 /*
498  * FUNCTION :   insert_map_in_list()
499  *
500  * DESCRIPTION:	add a map in map_id_list[]
501  *
502  * GIVEN :      map name
503  *              map unique ID
504  *
505  * RETURNS :	SUCCESS = map added
506  *		FAILURE = map not added
507  */
508 suc_code
509 insert_map_in_list(char *map_name, int unique_value)
510 {
511 	int index;
512 	bool_t yptol_nl_sav = yptol_newlock;
513 	map_id_elt_t *new_elt;
514 
515 	/*
516 	 * Index in the hash table is computed from the original
517 	 * hash function: make sure yptol_newlock is set to false.
518 	 */
519 	yptol_newlock = FALSE;
520 	index = hash(map_name);
521 	yptol_newlock = yptol_nl_sav;
522 
523 	new_elt = (map_id_elt_t *)calloc(1, sizeof (map_id_elt_t));
524 	if (new_elt == NULL) {
525 		return (FAILURE);
526 	}
527 	new_elt->map_name = strdup(map_name);
528 	if (new_elt->map_name == NULL) { /* strdup() failed */
529 		return (FAILURE);
530 	}
531 	new_elt->map_id = unique_value;
532 
533 	if (map_id_list[index] == NULL) {
534 		new_elt->next = NULL;
535 	} else {
536 		new_elt->next = map_id_list[index];
537 	}
538 	/* insert at begining */
539 	map_id_list[index] = new_elt;
540 
541 	return (SUCCESS);
542 }
543 
544 #ifdef NISDB_LDAP_DEBUG
545 /*
546  * FUNCTION :   dump_map_id_list()
547  *
548  * DESCRIPTION:	display max_map and dump map_id_list[]
549  *		not called, here for debug convenience only
550  *
551  * GIVEN :      nothing
552  *
553  * RETURNS :	nothing
554  */
555 void
556 dump_map_id_list()
557 {
558 	int i;
559 	map_id_elt_t *cur_elt;
560 
561 	logmsg(MSG_NOTIMECHECK, LOG_DEBUG,
562 		"dump_map_id_list: max_map is: %d, dumping map_idlist ...",
563 		max_map);
564 
565 	for (i = 0; i < MAXHASH; i++) {
566 		if (map_id_list[i] == NULL) {
567 			logmsg(MSG_NOTIMECHECK, LOG_DEBUG,
568 				"no map for index %d", i);
569 		} else {
570 			logmsg(MSG_NOTIMECHECK, LOG_DEBUG,
571 				"index %d has the following maps", i);
572 			cur_elt = map_id_list[i];
573 			do {
574 				logmsg(MSG_NOTIMECHECK, LOG_DEBUG,
575 					"%s, unique id: %d",
576 					cur_elt->map_name,
577 					cur_elt->map_id);
578 				cur_elt = cur_elt->next;
579 			} while (cur_elt != NULL);
580 		}
581 	}
582 }
583 #endif
584 
585 /*
586  * FUNCTION :   free_map_id_list()
587  *
588  * DESCRIPTION:	free all previously allocated elements of map_id_list[]
589  *		reset max_map to 0
590  *
591  * GIVEN :      nothing
592  *
593  * RETURNS :	nothing
594  */
595 void
596 free_map_id_list()
597 {
598 	int i;
599 	map_id_elt_t *cur_elt, *next_elt;
600 
601 	for (i = 0; i < MAXHASH; i++) {
602 		if (map_id_list[i] != NULL) {
603 			cur_elt = map_id_list[i];
604 			do {
605 				next_elt = cur_elt->next;
606 				if (cur_elt->map_name)
607 					sfree(cur_elt->map_name);
608 				sfree(cur_elt);
609 				cur_elt = next_elt;
610 			} while (cur_elt != NULL);
611 			map_id_list[i] = NULL;
612 		}
613 	}
614 	max_map = 0;
615 }
616 
617 /*
618  * FUNCTION :   map_id_list_init()
619  *
620  * DESCRIPTION:	initializes map_id_list[] and max_map
621  *
622  * GIVEN :      nothing
623  *
624  * RETURNS :	 0 if OK
625  *		-1 if failure
626  */
627 int
628 map_id_list_init()
629 {
630 	char **domain_list, **map_list = NULL;
631 	int domain_num;
632 	int i, j;
633 	char *myself = "map_id_list_init";
634 	char mapbuf[MAXPATHLEN];
635 	int mapbuf_len = sizeof (mapbuf);
636 	int map_name_len;
637 	int seqnum = 0;
638 	int rc = 0;
639 
640 	for (i = 0; i < MAXHASH; i++) {
641 		map_id_list[i] = NULL;
642 	}
643 
644 	domain_num = get_mapping_domain_list(&domain_list);
645 	for (i = 0; i < domain_num; i++) {
646 		if (map_list) {
647 			free_map_list(map_list);
648 			map_list = NULL;
649 		}
650 
651 		/* get map list from mapping file */
652 		map_list = get_mapping_map_list(domain_list[i]);
653 		if (map_list == NULL) {
654 			/* no map for this domain in mapping file */
655 			logmsg(MSG_NOTIMECHECK, LOG_DEBUG,
656 			    "%s: get_mapping_map_list()"
657 			    " found no map for domain %s",
658 			    myself, domain_list[i]);
659 		}
660 
661 		/* add maps from /var/yp/<domain> */
662 		if (add_map_domain_to_list(domain_list[i], &map_list) ==
663 		    FALSE) {
664 			logmsg(MSG_NOTIMECHECK, LOG_ERR,
665 			    "%s: add_map_domain_to_list() failed", myself);
666 			free_map_id_list();
667 			if (map_list) free_map_list(map_list);
668 			return (-1);
669 		}
670 
671 		if (map_list == NULL || map_list[0] == NULL) {
672 			logmsg(MSG_NOTIMECHECK, LOG_DEBUG,
673 			    "%s: no map in domain %s",
674 			    myself, domain_list[i]);
675 			continue;
676 		}
677 
678 		for (j = 0; map_list[j] != NULL; j++) {
679 			/* build long name */
680 			map_name_len = ypdbpath_sz + 1 +
681 					strlen(domain_list[i]) + 1 +
682 					strlen(NTOL_PREFIX) +
683 					strlen(map_list[j]) + 1;
684 			if (map_name_len > mapbuf_len) {
685 				logmsg(MSG_NOTIMECHECK, LOG_ERR,
686 				    "%s: map name too long for %s",
687 				    " in domain %s", myself, map_list[j],
688 				    domain_list[i]);
689 				free_map_id_list();
690 				if (map_list) free_map_list(map_list);
691 				return (-1);
692 			}
693 			(void) memset(mapbuf, 0, mapbuf_len);
694 			(void) snprintf(mapbuf, map_name_len, "%s%c%s%c%s%s",
695 				ypdbpath, SEP_CHAR, domain_list[i], SEP_CHAR,
696 				NTOL_PREFIX, map_list[j]);
697 
698 			if (insert_map_in_list(mapbuf, seqnum)
699 				    == FAILURE) {
700 				logmsg(MSG_NOTIMECHECK, LOG_ERR,
701 				    "%s: failed to insert map %s",
702 				    " in domain %s", myself, map_list[j]);
703 				free_map_id_list();
704 				if (map_list) free_map_list(map_list);
705 				return (-1);
706 			}
707 			seqnum++;
708 		}
709 	}
710 
711 	max_map = seqnum;
712 
713 #ifdef NISDB_LDAP_DEBUG
714 	dump_map_id_list();
715 #endif
716 
717 	/*
718 	 * If more maps than allocated spaces in shared memory, that's a failure
719 	 * probably need to free previously allocated memory if failure,
720 	 * before returning.
721 	 */
722 	if (max_map > MAXHASH) {
723 		rc = -1;
724 		logmsg(MSG_NOTIMECHECK, LOG_ERR,
725 		    "%s: too many maps (%d)",
726 		    myself, max_map);
727 		free_map_id_list();
728 	}
729 	if (map_list) free_map_list(map_list);
730 	return (rc);
731 }
732 
733 /*
734  * FUNCTION :   get_list_max()
735  *
736  * DESCRIPTION: return references to static variables map_id_list
737  *              and max_map;
738  *
739  * GIVEN :      address for referencing map_id_list
740  *              address for referencing max_map
741  *
742  * RETURNS :    nothing
743  */
744 void
745 get_list_max(map_id_elt_t ***list, int *max)
746 {
747 	*list = map_id_list;
748 	*max = max_map;
749 }
750