Print this page
5910 libnisdb won't build with modern GCC
Split |
Close |
Expand all |
Collapse all |
--- old/usr/src/lib/libnisdb/nis_db.cc
+++ new/usr/src/lib/libnisdb/nis_db.cc
1 1 /*
2 2 * CDDL HEADER START
3 3 *
4 4 * The contents of this file are subject to the terms of the
5 5 * Common Development and Distribution License (the "License").
6 6 * You may not use this file except in compliance with the License.
7 7 *
8 8 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
9 9 * or http://www.opensolaris.org/os/licensing.
10 10 * See the License for the specific language governing permissions
11 11 * and limitations under the License.
12 12 *
13 13 * When distributing Covered Code, include this CDDL HEADER in each
14 14 * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
15 15 * If applicable, add the following below this CDDL HEADER, with the
↓ open down ↓ |
15 lines elided |
↑ open up ↑ |
16 16 * fields enclosed by brackets "[]" replaced with your own identifying
17 17 * information: Portions Copyright [yyyy] [name of copyright owner]
18 18 *
19 19 * CDDL HEADER END
20 20 */
21 21 /*
22 22 * nis_db.cc
23 23 *
24 24 * Copyright 2009 Sun Microsystems, Inc. All rights reserved.
25 25 * Use is subject to license terms.
26 + *
27 + * Copyright 2015 RackTop Systems.
26 28 */
27 29
28 30
29 31 #include <sys/param.h>
30 32 #include <strings.h>
31 33 #include <syslog.h>
32 34 #include "nisdb_mt.h"
33 35 #include "db_headers.h"
34 36 #include "db_entry.h"
35 37 #include "db.h"
36 38 #include "db_dictionary.h"
37 39 #include "db_pickle.h"
38 40 #include "nis_db.h"
39 41 #include "nis_ldap.h"
40 42 #include "ldap_util.h"
41 43 #include "ldap_parse.h"
42 44 #include "ldap_glob.h"
43 45 #include "ldap_xdr.h"
44 46 #include "ldap_glob.h"
45 47
46 48 db_dictionary curdict;
47 49 db_dictionary tempdict; /* a temporary one */
48 50
49 51 db_dictionary *InUseDictionary = &curdict;
50 52 db_dictionary *FreeDictionary = &tempdict;
51 53
52 54 extern "C" {
53 55 static db_result *db_add_entry_x(char *tab, int numattrs,
54 56 nis_attr *attrname, entry_obj * newobj,
55 57 int skiplog, int nosync);
56 58 db_status db_table_exists(char *table_name);
57 59
58 60 /*
59 61 * (Imported from rpc.nisd/nis_xx_proc.c)
60 62 *
61 63 * 'tbl_prototype' is used to create a table that holds a directory.
62 64 */
63 65 static table_col cols[2] = {
64 66 {(char *)"object", TA_BINARY+TA_XDR, 0},
65 67 {(char *)"name", TA_CASE+TA_SEARCHABLE, 0}
66 68 };
67 69
68 70 table_obj tbl_prototype = { (char *)"DIRECTORY", 2, ' ', {2, &cols[0]}, NULL };
69 71 }
70 72
71 73 /*
72 74 * Free resources associated with a db_result structure
73 75 */
74 76 void
75 77 db_free_result(db_result *dr)
76 78 {
77 79 int i;
78 80
79 81 if (dr == 0)
80 82 return;
81 83
82 84 /* Can't have valid objects */
83 85 if (dr->status != DB_SUCCESS) {
84 86 free(dr);
85 87 return;
86 88 }
87 89
88 90 for (i = 0; i < dr->objects.objects_len; i++)
89 91 free_entry(dr->objects.objects_val[i]);
90 92 free(dr->objects.objects_val);
91 93 free(dr);
92 94 }
93 95
94 96
95 97 /* Return an empty db_result structure with its status field set to 's'. */
96 98 db_result*
97 99 empty_result(db_status s)
98 100 {
99 101 db_result * res = new db_result;
100 102 if (res != NULL) {
101 103 res->status = s;
102 104 res->nextinfo.db_next_desc_len = 0;
103 105 res->nextinfo.db_next_desc_val = NULL;
104 106 res->objects.objects_len = 0;
105 107 res->objects.objects_val = NULL;
106 108 } else {
107 109 WARNING("nis_db::empty_result: cannot allocate space");
108 110 }
109 111 return (res);
110 112 }
111 113
112 114 static db_result*
113 115 set_result(db_result* res, db_status s)
114 116 {
115 117 if (res != NULL) {
116 118 res->status = s;
117 119 }
118 120 return (res);
119 121 }
120 122
121 123 /*
122 124 * Given a FQ object name for a table or directory, return the (db *)
123 125 * corresponding to the object.
124 126 */
125 127 db *
126 128 tableDB(char *tableName) {
127 129 db_table_desc *tbl = 0;
128 130 char *intName;
129 131 db *dbase;
130 132
131 133 intName = internalTableName(tableName);
132 134 if (intName == 0)
133 135 return (0);
134 136
135 137 dbase = InUseDictionary->find_table(intName, &tbl);
136 138
↓ open down ↓ |
101 lines elided |
↑ open up ↑ |
137 139 sfree(intName);
138 140
139 141 return (dbase);
140 142 }
141 143
142 144 extern "C" {
143 145
144 146 bool_t
145 147 db_in_dict_file(char *name)
146 148 {
147 - return ((bool_t) InUseDictionary->find_table_desc(name));
149 + return (InUseDictionary->find_table_desc(name) != NULL);
148 150
149 151 }
150 152
151 153 const char
152 154 *db_perror(db_status dbstat)
153 155 {
154 156 const char *str = NULL;
155 157
156 158 switch (dbstat) {
157 159 case DB_SUCCESS:
158 160 str = "Success";
159 161 break;
160 162 case DB_NOTFOUND:
161 163 str = "Not Found";
162 164 break;
163 165 case DB_BADTABLE:
164 166 str = "Bad Table";
165 167 break;
166 168 case DB_BADQUERY:
167 169 str = "Bad Query";
168 170 break;
169 171 case DB_BADOBJECT:
170 172 str = "Bad Object";
171 173 break;
172 174 case DB_MEMORY_LIMIT:
173 175 str = "Memory limit exceeded";
174 176 break;
175 177 case DB_STORAGE_LIMIT:
176 178 str = "Database storage limit exceeded";
177 179 break;
178 180 case DB_INTERNAL_ERROR:
179 181 str = "Database internal error";
180 182 break;
181 183 case DB_SYNC_FAILED:
182 184 str = "Sync of log file failed";
183 185 break;
184 186 default:
185 187 str = "Unknown Error";
186 188 break;
187 189 }
188 190 return (str);
189 191 }
190 192
191 193 bool_t
192 194 db_extract_dict_entries(char *newdict, char **fs, int fscnt)
193 195 {
194 196 /*
195 197 * Use the "FreeDictionary" ptr for the backup
196 198 * dictionary.
197 199 */
198 200 if (!FreeDictionary->inittemp(newdict, *InUseDictionary))
199 201 return (FALSE);
200 202 return (InUseDictionary->extract_entries (*FreeDictionary,
201 203 fs, fscnt));
202 204 }
203 205
204 206 bool_t
205 207 db_copy_file(char *infile, char *outfile)
206 208 {
207 209 return (InUseDictionary->copyfile(infile, outfile));
208 210
209 211 }
210 212
211 213
212 214 /*
213 215 * The tok and repl parameters will allow us to merge two dictionaries
214 216 * that reference tables from different domains (master/replica in live
215 217 * in different domains). If set to NULL, then the dictionary merge is
216 218 * done as normal (no name changing).
217 219 */
218 220 db_status
219 221 db_begin_merge_dict(char *newdict, char *tok, char *repl)
220 222 {
221 223 db_status dbstat;
222 224
223 225 /*
224 226 * It is assumed that InUseDictionary has already been initialized.
225 227 */
226 228 dbstat = InUseDictionary->checkpoint();
227 229 if (dbstat != DB_SUCCESS)
228 230 return (dbstat);
229 231
230 232 /*
231 233 * Use the "FreeDictionary" ptr for the backup
232 234 * dictionary.
233 235 */
234 236 if (!FreeDictionary->init(newdict))
235 237 return (DB_INTERNAL_ERROR);
236 238
237 239 return (InUseDictionary->merge_dict(*FreeDictionary,
238 240 tok, repl));
239 241 }
240 242
241 243
242 244 db_status
243 245 db_end_merge_dict()
244 246 {
245 247 db_status dbstat;
246 248
247 249 dbstat = InUseDictionary->checkpoint();
248 250 if (dbstat != DB_SUCCESS) {
249 251 return (dbstat);
250 252 }
251 253 dbstat = InUseDictionary->db_shutdown();
252 254 if (dbstat != DB_SUCCESS) {
253 255 return (dbstat);
254 256 }
255 257 dbstat = FreeDictionary->db_shutdown();
256 258 if (dbstat != DB_SUCCESS) {
257 259 return (dbstat);
258 260 }
259 261 return (dbstat);
260 262 }
261 263
262 264
263 265
264 266 db_status
265 267 db_abort_merge_dict()
266 268 {
267 269 db_status dbstat;
268 270
269 271 dbstat = InUseDictionary->db_shutdown();
270 272 if (dbstat != DB_SUCCESS)
271 273 return (dbstat);
272 274 dbstat = FreeDictionary->db_shutdown();
273 275 if (dbstat != DB_SUCCESS)
274 276 return (dbstat);
275 277 }
276 278
277 279
278 280 /*
279 281 * Initialize system (dictionary) using file 'filename'. If system cannot
280 282 * be read from file, it is initialized to be empty. Returns TRUE if
281 283 * initialization succeeds, FALSE otherwise.
282 284 * This function must be called before any other.
283 285 */
284 286 bool_t
285 287 db_initialize(char * filename)
286 288 {
287 289 return (InUseDictionary->init(filename));
288 290 }
289 291
290 292
291 293 /*
292 294 * Massage the dictionary file by replacing the specified token with the
293 295 * the replacement string. This function is needed to provide backwards
294 296 * compatibility for providing a transportable dictionary file. The idea
295 297 * is that rpc.nisd will call this function when it wants to change the
296 298 * /var/nis/<hostname> strings with something like /var/nis/data.
297 299 *
298 300 */
299 301 db_status
300 302 db_massage_dict(char *newdictname, char *tok, char *repl)
301 303 {
302 304 return (InUseDictionary->massage_dict(newdictname, tok, repl));
303 305 }
304 306
305 307
306 308
307 309 /*
308 310 * Create new table using given table name and table descriptor.
309 311 * Returns DB_SUCCESS if successful; appropriate error code otherwise.
310 312 */
311 313 db_status
312 314 db_create_table(char * table_name, table_obj * table_desc)
313 315 {
314 316 return (InUseDictionary->add_table(table_name, table_desc));
315 317 }
316 318
317 319 /*
318 320 * Destroys table named by 'table_name.' Returns DB_SUCCESS if successful,
319 321 * error code otherwise. Note that currently, the removed table is no
320 322 * longer accessible from this interface and all files associated with it
321 323 * are removed from stable storage.
322 324 */
323 325 db_status
324 326 db_destroy_table(char * table_name)
325 327 {
326 328 return (InUseDictionary->delete_table(table_name));
327 329 }
328 330
329 331
330 332 /*
331 333 * Return a copy of the first entry in the specified table, that satisfies
332 334 * the given attributes. The returned structure 'db_result' contains the status,
333 335 * the copy of the object, and a 'db_next_desc' to be used for the 'next'
334 336 * operation.
335 337 */
336 338 db_result *
337 339 db_first_entry(char * table_name, int numattrs, nis_attr * attrname)
338 340 {
339 341 db_result * safety = empty_result(DB_SUCCESS);
340 342 db_table_desc * tbl = NULL;
341 343 db * dbase = InUseDictionary->find_table(table_name, &tbl);
342 344
343 345 if (tbl == NULL || dbase == NULL)
344 346 return (set_result(safety, DB_BADTABLE));
345 347 else {
346 348 db_result * res = NULL;
347 349 db_query *query = NULL;
348 350
349 351 if (numattrs != 0) {
350 352 query = InUseDictionary->translate_to_query(tbl,
351 353 numattrs, attrname);
352 354 if (query == NULL)
353 355 return (set_result(safety,
354 356 DB_BADQUERY));
355 357 }
356 358 res = dbase->execute(DB_FIRST, query, NULL, NULL);
357 359 if (query) delete query;
358 360 if (safety) delete safety;
359 361 return (res);
360 362 }
361 363 }
362 364
363 365 /*
364 366 * Return a copy of the next entry in the specified table as specified by
365 367 * the 'next_desc'. The returned structure 'db_result' contains the status,
366 368 * a copy of the object, and a db_next_desc to be used for a subsequent
367 369 * 'next' operation.
368 370 */
369 371 db_result *
370 372 db_next_entry(char * table_name, db_next_desc * next_desc)
371 373 {
372 374 db_result * safety = empty_result(DB_SUCCESS);
373 375 db * dbase = InUseDictionary->find_table(table_name);
374 376
375 377 if (dbase != NULL) {
376 378 if (safety) delete safety;
377 379 return (dbase->execute(DB_NEXT, NULL, NULL, next_desc));
378 380 } else
379 381 return (set_result(safety, DB_BADTABLE));
380 382 }
381 383
382 384 /*
383 385 * Indicate to the system that you are no longer interested in the rest of the
384 386 * results identified by [next_desc]. After executing this operation, the
385 387 * [next_desc] is no longer valid (cannot be used as an argument for next).
386 388 */
387 389
388 390 db_result *
389 391 db_reset_next_entry(char * table_name, db_next_desc * next_desc)
390 392 {
391 393 db_result * safety = empty_result(DB_SUCCESS);
392 394 db * dbase = InUseDictionary->find_table(table_name);
393 395
394 396 if (dbase != NULL) {
395 397 if (safety) delete safety;
396 398 return (dbase->execute(DB_RESET_NEXT,
397 399 NULL, NULL, next_desc));
398 400 } else
399 401 return (set_result(safety, DB_BADTABLE));
400 402 }
401 403
402 404 /*
403 405 * Returns copies of entries that satisfy the given attributes from table.
404 406 * Returns the status and entries in a db_result structure.
405 407 * If no attributes are specified, DB_BADQUERY is returned.
406 408 */
407 409 db_result *
408 410 __db_list_entries(char * table_name, int numattrs, nis_attr * attrname,
409 411 bool_t useDeferred)
410 412 {
411 413 db_result * safety = empty_result(DB_SUCCESS);
412 414 db_table_desc * tbl = NULL;
413 415 db * dbase = InUseDictionary->find_table(table_name, &tbl,
414 416 useDeferred);
415 417
416 418 if (tbl == NULL || dbase == NULL)
417 419 return (set_result(safety, DB_BADTABLE));
418 420 else {
419 421 db_result * res = NULL;
420 422 if (numattrs != 0) {
421 423 db_query *query;
422 424 query = InUseDictionary->translate_to_query(tbl,
423 425 numattrs, attrname);
424 426 if (query == NULL)
425 427 return (set_result(safety,
426 428 DB_BADQUERY));
427 429 res = dbase->execute(DB_LOOKUP, query,
428 430 NULL, NULL);
429 431 delete query;
430 432 } else {
431 433 res = dbase->execute(DB_ALL, NULL, NULL, NULL);
432 434 }
433 435 if (safety) delete safety;
434 436 return (res);
435 437 }
436 438 }
437 439
438 440 db_result *
439 441 db_list_entries(char *table_name, int numattrs, nis_attr *attrname) {
440 442 return (__db_list_entries(table_name, numattrs, attrname, TRUE));
441 443 }
442 444
443 445 /*
444 446 * Input: A fully qualified object name (example: "x.y.z").
445 447 * Output: Returns the first level of the object name ("x").
446 448 * If 'tableP' is non-NULL, '*tableP' will contain
447 449 * the internal table name for "y.z".
448 450 *
449 451 * Both the return value and '*tableP' must be freed by the caller.
450 452 */
451 453 char *
452 454 entryName(const char *msg, char *objName, char **tableP) {
453 455 char *name, *table, *dir;
454 456 const char *myself = "entryName";
455 457
456 458 if (msg == 0)
457 459 msg = myself;
458 460
459 461 name = sdup(msg, T, objName);
460 462 if (name == 0)
461 463 return (0);
462 464
463 465 dir = strchr(name, '.');
464 466 if (dir == 0) {
465 467 sfree(name);
466 468 return (0);
467 469 }
468 470 *(dir++) = '\0';
469 471
470 472 if (tableP == 0)
471 473 return (name);
472 474
473 475 table = internalTableName(dir);
474 476 if (table == 0) {
475 477 sfree(name);
476 478 return (0);
477 479 }
478 480
479 481 *tableP = table;
480 482
481 483 return (name);
482 484 }
483 485
484 486 #define RETSTAT(obj, status) \
485 487 { \
486 488 if (statP != 0) \
487 489 *statP = status; \
488 490 return (obj); \
489 491 }
490 492
491 493 /*
492 494 * Given a fully qualified object name, retrive a copy of the object,
493 495 * using the NIS+ DB only (i.e., no LDAP). Avoids using nis_leaf_of()
494 496 * etc., since they aren't re-entrant.
495 497 */
496 498 nis_object *
497 499 dbFindObject(char *objName, db_status *statP) {
498 500 char buf[MAXPATHLEN+NIS_MAXNAMELEN+1];
499 501 char *name, *table = 0;
500 502 nis_attr attr;
501 503 db *dbase;
502 504 db_result *res;
503 505 db_table_desc *tbl = 0;
504 506 db_query *query;
505 507 db_mindex *mindex;
506 508 nis_object *o;
507 509 int lstat;
508 510 const char *myself = "dbFindObject";
509 511
510 512 if (objName == 0)
511 513 RETSTAT(0, DB_BADQUERY);
512 514
513 515 /* The root dir is treated specially */
514 516 table = internalTableName(objName);
515 517 if (table == 0)
516 518 RETSTAT(0, DB_BADQUERY);
517 519 if (strcmp(ROOTDIRFILE, table) == 0) {
518 520 sfree(table);
519 521
520 522 o = get_root_object();
521 523 if (o == 0)
522 524 RETSTAT(0, DB_NOTFOUND);
523 525
524 526 RETSTAT(o, DB_SUCCESS);
525 527 }
526 528
527 529 /* If not the root dir, find the directory where the entry lives */
528 530
529 531 sfree(table);
530 532 name = entryName(myself, objName, &table);
531 533 if (name == 0 || table == 0) {
532 534 sfree(name);
533 535 RETSTAT(0, DB_MEMORY_LIMIT);
534 536 }
535 537
536 538 dbase = InUseDictionary->find_table_noLDAP(table, &tbl, TRUE, TRUE);
537 539 sfree(table);
538 540 if (dbase != 0)
539 541 mindex = dbase->mindex();
540 542 if (dbase == 0 || tbl == 0 || mindex == 0) {
541 543 sfree(name);
542 544 RETSTAT(0, DB_BADTABLE);
543 545 }
544 546
545 547 WRITELOCKNR(mindex, lstat, "mindex w dbFindObject");
546 548 if (lstat != 0) {
547 549 sfree(name);
548 550 RETSTAT(0, DB_LOCK_ERROR);
549 551 }
550 552
551 553 attr.zattr_ndx = (char *)"name";
552 554 attr.zattr_val.zattr_val_val = name;
553 555 attr.zattr_val.zattr_val_len = slen(name) + 1;
554 556
555 557 query = InUseDictionary->translate_to_query(tbl, 1, &attr);
556 558 if (query == 0) {
557 559 sfree(name);
558 560 WRITEUNLOCKNR(mindex, lstat, "mindex wu dbFindObject");
559 561 RETSTAT(0, DB_BADQUERY);
560 562 }
561 563
562 564 /* Only want to look in the local DB */
563 565 mindex->setNoLDAPquery();
564 566
565 567 res = dbase->execute(DB_LOOKUP, query, 0, 0);
566 568
567 569 mindex->clearNoLDAPquery();
568 570
569 571 delete query;
570 572
571 573 sfree(name);
572 574
573 575 WRITEUNLOCKNR(mindex, lstat, "mindex wu dbFindObject");
574 576 if (lstat != 0) {
575 577 db_free_result(res);
576 578 RETSTAT(0, DB_LOCK_ERROR);
577 579 }
578 580
579 581 if (res == 0)
580 582 RETSTAT(0, DB_MEMORY_LIMIT);
581 583
582 584 if (res->status != DB_SUCCESS) {
583 585 db_status st = res->status;
584 586
585 587 db_free_result(res);
586 588 RETSTAT(0, st);
587 589 }
588 590
589 591 if (res->objects.objects_len != 1 || res->objects.objects_val == 0 ||
590 592 res->objects.objects_val[0] == 0) {
591 593 db_free_result(res);
592 594 RETSTAT(0, DB_BADOBJECT);
593 595 }
594 596
595 597 o = unmakePseudoEntryObj(res->objects.objects_val[0], 0);
596 598
597 599 db_free_result(res);
598 600
599 601 if (o == 0) {
600 602 RETSTAT(0, DB_BADOBJECT);
601 603 }
602 604
603 605 RETSTAT(o, DB_SUCCESS);
604 606 }
605 607
606 608 /*
607 609 * Return the object specified by 't' or 'objName' from LDAP. Set
608 610 * the LDAP status in '*statP'.
609 611 */
610 612 nis_object *
611 613 ldapFindObj(__nis_table_mapping_t *t, char *objName, int *statP) {
612 614 nis_object *o;
613 615 int stat;
614 616 const char *myself = "ldapFindObj";
615 617
616 618 if (t == 0) {
617 619 char *table, tbuf[MAXPATHLEN + NIS_MAXNAMELEN + 1];
618 620
619 621 if (objName == 0) {
620 622 if (statP != 0)
621 623 *statP = LDAP_PARAM_ERROR;
622 624 return (0);
623 625 }
624 626
625 627 /* Look for mapping */
626 628 table = internal_table_name(objName, tbuf);
627 629 if (table == 0) {
628 630 if (statP != 0)
629 631 *statP = LDAP_PARAM_ERROR;
630 632 return (0);
631 633 }
632 634
633 635 t = (__nis_table_mapping_t *)__nis_find_item_mt(table,
634 636 &ldapMappingList, 0, 0);
635 637 if (t == 0) {
636 638 /* Not really an error; just not mapped */
637 639 *statP = LDAP_SUCCESS;
638 640 return (0);
639 641 }
640 642 }
641 643
642 644 o = 0;
643 645 stat = objFromLDAP(t, &o, 0, 0);
644 646
645 647 if (statP != 0)
646 648 *statP = stat;
647 649
648 650 return (o);
649 651 }
650 652
651 653 /*
652 654 * Look for the specified object, first locally, then in LDAP.
653 655 */
654 656 nis_object *
655 657 findObj(char *name, db_status *statP, int *lstatP) {
656 658 nis_object *o;
657 659 db_status stat = DB_SUCCESS;
658 660 int lstat = LDAP_SUCCESS;
659 661 const char *myself = "findObj";
660 662
661 663 o = dbFindObject(name, &stat);
662 664
663 665 if (o == 0) {
664 666 if (stat != DB_NOTFOUND)
665 667 logmsg(MSG_NOTIMECHECK, LOG_INFO,
666 668 "%s: DB error %d looking for \"%s\"",
667 669 myself, stat, NIL(name));
668 670
669 671 o = ldapFindObj(0, name, &lstat);
670 672 if (o == 0) {
671 673 if (lstat != LDAP_SUCCESS &&
672 674 lstat != LDAP_NO_SUCH_OBJECT)
673 675 logmsg(MSG_NOTIMECHECK, LOG_INFO,
674 676 "%s: LDAP error looking for \"%s\": %s",
675 677 myself, NIL(name),
676 678 ldap_err2string(lstat));
677 679 }
678 680 }
679 681
680 682 if (statP != 0)
681 683 *statP = stat;
682 684 if (lstatP != 0)
683 685 *lstatP = lstat;
684 686
685 687 return (o);
686 688 }
687 689
688 690 /*
689 691 * Delete the specified object from the local DB.
690 692 */
691 693 db_status
692 694 dbDeleteObj(char *objName) {
693 695 nisdb_tsd_t *tsd = __nisdb_get_tsd();
694 696 nis_object *o;
695 697 db_status stat;
696 698 nisdb_obj_del_t *nod, *tmp;
697 699 int xid;
698 700 const char *myself = "dbDeleteObj";
699 701
700 702 if (objName == 0)
701 703 return (DB_SUCCESS);
702 704
703 705 /*
704 706 * Since in-structure locks can't completely protect
705 707 * during structure deletion, we just note that the
706 708 * object should be deleted, and leave that for a
707 709 * (slightly) later time in rpc.nisd, where we can
708 710 * use the rpc.nisd's table/directory locks for
709 711 * protection.
710 712 */
711 713
712 714 if (tsd == 0)
713 715 return (DB_INTERNAL_ERROR);
714 716
715 717 o = dbFindObject(objName, &stat);
716 718 if (o == 0) {
717 719 if (stat == DB_NOTFOUND)
718 720 return (DB_SUCCESS);
719 721 else
720 722 return (stat);
721 723 }
722 724
723 725 /*
724 726 * In order to prevent a chicken-and-egg problem (if the
725 727 * object doesn't exist in LDAP, is that because we just
726 728 * haven't written it to LDAP yet, or because it's been
727 729 * removed), we only allow object deletion if we're the
728 730 * master for it.
729 731 */
730 732
731 733 nod = (nisdb_obj_del_t *)am(myself, sizeof (*nod));
732 734 if (nod == 0) {
733 735 nis_destroy_object(o);
734 736 return (DB_MEMORY_LIMIT);
735 737 }
736 738
737 739 nod->objType = o->zo_data.zo_type;
738 740 nis_destroy_object(o);
739 741
740 742 nod->objName = sdup(myself, T, objName);
741 743 if (nod->objName == 0) {
742 744 sfree(nod);
743 745 return (DB_MEMORY_LIMIT);
744 746 }
745 747
746 748 /* Check for a dup */
747 749 for (tmp = tsd->objDelList; tmp != 0;
748 750 tmp = (nisdb_obj_del_t *)tmp->next) {
749 751 if (strcmp(nod->objName, tmp->objName) == 0) {
750 752 sfree(nod->objName);
751 753 sfree(nod);
752 754 return (DB_SUCCESS);
753 755 }
754 756 }
755 757
756 758 /* Insert at start of list */
757 759 nod->next = tsd->objDelList;
758 760 tsd->objDelList = nod;
759 761
760 762 return (DB_SUCCESS);
761 763 }
762 764
763 765 /*
764 766 * Touch (i.e., update the expiration time for) the specified object.
765 767 */
766 768 db_status
767 769 dbTouchObj(char *objName) {
768 770 char *ent, *table;
769 771 db *dbase;
770 772 db_table_desc *tbl = 0;
771 773 db_mindex *mindex;
772 774 nis_attr attr;
773 775 db_query *query;
774 776 db_status stat;
775 777 const char *myself = "dbTouchObj";
776 778
777 779 table = internalTableName(objName);
778 780 if (table == 0)
779 781 return (DB_BADQUERY);
780 782
781 783 if (strcmp(ROOTDIRFILE, table) == 0) {
782 784 sfree(table);
783 785
784 786 if (touchRootDir() == 0)
785 787 return (DB_SUCCESS);
786 788 else
787 789 return (DB_INTERNAL_ERROR);
788 790 }
789 791
790 792 sfree(table);
791 793 table = 0;
792 794 ent = entryName(myself, objName, &table);
793 795 if (ent == 0 || table == 0) {
794 796 sfree(ent);
795 797 return (DB_MEMORY_LIMIT);
796 798 }
797 799
798 800 dbase = InUseDictionary->find_table(table, &tbl, TRUE);
799 801 if (dbase != 0)
800 802 mindex = dbase->mindex();
801 803 if (dbase == 0 || tbl == 0 || mindex == 0) {
802 804 sfree(ent);
803 805 sfree(table);
804 806 return (DB_BADTABLE);
805 807 }
806 808
807 809 attr.zattr_ndx = (char *)"name";
808 810 attr.zattr_val.zattr_val_val = ent;
809 811 attr.zattr_val.zattr_val_len = slen(ent) + 1;
810 812
811 813 query = InUseDictionary->translate_to_query(tbl, 1, &attr);
812 814 if (query == 0) {
813 815 sfree(ent);
814 816 sfree(table);
815 817 return (DB_BADQUERY);
816 818 }
817 819
818 820 mindex->touchEntry(query);
819 821
820 822 sfree(ent);
821 823 sfree(table);
822 824 delete query;
823 825
824 826 return (DB_SUCCESS);
825 827 }
826 828
827 829 /*
828 830 * Create a NIS_TABLE_OBJ.
829 831 * Borrows heavily from rpc.nisd/nis_db.c:__create_table().
830 832 */
831 833 db_status
832 834 dbCreateTable(char *intName, nis_object *obj) {
833 835 table_col tc[NIS_MAXCOLUMNS+1];
834 836 table_obj tobj, *t;
835 837 int i;
836 838 const char *myself = "dbCreateTable";
837 839
838 840 if (intName == 0 || obj == 0)
839 841 return (DB_BADTABLE);
840 842
841 843 t = &(obj->TA_data);
842 844
843 845 /* Make sure there are searchable columns */
844 846 for (i = 0; i < t->ta_cols.ta_cols_len; i++) {
845 847 if (t->ta_cols.ta_cols_val[i].tc_flags & TA_SEARCHABLE)
846 848 break;
847 849 }
848 850 if (i >= t->ta_cols.ta_cols_len) {
849 851 logmsg(MSG_NOTIMECHECK, LOG_INFO,
850 852 "%s: No searchable columns in \"%s\" (\"%s\")",
851 853 myself, NIL(obj->zo_name), NIL(intName));
852 854 return (DB_BADTABLE);
853 855 }
854 856
855 857 tobj = *t;
856 858 /* Shift columns one step right */
857 859 for (i = 0; i < tobj.ta_cols.ta_cols_len; i++) {
858 860 tc[i+1] = tobj.ta_cols.ta_cols_val[i];
859 861 }
860 862 tc[0].tc_name = 0;
861 863 tc[0].tc_flags = TA_XDR | TA_BINARY;
862 864 tc[0].tc_rights = 0;
863 865 tobj.ta_cols.ta_cols_len += 1;
864 866 tobj.ta_cols.ta_cols_val = tc;
865 867
866 868 return (db_create_table(intName, &tobj));
867 869 }
868 870
869 871 #define TABLE_COL(o, n) o->TA_data.ta_cols.ta_cols_val[n]
870 872
871 873 /*
872 874 * Refresh (if necessary, create), the specified object in the local DB.
873 875 */
874 876 db_status
875 877 dbRefreshObj(char *name, nis_object *o) {
876 878 char *objName;
877 879 __nis_buffer_t b = {0, 0};
878 880 nis_object *curObj;
879 881 db_status stat;
880 882 char *ent, *table, *objTable;
881 883 int rstat, isDir = 0, isTable = 0;
882 884 const char *myself = "refreshObj";
883 885
884 886 if (o == 0)
885 887 /* Delete it */
886 888 return (dbDeleteObj(name));
887 889
888 890 /* We don't work on entry objects */
889 891 if (o->zo_data.zo_type == NIS_ENTRY_OBJ)
890 892 return (DB_BADOBJECT);
891 893
892 894 if (name != 0)
893 895 objName = name;
894 896 else {
895 897 bp2buf(myself, &b, "%s.%s", NIL(o->zo_name), NIL(o->zo_domain));
896 898 objName = b.buf;
897 899 }
898 900
899 901 curObj = dbFindObject(objName, &stat);
900 902 if (curObj == 0 && stat != DB_NOTFOUND) {
901 903 sfree(b.buf);
902 904 return (stat);
903 905 }
904 906
905 907 /*
906 908 * If the object doesn't change, just touch it to update the
907 909 * expiration time.
908 910 */
909 911 if (curObj != 0) {
910 912 if (sameNisPlusObj(o, curObj)) {
911 913 sfree(b.buf);
912 914 nis_destroy_object(curObj);
913 915 return (dbTouchObj(objName));
914 916 }
915 917
916 918 /* Otherwise, check that the name and type is the same */
917 919 if (o->zo_data.zo_type != curObj->zo_data.zo_type ||
918 920 o->zo_name == 0 || curObj->zo_name == 0 ||
919 921 o->zo_domain == 0 || curObj->zo_domain == 0 ||
920 922 strcmp(o->zo_name, curObj->zo_name) != 0 ||
921 923 strcmp(o->zo_domain, curObj->zo_domain) != 0) {
922 924 sfree(b.buf);
923 925 nis_destroy_object(curObj);
924 926 return (DB_BADOBJECT);
925 927 }
926 928
927 929 /*
928 930 * If the object is a table, we can't allow the scheme
929 931 * to change.
930 932 */
931 933 if (o->zo_data.zo_type == NIS_TABLE_OBJ) {
932 934 int i;
933 935
934 936 if (o->TA_data.ta_maxcol !=
935 937 curObj->TA_data.ta_maxcol) {
936 938 sfree(b.buf);
937 939 nis_destroy_object(curObj);
938 940 return (DB_BADOBJECT);
939 941 }
940 942
941 943 for (i = 0; i < o->TA_data.ta_maxcol; i++) {
942 944 if ((TABLE_COL(o, i).tc_flags &
943 945 TA_SEARCHABLE) !=
944 946 (TABLE_COL(curObj, i).tc_flags &
945 947 TA_SEARCHABLE)) {
946 948 sfree(b.buf);
947 949 nis_destroy_object(curObj);
948 950 return (DB_BADOBJECT);
949 951 }
950 952 }
951 953 }
952 954 } else {
953 955 /*
954 956 * If we're creating a directory object, make a note
955 957 * so that we can add it to the serving list and create
956 958 * the disk file. Similarly, if creating a table, we
957 959 * also need to create the disk file.
958 960 */
959 961 if (o->zo_data.zo_type == NIS_DIRECTORY_OBJ)
960 962 isDir = 1;
961 963 else if (o->zo_data.zo_type == NIS_TABLE_OBJ)
962 964 isTable = 1;
963 965 }
964 966
965 967 objTable = internalTableName(objName);
966 968 if (objTable == 0) {
967 969 sfree(b.buf);
968 970 if (curObj != 0)
969 971 nis_destroy_object(curObj);
970 972 return (DB_BADQUERY);
971 973 }
972 974
973 975 if (strcmp(ROOTDIRFILE, objTable) == 0) {
974 976 sfree(objTable);
975 977
976 978 rstat = update_root_object((nis_name)ROOTOBJFILE, o);
977 979 if (rstat == 1)
978 980 stat = DB_SUCCESS;
979 981 else
980 982 stat = DB_INTERNAL_ERROR;
981 983 } else {
982 984 nis_attr attr;
983 985 entry_object *e, eo;
984 986 entry_col ec[2];
985 987 db *dbase;
986 988 db_table_desc *tbl = 0;
987 989 db_mindex *mindex;
988 990 db_result *dbres;
989 991 int lstat;
990 992
991 993 /* Find parent */
992 994 ent = entryName(myself, objName, &table);
993 995 if (ent == 0 || table == 0) {
994 996 sfree(b.buf);
995 997 sfree(objTable);
996 998 sfree(ent);
997 999 if (curObj != 0)
998 1000 nis_destroy_object(curObj);
999 1001 return (DB_MEMORY_LIMIT);
1000 1002 }
1001 1003
1002 1004 /*
1003 1005 * Calling vanilla find_table() here (which might go to
1004 1006 * LDAP and recurse back to ourselves) so that it should
1005 1007 * work to create a hierarchy of directories.
1006 1008 */
1007 1009 dbase = InUseDictionary->find_table(table, &tbl, TRUE);
1008 1010 if (dbase != 0)
1009 1011 mindex = dbase->mindex();
1010 1012 if (dbase == 0 || tbl == 0 || mindex == 0) {
1011 1013 sfree(b.buf);
1012 1014 sfree(objTable);
1013 1015 sfree(ent);
1014 1016 sfree(table);
1015 1017 if (curObj != 0)
1016 1018 nis_destroy_object(curObj);
1017 1019 return (DB_BADTABLE);
1018 1020 }
1019 1021
1020 1022 /* Construct suitable nis_attr and entry_object */
1021 1023 attr.zattr_ndx = (char *)"name";
1022 1024 attr.zattr_val.zattr_val_val = ent;
1023 1025 attr.zattr_val.zattr_val_len = slen(ent) + 1;
1024 1026
1025 1027 ec[1].ec_flags = 0;
1026 1028 ec[1].ec_value.ec_value_val = ent;
1027 1029 ec[1].ec_value.ec_value_len = attr.zattr_val.zattr_val_len;
1028 1030
1029 1031 eo.en_type = (char *)"IN_DIRECTORY";
1030 1032 eo.en_cols.en_cols_val = ec;
1031 1033 eo.en_cols.en_cols_len = 2;
1032 1034
1033 1035 e = makePseudoEntryObj(o, &eo, 0);
1034 1036 if (e == 0) {
1035 1037 sfree(objTable);
1036 1038 sfree(table);
1037 1039 sfree(ent);
1038 1040 if (curObj != 0)
1039 1041 nis_destroy_object(curObj);
1040 1042 return (DB_INTERNAL_ERROR);
1041 1043 }
1042 1044
1043 1045 /* Only want to update the local DB */
1044 1046
1045 1047 WRITELOCKNR(mindex, lstat, "mindex w dbRefreshObj");
1046 1048 if (lstat != 0) {
1047 1049 sfree(objTable);
1048 1050 sfree(table);
1049 1051 sfree(ent);
1050 1052 if (curObj != 0)
1051 1053 nis_destroy_object(curObj);
1052 1054 return (DB_LOCK_ERROR);
1053 1055 }
1054 1056 mindex->setNoWriteThrough();
1055 1057 mindex->setNoLDAPquery();
1056 1058
1057 1059 dbres = db_add_entry_x(table, 1, &attr, e, 0, 0);
1058 1060
1059 1061 mindex->clearNoLDAPquery();
1060 1062 mindex->clearNoWriteThrough();
1061 1063 WRITEUNLOCKNR(mindex, lstat, "mindex wu dbRefreshObj");
1062 1064 if (lstat != 0) {
1063 1065 sfree(objTable);
1064 1066 sfree(table);
1065 1067 sfree(ent);
1066 1068 if (curObj != 0)
1067 1069 nis_destroy_object(curObj);
1068 1070 db_free_result(dbres);
1069 1071 return (DB_LOCK_ERROR);
1070 1072 }
1071 1073
1072 1074 sfree(ent);
1073 1075 sfree(table);
1074 1076
1075 1077 if (dbres == 0)
1076 1078 stat = DB_MEMORY_LIMIT;
1077 1079 else
1078 1080 stat = dbres->status;
1079 1081
1080 1082 db_free_result(dbres);
1081 1083
1082 1084 /*
1083 1085 * If successful so far, add the transaction.
1084 1086 */
1085 1087 if (stat == DB_SUCCESS) {
1086 1088 int xid, st;
1087 1089 db_status ds;
1088 1090 nis_object *dirObj;
1089 1091
1090 1092 /* Find the directory where this is added */
1091 1093 dirObj = dbFindObject(o->zo_domain, &ds);
1092 1094 if (dirObj == 0) {
1093 1095 sfree(objTable);
1094 1096 if (curObj != 0)
1095 1097 nis_destroy_object(curObj);
1096 1098 return (ds);
1097 1099 }
1098 1100
1099 1101 xid = beginTransaction();
1100 1102 if (xid == 0) {
1101 1103 sfree(objTable);
1102 1104 if (curObj != 0)
1103 1105 nis_destroy_object(curObj);
1104 1106 nis_destroy_object(dirObj);
1105 1107 return (DB_INTERNAL_ERROR);
1106 1108 }
1107 1109
1108 1110 st = addUpdate((curObj == 0) ? ADD_NAME : MOD_NAME_NEW,
1109 1111 objName, 0, 0, o, curObj, 0);
1110 1112 if (st != 0) {
1111 1113 (void) abort_transaction(xid);
1112 1114 sfree(objTable);
1113 1115 if (curObj != 0)
1114 1116 nis_destroy_object(curObj);
1115 1117 nis_destroy_object(dirObj);
1116 1118 return (DB_INTERNAL_ERROR);
1117 1119 }
1118 1120
1119 1121 st = endTransaction(xid, dirObj);
1120 1122 if (st != 0)
1121 1123 stat = DB_INTERNAL_ERROR;
1122 1124
1123 1125 if (curObj != 0)
1124 1126 nis_destroy_object(curObj);
1125 1127 nis_destroy_object(dirObj);
1126 1128 }
1127 1129
1128 1130 /*
1129 1131 * If it's a table or directory, create the DB file.
1130 1132 * If a directory, also add it to the serving list.
1131 1133 */
1132 1134 if (stat == DB_SUCCESS &&(isDir || isTable)) {
1133 1135 if (isDir) {
1134 1136 stat = db_create_table(objTable,
1135 1137 &tbl_prototype);
1136 1138 } else {
1137 1139 stat = dbCreateTable(objTable, o);
1138 1140 }
1139 1141 }
1140 1142 sfree(objTable);
1141 1143 }
1142 1144
1143 1145 sfree(b.buf);
1144 1146
1145 1147 return (stat);
1146 1148 }
1147 1149
1148 1150 /*
1149 1151 * Replace the object stored with the mapping 't'. Return TRUE if
1150 1152 * at least one object was replaced, FALSE otherwise.
1151 1153 */
1152 1154 bool_t
1153 1155 replaceMappingObj(__nis_table_mapping_t *t, nis_object *n) {
1154 1156 __nis_table_mapping_t *x;
1155 1157 nis_object *old = 0;
1156 1158 int assigned = 0;
1157 1159
1158 1160 /*
1159 1161 * The alternate mappings are usually mostly copies
1160 1162 * of the original, so we try to make sure that we
1161 1163 * don't free the same nis_object twice.
1162 1164 */
1163 1165 for (x = t; x != 0; x = (__nis_table_mapping_t *)x->next) {
1164 1166 if (old == 0) {
1165 1167 old = x->obj;
1166 1168 if (x->obj != 0)
1167 1169 nis_destroy_object(x->obj);
1168 1170 } else {
1169 1171 if (x->obj != old && x->obj != 0)
1170 1172 nis_destroy_object(x->obj);
1171 1173 }
1172 1174 x->obj = n;
1173 1175 assigned++;
1174 1176 }
1175 1177
1176 1178 return (assigned > 0);
1177 1179 }
1178 1180
1179 1181 /*
1180 1182 * Set object type, column info, and obj for the specified
1181 1183 * mapping 't' from the object 'o'. Returns zero if 'o' was unused,
1182 1184 * and should be freed by the caller, larger than zero otherwise.
1183 1185 */
1184 1186 int
1185 1187 setMappingObjTypeEtc(__nis_table_mapping_t *t, nis_object *o) {
1186 1188 __nis_table_mapping_t *x;
1187 1189 int ls, ret;
1188 1190 int i;
1189 1191
1190 1192 if (t == 0 || o == 0)
1191 1193 return (0);
1192 1194
1193 1195 t->objType = o->zo_data.zo_type;
1194 1196 for (x = t; x != 0; x = (__nis_table_mapping_t *)x->next) {
1195 1197 if (x != t) {
1196 1198 x->objType = t->objType;
1197 1199 }
1198 1200 if (x->objType == NIS_TABLE_OBJ) {
1199 1201 /*
1200 1202 * If we have rules, this mapping is for table entries,
1201 1203 * and we need the column names. Otherwise, remove the
1202 1204 * column names (if any).
1203 1205 */
1204 1206
1205 1207 for (i = 0; i < x->numColumns; i++)
1206 1208 sfree(x->column[i]);
1207 1209 sfree(x->column);
1208 1210 x->column = 0;
1209 1211 x->numColumns = 0;
1210 1212 }
1211 1213 }
1212 1214 ret = replaceMappingObj(t, o);
1213 1215
1214 1216 return (ret);
1215 1217 }
1216 1218
1217 1219 /*
1218 1220 * Retrieve the specified object (internal DB name) from LDAP, and
1219 1221 * refresh/create as appropriate.
1220 1222 */
1221 1223 db_status
1222 1224 dbCreateFromLDAP(char *intName, int *ldapStat) {
1223 1225 __nis_table_mapping_t *t;
1224 1226 int lstat, doDestroy;
1225 1227 nis_object *obj = 0;
1226 1228 db_status dstat;
1227 1229 const char *myself = "dbCreateFromLDAP";
1228 1230
1229 1231 if (!useLDAPrespository) {
1230 1232 if (ldapStat != 0)
1231 1233 *ldapStat = LDAP_SUCCESS;
1232 1234 return (DB_SUCCESS);
1233 1235 }
1234 1236
1235 1237 t = (__nis_table_mapping_t *)__nis_find_item_mt(intName,
1236 1238 &ldapMappingList,
1237 1239 0, 0);
1238 1240
1239 1241 /* No mapping isn't a failure */
1240 1242 if (t == 0) {
1241 1243 if (ldapStat != 0)
1242 1244 *ldapStat = LDAP_SUCCESS;
1243 1245 return (DB_NOTFOUND);
1244 1246 }
1245 1247
1246 1248 lstat = objFromLDAP(t, &obj, 0, 0);
1247 1249 if (ldapStat != 0)
1248 1250 *ldapStat = lstat;
1249 1251 if (lstat != LDAP_SUCCESS)
1250 1252 return (DB_NOTFOUND);
1251 1253
1252 1254 /*
1253 1255 * If the LDAP operation was successful, but 'obj' is NULL,
1254 1256 * there's no mapping for this object, and we're done.
1255 1257 */
1256 1258 if (obj == 0)
1257 1259 return (DB_SUCCESS);
1258 1260
1259 1261 /* Update the mapping with object info */
1260 1262 doDestroy = setMappingObjTypeEtc(t, obj) == 0;
1261 1263
1262 1264 dstat = dbRefreshObj(t->objName, obj);
1263 1265
1264 1266 if (doDestroy)
1265 1267 nis_destroy_object(obj);
1266 1268
1267 1269 return (dstat);
1268 1270 }
1269 1271
1270 1272 /*
1271 1273 * Up- (fromLDAP==0) or down- (fromLDAP==1) load all LDAP mapped data.
1272 1274 * Returns an LDAP error status.
1273 1275 */
1274 1276 int
1275 1277 loadAllLDAP(int fromLDAP, void *cookie, db_status *dstatP) {
1276 1278 __nis_table_mapping_t *t, *start;
1277 1279 int stat = LDAP_SUCCESS;
1278 1280 db_status dstat = DB_SUCCESS;
1279 1281 db *dbase;
1280 1282 db_table_desc *tbl = 0;
1281 1283 db_mindex *mindex;
1282 1284 const char *myself = "loadAllLDAP";
1283 1285
1284 1286 /*
1285 1287 * If the 'cookie' and '*cookie' are non-NULL, start scanning
1286 1288 * the mappings from '*cookie'. When we return with an error,
1287 1289 * we set '*cookie' to point to the mapping being processed.
1288 1290 * This enables our caller to react appropriately, and retry
1289 1291 * if desired.
1290 1292 *
1291 1293 * The cookie is opaque to our caller, who's only allowed to
1292 1294 * initialize *cookie to NULL.
1293 1295 */
1294 1296 if (cookie != 0) {
1295 1297 start = *((__nis_table_mapping_t **)cookie);
1296 1298 if (start == 0)
1297 1299 start = ldapMappingSeq;
1298 1300 } else {
1299 1301 start = ldapMappingSeq;
1300 1302 }
1301 1303
1302 1304 for (t = start; t != 0; t = (__nis_table_mapping_t *)t->seqNext) {
1303 1305 __nis_table_mapping_t **tp;
1304 1306 int nm;
1305 1307
1306 1308 if (fromLDAP) {
1307 1309 /* Are there any mappings for the object proper ? */
1308 1310 tp = selectTableMapping(t, 0, 0, 1, t->dbId, &nm);
1309 1311 if (tp != 0 && nm > 0) {
1310 1312 dstat = dbCreateFromLDAP(t->objPath, &stat);
1311 1313 if (dstat != DB_SUCCESS) {
1312 1314 logmsg(MSG_NOTIMECHECK, LOG_ERR,
1313 1315 "%s: DB error %d creating \"%s\": %s",
1314 1316 myself, dstat, NIL(t->objName),
1315 1317 ldap_err2string(stat));
1316 1318 if (cookie != 0)
1317 1319 *((__nis_table_mapping_t **)
1318 1320 cookie) = t;
1319 1321 if (dstatP != 0)
1320 1322 *dstatP = dstat;
1321 1323 else if (stat == LDAP_SUCCESS)
1322 1324 stat = LDAP_OPERATIONS_ERROR;
1323 1325 sfree(tp);
1324 1326 return (stat);
1325 1327 }
1326 1328 }
1327 1329 sfree(tp);
1328 1330
1329 1331 /* Any mappings for table entries ? */
1330 1332 tp = selectTableMapping(t, 0, 0, 0, t->dbId, &nm);
1331 1333 if (tp == 0 || nm <= 0) {
1332 1334 sfree(tp);
1333 1335 continue;
1334 1336 }
1335 1337 sfree(tp);
1336 1338
1337 1339 /*
1338 1340 * The object itself must exist in the local
1339 1341 * DB by now. Get the db_mindex and let
1340 1342 * db_mindex::queryLDAP() do the work; if
1341 1343 * the object isn't a table, queryLDAP()
1342 1344 * will do nothing and return success.
1343 1345 */
1344 1346 dbase = InUseDictionary->find_table(t->objPath,
1345 1347 &tbl, TRUE);
1346 1348 if (dbase != 0)
1347 1349 mindex = dbase->mindex();
1348 1350 if (dbase == 0 || tbl == 0 || mindex == 0) {
1349 1351 logmsg(MSG_NOTIMECHECK, LOG_ERR,
1350 1352 "%s: No local DB entry for \"%s\" (%s:%s)",
1351 1353 myself, NIL(t->objPath),
1352 1354 NIL(t->dbId), NIL(t->objName));
1353 1355 if (cookie != 0)
1354 1356 *((__nis_table_mapping_t **)cookie) =
1355 1357 t;
1356 1358 if (dstatP != 0)
1357 1359 *dstatP = DB_BADTABLE;
1358 1360 return ((dstatP != 0) ?
1359 1361 LDAP_SUCCESS : LDAP_OPERATIONS_ERROR);
1360 1362 }
1361 1363 mindex->setInitialLoad();
1362 1364 stat = mindex->queryLDAP(0, t->dbId, 0);
1363 1365 mindex->clearInitialLoad();
1364 1366 if (stat != LDAP_SUCCESS) {
1365 1367 logmsg(MSG_NOTIMECHECK, LOG_ERR,
1366 1368 "%s: LDAP error retrieving entries for %s:%s: %s",
1367 1369 myself, NIL(t->dbId), NIL(t->objName),
1368 1370 ldap_err2string(stat));
1369 1371 if (cookie != 0)
1370 1372 *((__nis_table_mapping_t **)cookie) =
1371 1373 t;
1372 1374 if (dstatP != 0)
1373 1375 *dstatP = DB_SUCCESS;
1374 1376 return (stat);
1375 1377 }
1376 1378 } else {
1377 1379 nis_object *obj;
1378 1380 char *ent, *objPath;
1379 1381 int freeObjPath = 0;
1380 1382
1381 1383 /*
1382 1384 * Up-loading to LDAP, so the object must
1383 1385 * already exist in the local DB.
1384 1386 */
1385 1387 obj = dbFindObject(t->objName, &dstat);
1386 1388 if (obj == 0) {
1387 1389 if (dstat == DB_NOTFOUND)
1388 1390 logmsg(MSG_NOTIMECHECK, LOG_WARNING,
1389 1391 "%s: No local DB object for \"%s\" (%s:%s); skipping up-load",
1390 1392 myself, NIL(t->objPath),
1391 1393 NIL(t->dbId),
1392 1394 NIL(t->objName));
1393 1395 else
1394 1396 logmsg(MSG_NOTIMECHECK, LOG_WARNING,
1395 1397 "%s: DB error %d for \"%s\" (%s:%s); skipping up-load",
1396 1398 myself, dstat,
1397 1399 NIL(t->objPath),
1398 1400 NIL(t->dbId),
1399 1401 NIL(t->objName));
1400 1402 continue;
1401 1403 }
1402 1404
1403 1405 /*
1404 1406 * If it's a table or directory, there will be
1405 1407 * a dictionary entry for the object itself.
1406 1408 * Otherwise, we need the dictionary entry for
1407 1409 * the parent directory.
1408 1410 *
1409 1411 * For a table, we need the db_mindex for both the
1410 1412 * table object itself, as well as for the parent
1411 1413 * directory (in order to store table entries).
1412 1414 * We start with the latter.
1413 1415 */
1414 1416 if (obj->zo_data.zo_type == NIS_DIRECTORY_OBJ) {
1415 1417 objPath = t->objPath;
1416 1418 ent = 0;
1417 1419 } else {
1418 1420 objPath = 0;
1419 1421 ent = entryName(myself, t->objName,
1420 1422 &objPath);
1421 1423 if (ent == 0 || objPath == 0) {
1422 1424 logmsg(MSG_NOTIMECHECK, LOG_ERR,
1423 1425 "%s: Error deriving entry/DB-table names for %s:%s; skipping up-load",
1424 1426 myself, NIL(t->dbId),
1425 1427 NIL(t->objName));
1426 1428 sfree(ent);
1427 1429 sfree(objPath);
1428 1430 nis_destroy_object(obj);
1429 1431 obj = 0;
1430 1432 continue;
1431 1433 }
1432 1434 freeObjPath = 1;
1433 1435 }
1434 1436
1435 1437 dbase = InUseDictionary->find_table(objPath,
1436 1438 &tbl, TRUE);
1437 1439 if (dbase != 0)
1438 1440 mindex = dbase->mindex();
1439 1441 if (dbase == 0 || tbl == 0 || mindex == 0) {
1440 1442 logmsg(MSG_NOTIMECHECK, LOG_WARNING,
1441 1443 "%s: No local DB entry for \"%s\" (%s:%s); skipping up-load",
1442 1444 myself, objPath,
1443 1445 NIL(t->dbId), NIL(t->objName));
1444 1446 sfree(ent);
1445 1447 if (freeObjPath)
1446 1448 sfree(objPath);
1447 1449 nis_destroy_object(obj);
1448 1450 obj = 0;
1449 1451 continue;
1450 1452 }
1451 1453
1452 1454 /*
1453 1455 * Our next action(s) depend on the object type:
1454 1456 *
1455 1457 * directory Store dir object
1456 1458 *
1457 1459 * table Store table obj, as well
1458 1460 * as any entries in the
1459 1461 * table
1460 1462 *
1461 1463 * other Store object; we need to
1462 1464 * build a db_query specifying
1463 1465 * the first-level name of the
1464 1466 * object.
1465 1467 *
1466 1468 * storeLDAP() will just do nothing and return
1467 1469 * success if we try to, say, store a table object
1468 1470 * when only the table entries are mapped. Hence,
1469 1471 * we don't have to worry about those distinctions
1470 1472 * here.
1471 1473 */
1472 1474 if (obj->zo_data.zo_type == NIS_DIRECTORY_OBJ) {
1473 1475 stat = mindex->storeLDAP(0, 0, obj, 0, t->dbId);
1474 1476 } else {
1475 1477 nis_attr attr;
1476 1478 db_query *q;
1477 1479
1478 1480 attr.zattr_ndx = (char *)"name";
1479 1481 attr.zattr_val.zattr_val_val = ent;
1480 1482 attr.zattr_val.zattr_val_len = slen(ent) + 1;
1481 1483
1482 1484 q = new db_query(mindex->getScheme(), 1, &attr);
1483 1485 if (q == 0) {
1484 1486 logmsg(MSG_NOTIMECHECK, LOG_ERR,
1485 1487 "%s: error creating db_query for \"%s\" in \"%s\"; skipping up-load",
1486 1488 myself, ent, objPath);
1487 1489 sfree(ent);
1488 1490 if (freeObjPath)
1489 1491 sfree(objPath);
1490 1492 nis_destroy_object(obj);
1491 1493 obj = 0;
1492 1494 continue;
1493 1495 }
1494 1496
1495 1497 stat = mindex->storeLDAP(q, 0, obj, 0, t->dbId);
1496 1498
1497 1499 delete q;
1498 1500
1499 1501 }
1500 1502
1501 1503 sfree(ent);
1502 1504 if (freeObjPath)
1503 1505 sfree(objPath);
1504 1506
1505 1507 if (stat != LDAP_SUCCESS) {
1506 1508 logmsg(MSG_NOTIMECHECK, LOG_ERR,
1507 1509 "%s: Error storing %s:%s to LDAP: %s",
1508 1510 myself, NIL(t->dbId), NIL(t->objName),
1509 1511 ldap_err2string(stat));
1510 1512 nis_destroy_object(obj);
1511 1513 obj = 0;
1512 1514 if (cookie != 0)
1513 1515 *((__nis_table_mapping_t **)
1514 1516 cookie) = t;
1515 1517 if (dstatP != 0)
1516 1518 *dstatP = DB_SUCCESS;
1517 1519 return (stat);
1518 1520 }
1519 1521
1520 1522 /* Any mappings for table entries ? */
1521 1523 tp = selectTableMapping(t, 0, 0, 0, t->dbId, &nm);
1522 1524 if (tp == 0 || nm <= 0) {
1523 1525 sfree(tp);
1524 1526 nis_destroy_object(obj);
1525 1527 obj = 0;
1526 1528 continue;
1527 1529 }
1528 1530 sfree(tp);
1529 1531
1530 1532 /*
1531 1533 * If it's a table, we also need to store the table
1532 1534 * entries.
1533 1535 */
1534 1536 if (obj->zo_data.zo_type == NIS_TABLE_OBJ) {
1535 1537 tbl = 0;
1536 1538 dbase = InUseDictionary->find_table(t->objPath,
1537 1539 &tbl, TRUE);
1538 1540 if (dbase != 0)
1539 1541 mindex = dbase->mindex();
1540 1542 if (dbase == 0 || tbl == 0 || mindex == 0) {
1541 1543 logmsg(MSG_NOTIMECHECK, LOG_WARNING,
1542 1544 "%s: No local DB entry for \"%s\" (%s:%s); skipping entry up-load",
1543 1545 myself, NIL(t->objPath),
1544 1546 NIL(t->dbId), NIL(t->objName));
1545 1547 nis_destroy_object(obj);
1546 1548 obj = 0;
1547 1549 continue;
1548 1550 }
1549 1551
1550 1552 stat = mindex->storeLDAP(0, 0, obj, 0, t->dbId);
1551 1553
1552 1554 if (stat != LDAP_SUCCESS) {
1553 1555 logmsg(MSG_NOTIMECHECK, LOG_ERR,
1554 1556 "%s: Error storing %s:%s entries to LDAP: %s",
1555 1557 myself, NIL(t->dbId),
1556 1558 NIL(t->objName),
1557 1559 ldap_err2string(stat));
1558 1560 nis_destroy_object(obj);
1559 1561 obj = 0;
1560 1562 if (cookie != 0)
1561 1563 *((__nis_table_mapping_t **)
1562 1564 cookie) = t;
1563 1565 if (dstatP != 0)
1564 1566 *dstatP = DB_SUCCESS;
1565 1567 return (stat);
1566 1568 }
1567 1569 }
1568 1570 nis_destroy_object(obj);
1569 1571 obj = 0;
1570 1572 }
1571 1573 }
1572 1574
1573 1575 if (dstatP != 0)
1574 1576 *dstatP = dstat;
1575 1577 return (stat);
1576 1578 }
1577 1579
1578 1580 /*
1579 1581 * Object identified by given attribute name is added to specified table.
1580 1582 * If object already exists, it is replaced. If more than one object
1581 1583 * matches the given attribute name, DB_NOTUNIQUE is returned.
1582 1584 */
1583 1585 static
1584 1586 db_result *
1585 1587 db_add_entry_x(char * tab, int numattrs, nis_attr * attrname,
1586 1588 entry_obj * newobj, int skiplog, int nosync)
1587 1589 {
1588 1590 db_result * safety = empty_result(DB_SUCCESS);
1589 1591 db_table_desc * tbl = NULL;
1590 1592 db * dbase = InUseDictionary->find_table(tab, &tbl, FALSE);
1591 1593
1592 1594 if (tbl == NULL || dbase == NULL) {
1593 1595 return (set_result(safety, DB_BADTABLE));
1594 1596 } else if (skiplog) {
1595 1597 db_result * res;
1596 1598 res = dbase->execute(DB_ADD_NOLOG, NULL,
1597 1599 (entry_object *) newobj, NULL);
1598 1600 if (safety) delete safety;
1599 1601 return (res);
1600 1602 } else {
1601 1603 db_result *res;
1602 1604 db_query *
1603 1605 query = InUseDictionary->translate_to_query(tbl,
1604 1606 numattrs, attrname);
1605 1607 if (query == NULL)
1606 1608 return (set_result(safety, DB_BADQUERY));
1607 1609 if (nosync)
1608 1610 res = dbase->execute(DB_ADD_NOSYNC,
1609 1611 query, (entry_object *) newobj, NULL);
1610 1612 else
1611 1613 res = dbase->execute(DB_ADD, query,
1612 1614 (entry_object *) newobj, NULL);
1613 1615 delete query;
1614 1616 if (safety) delete safety;
1615 1617 return (res);
1616 1618 }
1617 1619 }
1618 1620
1619 1621 db_result *
1620 1622 db_add_entry(char * tab, int numattrs, nis_attr * attrname,
1621 1623 entry_obj * newobj)
1622 1624 {
1623 1625 return (db_add_entry_x(tab, numattrs, attrname, newobj, 0, 0));
1624 1626 }
1625 1627
1626 1628 db_result *
1627 1629 __db_add_entry_nolog(char * tab, int numattrs, nis_attr * attrname,
1628 1630 entry_obj * newobj)
1629 1631 {
1630 1632 return (db_add_entry_x(tab, numattrs, attrname, newobj, 1, 0));
1631 1633 }
1632 1634
1633 1635 db_result *
1634 1636 __db_add_entry_nosync(char * tab, int numattrs, nis_attr * attrname,
1635 1637 entry_obj * newobj)
1636 1638 {
1637 1639 return (db_add_entry_x(tab, numattrs, attrname, newobj, 0, 1));
1638 1640 }
1639 1641
1640 1642 /*
1641 1643 * Remove object identified by given attributes from specified table.
1642 1644 * If no attribute is supplied, all entries in table are removed.
1643 1645 * If attributes identify more than one object, all objects are removed.
1644 1646 */
1645 1647
1646 1648 db_result *
1647 1649 db_remove_entry_x(char * table_name, int num_attrs, nis_attr * attrname,
1648 1650 int nosync)
1649 1651 {
1650 1652 db_result * safety = empty_result(DB_SUCCESS);
1651 1653 db_table_desc * tbl = NULL;
1652 1654 db * dbase = InUseDictionary->find_table(table_name, &tbl, FALSE);
1653 1655 db_result * res;
1654 1656
1655 1657 if (tbl == NULL || dbase == NULL)
1656 1658 return (set_result(safety, DB_BADTABLE));
1657 1659 else {
1658 1660 if (num_attrs != 0) {
1659 1661 db_query *query;
1660 1662 query = InUseDictionary->translate_to_query(tbl,
1661 1663 num_attrs, attrname);
1662 1664 if (query == NULL)
1663 1665 return (set_result(safety,
1664 1666 DB_BADQUERY));
1665 1667 if (nosync)
1666 1668 res = dbase->execute(DB_REMOVE_NOSYNC,
1667 1669 query, NULL, NULL);
1668 1670 else
1669 1671 res = dbase->execute(DB_REMOVE, query,
1670 1672 NULL, NULL);
1671 1673 delete query;
1672 1674 } else {
1673 1675 if (nosync)
1674 1676 res = dbase->execute(DB_REMOVE_NOSYNC,
1675 1677 NULL, NULL, NULL);
1676 1678 else
1677 1679 res = dbase->execute(DB_REMOVE,
1678 1680 NULL, NULL, NULL);
1679 1681 }
1680 1682 if (safety) delete safety;
1681 1683 return (res);
1682 1684 }
1683 1685 }
1684 1686
1685 1687 db_result *
1686 1688 db_remove_entry(char * table_name, int num_attrs, nis_attr * attrname)
1687 1689 {
1688 1690 return (db_remove_entry_x(table_name, num_attrs, attrname, 0));
1689 1691 }
1690 1692
1691 1693 db_result *
1692 1694 __db_remove_entry_nosync(char * table_name, int num_attrs, nis_attr * attrname)
1693 1695 {
1694 1696 return (db_remove_entry_x(table_name, num_attrs, attrname, 1));
1695 1697 }
1696 1698
1697 1699 /* Return a copy of the version of specified table. */
1698 1700 vers *
1699 1701 db_version(char * table_name)
1700 1702 {
1701 1703 db * dbase = InUseDictionary->find_table(table_name);
1702 1704
1703 1705 if (dbase == NULL)
1704 1706 return (NULL);
1705 1707 vers* v = new vers(dbase->get_version());
1706 1708 if (v == NULL)
1707 1709 WARNING("nis_db::db_version: cannot allocate space");
1708 1710 return (v);
1709 1711 }
1710 1712
1711 1713 /* Return log entries since (later than) given version 'v' of table. */
1712 1714 db_log_list *
1713 1715 db_log_entries_since(char * table_name, vers * v)
1714 1716 {
1715 1717 db * dbase = InUseDictionary->find_table(table_name);
1716 1718
1717 1719 if (dbase == NULL)
1718 1720 return (NULL);
1719 1721 return (dbase->get_log_entries_since(v));
1720 1722 }
1721 1723
1722 1724 db_status
1723 1725 db_sync_log(char *table_name) {
1724 1726
1725 1727 db * dbase = InUseDictionary->find_table(table_name);
1726 1728
1727 1729 if (dbase == NULL)
1728 1730 return (DB_BADTABLE);
1729 1731 return (dbase->sync_log());
1730 1732 }
1731 1733
1732 1734 /*
1733 1735 * Apply the given update specified in 'entry' to the specified table.
1734 1736 * Returns DB_SUCCESS if update was executed.
1735 1737 * Returns DB_NOTFOUND if update occurs too early to be applied.
1736 1738 */
1737 1739 db_status
1738 1740 db_apply_log_entry(char * table_name, db_log_entry * entry)
1739 1741 {
1740 1742 db * dbase = InUseDictionary->find_table(table_name, NULL, FALSE);
1741 1743
1742 1744 if (dbase == NULL)
1743 1745 return (DB_BADTABLE);
1744 1746 if (dbase->execute_log_entry(entry))
1745 1747 return (DB_SUCCESS); /* got executed */
1746 1748 else
1747 1749 return (DB_NOTFOUND); /* not executed */
1748 1750 }
1749 1751
1750 1752 /*
1751 1753 * Checkpoint specified table (i.e. incorporate logged updates to main
1752 1754 * database file). If table_name is NULL, checkpoint all tables that
1753 1755 * needs it.
1754 1756 */
1755 1757 db_status
1756 1758 db_checkpoint(char * table_name)
1757 1759 {
1758 1760 return (InUseDictionary->db_checkpoint(table_name));
1759 1761 }
1760 1762
1761 1763 /* Print names of tables in system. */
1762 1764 void
1763 1765 db_print_table_names()
1764 1766 {
1765 1767 int i;
1766 1768 db_table_names * answer = InUseDictionary->get_table_names();
1767 1769
1768 1770 if (answer != NULL) {
1769 1771 for (i = 0; i < answer->db_table_names_len; i++) {
1770 1772 printf("%s\n", answer->db_table_names_val[i]);
1771 1773 delete answer->db_table_names_val[i];
1772 1774 }
1773 1775 delete answer->db_table_names_val;
1774 1776 delete answer;
1775 1777 }
1776 1778 }
1777 1779
1778 1780 /* Print statistics of specified table to stdout. */
1779 1781 db_status
1780 1782 db_stats(char * table_name)
1781 1783 {
1782 1784 db_table_desc * tbl = NULL;
1783 1785 db *dbase = InUseDictionary->find_table(table_name, &tbl);
1784 1786
1785 1787 if (tbl == NULL || dbase == NULL || tbl->scheme == NULL)
1786 1788 return (DB_BADTABLE);
1787 1789
1788 1790 dbase->print();
1789 1791 tbl->scheme->print();
1790 1792 return (DB_SUCCESS);
1791 1793 }
1792 1794
1793 1795
1794 1796 /* Print statistics of indices of specified table to stdout. */
1795 1797 db_status
1796 1798 db_print_all_indices(char * table_name)
1797 1799 {
1798 1800 db * dbase = InUseDictionary->find_table(table_name);
1799 1801
1800 1802 if (dbase == NULL)
1801 1803 return (DB_BADTABLE);
1802 1804 dbase->print_all_indices();
1803 1805 return (DB_SUCCESS);
1804 1806 }
1805 1807
1806 1808 /* Print specified index of table to stdout. */
1807 1809 db_status
1808 1810 db_print_index(char * table_name, int which)
1809 1811 {
1810 1812 db * dbase = InUseDictionary->find_table(table_name);
1811 1813
1812 1814 if (dbase == NULL)
1813 1815 return (DB_BADTABLE);
1814 1816 dbase->print_index(which);
1815 1817 return (DB_SUCCESS);
1816 1818 }
1817 1819
1818 1820 /* close open files */
1819 1821 db_status
1820 1822 db_standby(char * table_name)
1821 1823 {
1822 1824 return (InUseDictionary->db_standby(table_name));
1823 1825 }
1824 1826
1825 1827 /* Returns DB_SUCCESS if table exists; DB_BADTABLE if table does not exist. */
1826 1828 db_status
1827 1829 db_table_exists(char * table_name)
1828 1830 {
1829 1831 db_table_desc *dbtab = InUseDictionary->find_table_desc(table_name);
1830 1832
1831 1833 if (dbtab == NULL)
1832 1834 return (DB_BADTABLE);
1833 1835 return (DB_SUCCESS);
1834 1836 }
1835 1837
1836 1838 /*
1837 1839 * Returns DB_SUCCESS if table exists; DB_BADTABLE if table does not exist.
1838 1840 * If table already loaded, unload it.
1839 1841 */
1840 1842 db_status
1841 1843 db_unload_table(char * table_name)
1842 1844 {
1843 1845 db_table_desc *
1844 1846 dbtab = InUseDictionary->find_table_desc(table_name);
1845 1847 if (dbtab == NULL)
1846 1848 return (DB_BADTABLE);
1847 1849 // unload
1848 1850 if (dbtab->database != NULL) {
1849 1851 delete dbtab->database;
1850 1852 dbtab->database = NULL;
1851 1853 }
1852 1854 return (DB_SUCCESS);
1853 1855 }
1854 1856
1855 1857 /*
1856 1858 * Put the specified table in deferred mode, which means that updates go
1857 1859 * to the original table, but reads are satisfied out of a copy (which we
1858 1860 * make here). Thus, "defer" refers to the table as seen by read requests,
1859 1861 * since for them, changes are deferred.
1860 1862 */
1861 1863 db_status
1862 1864 __db_defer(char *table_name) {
1863 1865 db_status stat;
1864 1866
1865 1867 stat = InUseDictionary->defer(table_name);
1866 1868 return (stat);
1867 1869 }
1868 1870
1869 1871 /*
1870 1872 * Commit deferred changes for the specified table. I.e., make visible
1871 1873 * any updates made since the table was deferred.
1872 1874 */
1873 1875 db_status
1874 1876 __db_commit(char *table_name) {
1875 1877 db_status stat;
1876 1878
1877 1879 stat = InUseDictionary->commit(table_name);
1878 1880 return (stat);
1879 1881 }
1880 1882
1881 1883 /*
1882 1884 * Rollback, i.e., return to the state before we entered deferred mode.
1883 1885 */
1884 1886 db_status
1885 1887 __db_rollback(char *table_name) {
1886 1888 db_status stat;
1887 1889
1888 1890 stat = InUseDictionary->rollback(table_name);
1889 1891 return (stat);
1890 1892 }
1891 1893
1892 1894 db_status
1893 1895 __db_configure(char *table_name) {
1894 1896 db_status stat;
1895 1897 char tablePath[MAXPATHLEN + NIS_MAXNAMELEN + 1];
1896 1898 db *dbase = InUseDictionary->find_table(table_name, NULL);
1897 1899
1898 1900 if (dbase == NULL || table_name == 0)
1899 1901 return (DB_BADTABLE);
1900 1902
1901 1903 if (strlen(table_name) >= sizeof (tablePath))
1902 1904 return (DB_BADQUERY);
1903 1905
1904 1906 if (internal_table_name(table_name, tablePath) == 0)
1905 1907 return (DB_STORAGE_LIMIT);
1906 1908
1907 1909 if (dbase->configure(tablePath))
1908 1910 stat = DB_SUCCESS;
1909 1911 else
1910 1912 stat = DB_INTERNAL_ERROR;
1911 1913
1912 1914 return (stat);
1913 1915 }
1914 1916
1915 1917 /*
1916 1918 * During some rpc.nisd operations (such as when recovering the trans.log),
1917 1919 * we don't want to use the LDAP repository, so we provide a main switch.
1918 1920 * Note that we expect this to be used only when rpc.nisd is single-threaded,
1919 1921 * so there is no need for synchronization when reading or modifying the
1920 1922 * value of the main switch.
1921 1923 */
1922 1924 int useLDAPrespository = 1;
1923 1925
1924 1926 void
1925 1927 __db_disallowLDAP(void) {
1926 1928 useLDAPrespository = 0;
1927 1929 }
1928 1930
1929 1931 void
1930 1932 __db_allowLDAP(void) {
1931 1933 useLDAPrespository = 1;
1932 1934 }
1933 1935
1934 1936 } /* extern "C" */
↓ open down ↓ |
1777 lines elided |
↑ open up ↑ |
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX