|
|
1.1 ! root 1: #ifndef lint ! 2: static char *rcsid = "$Header: xnsrestore.c,v 1.1 87/03/17 16:27:26 ed Exp $"; ! 3: #endif lint ! 4: ! 5: /* ! 6: * Copyright (c) 1986, 1987 Xerox Corporation. ! 7: */ ! 8: ! 9: /* $Log: xnsrestore.c,v $ ! 10: * Revision 1.1 87/03/17 16:27:26 ed ! 11: * Initial revision ! 12: * ! 13: * ! 14: */ ! 15: ! 16: #include <stdio.h> ! 17: #include <sys/time.h> ! 18: #include <sys/param.h> ! 19: #include <sys/stat.h> ! 20: #include <netns/ns.h> ! 21: #include <netns/sp.h> ! 22: #include <xnscourier/Filing4.h> ! 23: #include <xnscourier/except.h> ! 24: #include <xnscourier/CH.h> ! 25: #include <xnscourier/filetypes.h> ! 26: #define XNS_TIME_DIFFERENCE 2177452800 /* [(1970-1901) years * 365 days/year + 17 leap days */ ! 27: /* * 24 hours/day * 60 minutes/hour * 60 seconds/minute */ ! 28: ! 29: #define ROOT_DIRECTORY "/" ! 30: ! 31: CourierConnection *connected; ! 32: Clearinghouse2_ObjectName hostobjname; ! 33: Authentication2_Verifier verifier; ! 34: ! 35: /* the following 3 items make up the current session */ ! 36: Filing4_Session session; /* the current session */ ! 37: Clearinghouse2_ObjectName username; ! 38: Filing4_Handle rootHandle; ! 39: char cur_dir[512]= 0; ! 40: char cur_pathname[512]= 0; ! 41: char cur_name[512]= 0; ! 42: ! 43: static Filing4_ControlSequence nullControls = {0,0}; ! 44: ! 45: /* global data used to communicate with BDT procedures ! 46: */ ! 47: extern GetAttributeSequences(), ! 48: listproc(), isdirproc(), storeproc(); ! 49: ! 50: char *AttrToString(); ! 51: Boolean AttrToBoolean(); ! 52: LongCardinal AttrToLongCardinal(); ! 53: Cardinal AttrToCardinal(); ! 54: ! 55: static (*ProcEachSeq)(); ! 56: static FILE *fin= NULL; ! 57: FILE *lfile= NULL; ! 58: ! 59: Boolean files_found= FALSE; ! 60: Boolean verbose= FALSE; ! 61: Boolean is_a_directory= FALSE; ! 62: char *logfile= 0; ! 63: ! 64: char *ctime(); ! 65: long time(); ! 66: char *service; ! 67: extern int errno; ! 68: ! 69: main(argc, argv) ! 70: int argc; ! 71: char *argv[]; ! 72: { ! 73: char *remotefile; ! 74: char *localfile; ! 75: int i; ! 76: CourierConnection *hookup(); ! 77: int opt; ! 78: extern int optind; ! 79: extern char *optarg; ! 80: ! 81: static char *options= "vl:"; ! 82: static char *usage= "Usage: %s [-v] [-l log-file] local-file remote-file\n"; ! 83: ! 84: if ( argc < 3 ) { ! 85: fprintf(stderr, usage, argv[0]); ! 86: exit(1); ! 87: } ! 88: ! 89: while ((opt= getopt(argc, argv, options)) != EOF) ! 90: switch (opt) { ! 91: case 'l' : ! 92: logfile= optarg; ! 93: break; ! 94: ! 95: case 'v' : ! 96: verbose++; ! 97: break; ! 98: ! 99: default: ! 100: fprintf(stderr, "Invalid command option -%c\n", opt); ! 101: exit(1); ! 102: } ! 103: ! 104: localfile= argv[optind]; ! 105: optind++; ! 106: if ( getserviceandfile(argv[optind], &service, &remotefile) == 0 ) { ! 107: fprintf(stderr, "Invalid name %s\n", argv[optind]); ! 108: exit(1); ! 109: } ! 110: DURING { ! 111: if ( (connected= hookup(service)) == (CourierConnection *)0 ) { ! 112: fprintf(stderr, "\nCan't connect to %s\n", service); ! 113: exit(1); ! 114: } ! 115: login(0,0); ! 116: ! 117: deserializefile(localfile, remotefile); ! 118: ! 119: } HANDLER { ! 120: FilingErrMsg(Exception.Code, Exception.Message); ! 121: } END_HANDLER; ! 122: ! 123: return(0); ! 124: } ! 125: ! 126: getserviceandfile(name, srvcptr, fileptr) ! 127: char *name; ! 128: char **srvcptr, **fileptr; ! 129: { ! 130: char *sptr, *fptr; ! 131: char *index(), *rindex(); ! 132: ! 133: /* ! 134: * look for Xerox forms first: ! 135: * [host]filename ! 136: */ ! 137: ! 138: if ( (sptr= index(name, '[')) != 0 ) { ! 139: if ( (fptr= index(sptr, ']')) != 0 ) { ! 140: *fptr= '\0'; ! 141: *srvcptr= sptr + 1; ! 142: *fileptr= fptr + 1; ! 143: return(1); ! 144: } else ! 145: return(0); ! 146: } ! 147: ! 148: /* ! 149: * (host)filename ! 150: */ ! 151: ! 152: if ( (sptr= index(name, '(')) != 0 ) { ! 153: if ( (fptr= index(sptr, ')')) != 0 ) { ! 154: *fptr= '\0'; ! 155: *srvcptr= sptr + 1; ! 156: *fileptr= fptr + 1; ! 157: return(1); ! 158: } else ! 159: return(0); ! 160: } ! 161: ! 162: /* ! 163: * look for XNS style with trailing : delimiter ! 164: * (assumes no : in file name, use alternate spec instead) ! 165: * object:domain:organization:filename ! 166: * domain & organization are optional ! 167: */ ! 168: ! 169: if ( (fptr= rindex(name, ':')) != 0 ) { ! 170: *fptr= '\0'; ! 171: *srvcptr= name; ! 172: *fileptr= fptr + 1; ! 173: return(1); ! 174: } else ! 175: return(0); ! 176: } ! 177: ! 178: copyhandle(dest,src) ! 179: Filing4_Handle dest,src; ! 180: { ! 181: if (dest == (Unspecified *) 0) { ! 182: fprintf(stderr,"Oops. dest is null in copyhandle\n"); ! 183: exit(1); ! 184: } ! 185: dest[0] = src[0]; ! 186: dest[1] = src[1]; ! 187: } ! 188: ! 189: getfilehandle(filename, handle) ! 190: char *filename; ! 191: Filing4_Handle handle; ! 192: { ! 193: Filing4_Attribute pathattr[1]; ! 194: Filing4_AttributeSequence attrseq; ! 195: Filing4_OpenResults openresult; ! 196: Filing4_OpenResults openresult2; ! 197: ! 198: if (filename == (char *)0 || *filename == '\000' || (strcmp(filename, "/") == 0) ) { ! 199: copyhandle(handle,rootHandle); ! 200: return; ! 201: } ! 202: ! 203: attrseq.length = 1; ! 204: attrseq.sequence = pathattr; ! 205: pathattr[0].type = Filing4_pathname; ! 206: copyhandle(handle, Filing4_nullHandle); ! 207: #ifdef XEROXFSCOMPATIBILITY ! 208: if ( filename[0] == '/') ! 209: StringToAttr(filename+1, &pathattr[0]); ! 210: else ! 211: StringToAttr(filename, &pathattr[0]); ! 212: #else XEROXFSCOMPATIBILITY ! 213: StringToAttr(filename, &pathattr[0]); ! 214: #endif XEROXFSCOMPATIBILITY ! 215: alarm(0); ! 216: openresult2 = Filing4_Open(connected, NULL, attrseq, ! 217: handle, nullControls, ! 218: session); ! 219: copyhandle(handle, openresult2.file); ! 220: } ! 221: ! 222: getdirhandle(filename, handle) ! 223: char *filename; ! 224: Filing4_Handle handle; ! 225: { ! 226: Filing4_Attribute pathattr[1]; ! 227: Filing4_AttributeSequence attrseq; ! 228: Filing4_OpenResults openresult; ! 229: Filing4_OpenResults openresult2; ! 230: char *rindex(); ! 231: char *slash; ! 232: ! 233: if (filename == (char *)0 || *filename == '\000' || (strcmp(filename, "/") == 0) ) { ! 234: strcpy(cur_pathname, "/"); ! 235: strcpy(cur_name, "/"); ! 236: copyhandle(handle,rootHandle); ! 237: return; ! 238: } else if ( filename[0] == '/' ) { ! 239: strcpy(cur_pathname, filename); ! 240: } else { ! 241: strcpy(cur_pathname, cur_dir); ! 242: if ( strcmp(cur_pathname, "/") != 0 ) ! 243: strcat(cur_pathname, "/"); ! 244: strcat(cur_pathname, filename); ! 245: } ! 246: ! 247: if ( (slash= rindex(cur_pathname,'/')) == NULL ) ! 248: strcpy(cur_name, cur_pathname); ! 249: else ! 250: strcpy(cur_name, slash+1); ! 251: ! 252: if ( slash == cur_pathname) { ! 253: copyhandle(handle, rootHandle); ! 254: return; ! 255: } ! 256: ! 257: attrseq.length = 1; ! 258: attrseq.sequence = pathattr; ! 259: pathattr[0].type = Filing4_pathname; ! 260: copyhandle(handle, Filing4_nullHandle); ! 261: *slash= '\0'; /* separate pathname from name */ ! 262: #ifdef XEROXFSCOMPATIBILITY ! 263: if ( cur_pathname[0] == '/' ) ! 264: StringToAttr(cur_pathname+1, &pathattr[0]); ! 265: else ! 266: StringToAttr(cur_pathname, &pathattr[0]); ! 267: #else XEROXFSCOMPATIBILITY ! 268: StringToAttr(cur_pathname, &pathattr[0]); ! 269: #endif XEROXFSCOMPATIBILITY ! 270: *slash= '/'; /* and put back */ ! 271: alarm(0); ! 272: openresult2 = Filing4_Open(connected, NULL, attrseq, ! 273: handle, nullControls, session); ! 274: copyhandle(handle, openresult2.file); ! 275: } ! 276: ! 277: freefilehandle(handle) ! 278: Filing4_Handle handle; ! 279: { ! 280: if (handle[0] == Filing4_nullHandle[0] && ! 281: handle[1] == Filing4_nullHandle[1]) ! 282: return; /* don't free nullHandle */ ! 283: if (handle[0] == rootHandle[0] && ! 284: handle[1] == rootHandle[1]) ! 285: return; /* don't free root directory */ ! 286: alarm(0); ! 287: Filing4_Close(connected, NULL, handle, session); ! 288: } ! 289: ! 290: CourierConnection * ! 291: hookup(name) ! 292: char *name; ! 293: { ! 294: register struct ns_addr *hostaddr; ! 295: extern struct ns_addr *getXNSaddr(); ! 296: Clearinghouse2_ObjectName defaultobjname; ! 297: static char hnamebuf[128]; ! 298: CourierConnection *cconn; ! 299: ! 300: CH_NameDefault(&defaultobjname); ! 301: hostobjname = CH_StringToName(name, &defaultobjname); ! 302: if ((hostaddr = CH_LookupAddrDN( hostobjname, 0, hnamebuf, 128))) { ! 303: /* should check here to be sure host is a file service */ ! 304: hostaddr->x_port = htons(5); /* ?? */ ! 305: cconn = CourierOpen(hostaddr); ! 306: /* reset objname to flush wildcards */ ! 307: /* clear_Clearinghouse2_ThreePartName(&hostobjname); */ ! 308: hostobjname = CH_StringToName(hnamebuf, &defaultobjname); ! 309: } ! 310: return(cconn); ! 311: } ! 312: ! 313: ! 314: login(name,pwd) ! 315: char *pwd; ! 316: char *name; ! 317: { ! 318: Filing4_Credentials credentials; ! 319: Filing4_LogonResults logonresult; ! 320: Filing4_AttributeSequence attrseq; ! 321: Filing4_OpenResults openresult; ! 322: ! 323: ! 324: if ( name != 0 ) ! 325: username = CH_StringToName(name,&hostobjname); ! 326: ! 327: if ( name == 0 && pwd == 0 ) { ! 328: GetSimpleCredsAndVerifier(&username, 0, ! 329: &credentials, &verifier); ! 330: } else { ! 331: MakeSimpleCredsAndVerifier(&username,pwd, ! 332: &credentials, &verifier); ! 333: } ! 334: logonresult= Filing4_Logon(connected, NULL, hostobjname, ! 335: credentials, verifier); ! 336: session = logonresult.session; ! 337: ! 338: attrseq.length= 0; ! 339: attrseq.sequence= 0; ! 340: openresult= Filing4_Open(connected, NULL, attrseq, ! 341: Filing4_nullHandle, nullControls, ! 342: session); ! 343: copyhandle(rootHandle, openresult.file); ! 344: strcpy(cur_dir, ROOT_DIRECTORY); ! 345: } ! 346: ! 347: logout() ! 348: { ! 349: Filing4_Logoff(connected, NULL, session); ! 350: clear_Filing4_Session(&session); ! 351: } ! 352: ! 353: ! 354: deserializefile(local, remote) ! 355: char *local; ! 356: char *remote; ! 357: { ! 358: FILE *fopen(); ! 359: Filing4_Handle remotehandle; /* note: an array */ ! 360: Filing4_Handle dirhandle; /* note: an array */ ! 361: Filing4_Handle listhandle; /* note: an array */ ! 362: Filing4_DeserializeResults results; ! 363: Filing4_AttributeSequence attrseq; ! 364: Filing4_Attribute attrvals[50]; ! 365: Filing4_AttributeTypeSequence typeseq; ! 366: Filing4_AttributeType tsvals[10]; ! 367: Filing4_ScopeSequence scopeseq; ! 368: Filing4_Scope scope; ! 369: Filing4_ScopeSequence lscopeseq; ! 370: Filing4_Scope lscope; ! 371: int i; ! 372: char *name, *rindex(); ! 373: long date; ! 374: Boolean piping= FALSE; ! 375: ! 376: name= '\0'; ! 377: ! 378: scopeseq.sequence= &scope; lscopeseq.sequence= &lscope; ! 379: attrseq.sequence= attrvals; ! 380: ! 381: if ( strcmp(local, "-") == 0 ) { ! 382: fin= stdin; ! 383: piping= TRUE; ! 384: } else { ! 385: if ( (fin= fopen(local, "r")) == NULL ) { ! 386: perror("Cannot open local file "); ! 387: return(1); ! 388: } ! 389: if ( (name= rindex(local, '/')) == 0 ) { ! 390: name= local; ! 391: } else { ! 392: name++; ! 393: } ! 394: } ! 395: ! 396: getdirhandle(remote, dirhandle); ! 397: ! 398: scopeseq.length= 1; ! 399: scope.designator= Filing4_filter; ! 400: scope.Filing4_filter_case.designator= Filing4_matches; ! 401: scope.Filing4_filter_case.Filing4_matches_case.attribute.type= Filing4_name; ! 402: StringToAttr(cur_name,&scope.Filing4_filter_case.Filing4_matches_case.attribute); ! 403: ! 404: typeseq.length = 2; typeseq.sequence = tsvals; ! 405: typeseq.sequence[0] = Filing4_name; ! 406: typeseq.sequence[1] = Filing4_isDirectory; ! 407: ! 408: is_a_directory= FALSE; ! 409: ! 410: ProcEachSeq = isdirproc; ! 411: Filing4_List(connected, GetAttributeSequences, dirhandle, ! 412: typeseq, scopeseq, ! 413: BulkData1_immediateSink, session); ! 414: ! 415: if ( files_found ) { ! 416: if ( is_a_directory ) { ! 417: if ( piping ) { ! 418: fprintf(stderr, "Must specify file name when pinput is from stdin\n"); ! 419: return(1); ! 420: } ! 421: freefilehandle(dirhandle); ! 422: getfilehandle(remote, dirhandle); ! 423: strcat(cur_pathname, "/"); ! 424: strcat(cur_pathname, name); ! 425: } else { ! 426: name= cur_name; ! 427: } ! 428: } else { ! 429: name= cur_name; ! 430: } ! 431: ! 432: attrseq.length= 1; ! 433: attrvals[0].type= Filing4_name; ! 434: StringToAttr(name, &attrvals[0]); ! 435: ! 436: if ( AddExtendedDeserializeAttributes(fin, &attrseq) != -1 ) { ! 437: fprintf(stderr, "Cannot determine extended attributes\n"); ! 438: fclose(fin); ! 439: return(1); ! 440: } ! 441: ! 442: if ( verbose ) { ! 443: fprintf(stdout, " Restoring %s\n", cur_pathname); ! 444: } ! 445: ! 446: results= Filing4_Deserialize(connected, storeproc, dirhandle, ! 447: attrseq, nullControls, ! 448: BulkData1_immediateSink, session); ! 449: ! 450: if ( logfile ) { ! 451: if ( (lfile= fopen(logfile, "w")) != NULL ) { ! 452: scopeseq.length= 1; ! 453: scope.designator= Filing4_filter; ! 454: scope.Filing4_filter_case.designator= Filing4_matches; ! 455: scope.Filing4_filter_case.Filing4_matches_case.attribute.type= Filing4_name; ! 456: StringToAttr(name,&scope.Filing4_filter_case.Filing4_matches_case.attribute); ! 457: ! 458: typeseq.length = 2; typeseq.sequence = tsvals; ! 459: typeseq.sequence[0] = Filing4_pathname; ! 460: typeseq.sequence[1] = Filing4_isDirectory; ! 461: ! 462: is_a_directory= FALSE; ! 463: ! 464: ProcEachSeq = isdirproc; ! 465: Filing4_List(connected, GetAttributeSequences, dirhandle, ! 466: typeseq, scopeseq, ! 467: BulkData1_immediateSink, session); ! 468: ! 469: /* ! 470: * for a directory, we list all files... ! 471: * for non-directory, just list it... ! 472: */ ! 473: if ( is_a_directory ) { ! 474: copyhandle(listhandle, results.file); ! 475: lscopeseq.length= 1; ! 476: lscope.designator= Filing4_depth; ! 477: lscope.Filing4_depth_case= Filing4_allDescendants; ! 478: } else { ! 479: copyhandle(listhandle, dirhandle); ! 480: lscopeseq.length= 1; ! 481: lscope.designator= Filing4_filter; ! 482: lscope.Filing4_filter_case.designator= Filing4_matches; ! 483: lscope.Filing4_filter_case.Filing4_matches_case.attribute.type= Filing4_name; ! 484: StringToAttr(name,&lscope.Filing4_filter_case.Filing4_matches_case.attribute); ! 485: ! 486: } ! 487: ! 488: typeseq.length = 4; ! 489: typeseq.sequence[0] = Filing4_pathname; ! 490: typeseq.sequence[1] = Filing4_type; ! 491: typeseq.sequence[2] = Filing4_createdOn; ! 492: typeseq.sequence[3] = Filing4_modifiedOn; ! 493: ! 494: date= time(0); ! 495: fprintf(lfile, "\n\n\tRestore of %s\n\tPerformed on %s\n\n\n", cur_pathname, ctime(&date)); ! 496: fprintf(lfile, "\tFiles restored as follows:\n\n"); ! 497: fprintf(lfile, " Create Date\t\t Modification Date\t Type\t\t\t\t\t\tName\n\n"); ! 498: ! 499: ProcEachSeq = listproc; ! 500: Filing4_List(connected, GetAttributeSequences, listhandle, ! 501: typeseq, lscopeseq, ! 502: BulkData1_immediateSink, session); ! 503: fclose(lfile); ! 504: } ! 505: } ! 506: ! 507: freefilehandle(results.file); ! 508: freefilehandle(dirhandle); ! 509: } ! 510: ! 511: listproc(attr) ! 512: Filing4_AttributeSequence attr; ! 513: { ! 514: int i; ! 515: char *thisname, *typetostring(); ! 516: char createstr[30], modstr[30]; ! 517: LongCardinal thistype, createdate, moddate; ! 518: Filing4_AttributeType t; ! 519: ! 520: files_found= TRUE; ! 521: createdate= moddate= time(0); ! 522: ! 523: for (i = 0; i < attr.length; i++) { ! 524: t = attr.sequence[i].type; ! 525: if (t == Filing4_pathname) { ! 526: thisname = AttrToString(&attr.sequence[i]); ! 527: } else if (t == Filing4_type) { ! 528: thistype = AttrToLongCardinal(&attr.sequence[i]); ! 529: } else if (t == Filing4_createdOn) { ! 530: createdate= AttrToLongCardinal(&attr.sequence[i]); ! 531: createdate= createdate - XNS_TIME_DIFFERENCE; ! 532: strcpy(createstr, ctime(&createdate)); ! 533: createstr[24]= '\0'; ! 534: } else if (t == Filing4_modifiedOn) { ! 535: moddate= AttrToLongCardinal(&attr.sequence[i]); ! 536: moddate= moddate - XNS_TIME_DIFFERENCE; ! 537: strcpy(modstr, ctime(&moddate)); ! 538: modstr[24]= '\0'; ! 539: } ! 540: } ! 541: ! 542: fprintf(lfile, "%s\t%s\t%-16s\t%s\n", createstr+4, modstr+4, typetostring(thistype), thisname); ! 543: clear_String(&thisname); ! 544: } ! 545: ! 546: isdirproc(attr) ! 547: Filing4_AttributeSequence attr; ! 548: { ! 549: int i; ! 550: Filing4_AttributeType t; ! 551: char *thisname; ! 552: ! 553: files_found= TRUE; ! 554: ! 555: for (i = 0; i < attr.length; i++) { ! 556: t = attr.sequence[i].type; ! 557: if (t == Filing4_isDirectory) { ! 558: is_a_directory = AttrToBoolean(&attr.sequence[i]); ! 559: } else if (t == Filing4_name) { ! 560: thisname= AttrToString(&attr.sequence[i]); ! 561: strcpy(cur_name, thisname); ! 562: clear_String(&thisname); ! 563: } ! 564: } ! 565: ! 566: } ! 567: ! 568: #define MAXPACKS 20 ! 569: static ! 570: GetAttributeSequences(conn) ! 571: CourierConnection *conn; ! 572: { ! 573: int count, i; ! 574: Unspecified buffer[MAXWORDS*MAXPACKS], *bp, *bufend; ! 575: Filing4_StreamOfAttributeSequence attrs; ! 576: ! 577: files_found= FALSE; ! 578: ! 579: bufend = buffer; ! 580: bp = buffer+((MAXWORDS-1)*MAXPACKS); /* end of available space */ ! 581: while ((count = BDTread(conn, (char*)bufend, ! 582: MAXWORDS*sizeof(Unspecified))) > 0) { ! 583: bufend += count/sizeof(Unspecified); ! 584: if (bufend > bp) { ! 585: fprintf(stderr,"BDT read too big to fit\n"); ! 586: BDTabort(conn); ! 587: /* should clear out stuff here if we knew how much */ ! 588: } ! 589: } ! 590: bp = buffer; ! 591: while (bp < bufend) { ! 592: bp += internalize_Filing4_StreamOfAttributeSequence(&attrs,bp); ! 593: if (0 == (int) attrs.designator) { ! 594: for (i=0; i < attrs.nextSegment_case.segment.length; i++) { ! 595: (*ProcEachSeq)( ! 596: attrs.nextSegment_case.segment.sequence[i]); ! 597: } ! 598: free(attrs.nextSegment_case.segment.sequence); ! 599: } else { ! 600: for (i = 0; i < attrs.lastSegment_case.length; i++) { ! 601: (*ProcEachSeq)( ! 602: attrs.lastSegment_case.sequence[i]); ! 603: } ! 604: free(attrs.lastSegment_case.sequence); ! 605: return; ! 606: } ! 607: } ! 608: } ! 609: ! 610: storeproc(conn) ! 611: CourierConnection *conn; ! 612: { ! 613: int count, ocount, ch; ! 614: char buffer[SPPMAXDATA]; ! 615: char *bp; ! 616: ! 617: errno = ocount = 0; ! 618: clearerr(fin); ! 619: ! 620: while ( ((count= fread(buffer, sizeof(char), SPPMAXDATA, fin)) != 0) ! 621: && (ocount= BDTwrite (conn, buffer, count)) > 0) { ! 622: ; ! 623: } ! 624: if ( count < 0 ) { ! 625: BDTabort(conn); ! 626: perror("netout"); ! 627: exit(1); ! 628: } else if ( ferror(fin) ) { ! 629: BDTabort(conn); ! 630: perror("fread"); ! 631: exit(1); ! 632: } else { ! 633: BDTclosewrite(conn); ! 634: } ! 635: } ! 636:
This archive runs on limited infrastructure. Preserving old code on modern bandwidth. Automated agents are requested to crawl responsibly.