_DISTRIBUTED COMPUTING AND OSF/DCE_ by John Bloomer Listing One #include #include #include #include "im.h" #define USAGE() { fprintf(stderr, "Usage: %s ", argv[0]); \ fprintf(stderr, "\t-a imageName \"comments\" width height depth compressType"); \ fprintf(stderr, "\n\t\t\t\t\tadd an image from file 'imageName'\n"); \ fprintf(stderr, "\t\t-d imageName\t\tdelete an image\n"); \ fprintf(stderr, "\t\t-x imageName\t\textract an image to file 'imageName'\n"); \ fprintf(stderr, "\t\t-l\t\t\tlist contents of archive\n"); \ exit(1); } #define PRINTHEAD(pI) { \ printf("name:\t%s\n\towner: %s\n\tcomments: %s\n\tdate: %s\n", \ pI->sN, pI->sO, pI->sC, pI->sD); \ printf("\tbytes: %d\twidth: %d\theight: %d\tdepth: %d\tcompress: %d\n", \ pI->b, pI->x, pI->y, pI->d, pI->c); } image *readImage(); FILE *fp; main(argc, argv) int argc; char *argv[]; { pStr expectEmpty; /* a NULL if success, else an error string */ imageList *pIL; image *pI; pStr sImageName; int arg; /* Parse the command line, doing local procedure calls as requested. */ if (argc < 2) { USAGE(); exit(); } for (arg = 1; arg < argc; arg++) { if (argv[arg][0] != '-') USAGE(); switch (argv[arg][1]) { case 't': arg++; break; case 'a': if ((argc - (++arg) < 6) || !(pI = readImage(argv, &arg))) USAGE(); expectEmpty = add(pI); if (expectEmpty[0] != '\0') fprintf(stderr, "local call failed: %s", expectEmpty); break; case 'd': if (argc - (++arg) < 1) USAGE(); sImageName = (pStr) strdup(argv[arg]); expectEmpty = delete(sImageName); if (expectEmpty[0] != '\0') fprintf(stderr, "local call failed: %s", expectEmpty); break; case 'x': if (argc - (++arg) < 1) USAGE(); sImageName = (pStr) strdup(argv[arg]); expectEmpty = extract(sImageName, &pI); if (expectEmpty[0] != '\0') fprintf(stderr, "local call failed: %s", expectEmpty); else (void) writeImage(pI, sImageName); break; case 'l':{ if (!(pIL = list())) fprintf(stderr, "local call failed:"); else for (pI = pIL->pImage; pIL->pNext; pIL = pIL->pNext, pI = pIL->pImage) PRINTHEAD(pI); break; } default: USAGE(); } } } image * readImage(argv, pArg) char **argv; int *pArg; { static image im; char buffer[MAXBUF]; char null = '\0'; u_int reallyRead; u_int imageSize = 0; /* Build the header information then look at stdin for data. */ im.sN = (pStr) strdup(argv[*pArg]); im.sO = UIDTONAME(getuid()); im.sC = (pStr) strdup(argv[++*pArg]); im.x = atoi(argv[++*pArg]); im.y = atoi(argv[++*pArg]); im.d = atoi(argv[++*pArg]); im.c = atoi(argv[++*pArg]); im.sD = &null; /* don't forget to terminate those empty strings! */ im.data = (char *) malloc(0); if (!(fp = fopen(im.sN, "r"))) { fprintf(stderr, "error opening imageName \"%s\" for reading\n", im.sN); return (0); } while (reallyRead = fread(buffer, 1, MAXBUF, fp)) { im.data = (char *) realloc(im.data, imageSize + reallyRead); (void) bcopy(buffer, im.data + imageSize, reallyRead); imageSize += reallyRead; } im.b = imageSize; fclose(fp); return (&im); } writeImage(pImage, sImageName) image *pImage; pStr sImageName; { if (!(fp = fopen(sImageName, "w"))) { fprintf(stderr, "error opening imageName \"%s\" for writing\n", sImageName); return (1); } PRINTHEAD(pImage); if (fwrite(pImage->data, 1, pImage->b, fp) != pImage->b) { fprintf(stderr, "error writing imageName \"%s\" data\n", sImageName); fclose(fp); return (1); } fclose(fp); return (0); } Listing Two /* rim_client.c - client application for remote image database service */ #include #include #include #include #include #include #include "rim.h" #include "rim_util.h" #define USAGE() { fprintf(stderr, "commands:\n"); \ fprintf(stderr, "\ta imageName \"comments\" width height depth compressType"); \ fprintf(stderr, "\n\t\t\t\t\tadd an image from file 'imageName'\n"); \ fprintf(stderr, "\td imageName\t\tdelete an image\n"); \ fprintf(stderr, "\tx imageName\t\textract an image to file 'imageName'\n"); \ fprintf(stderr, "\tl\t\t\tlist contents of archive\n"); \ fprintf(stderr, "\tq\t\t\tquits\n"); } #define PRINTHEAD(pI) { \ printf("name:\t%s\n\towner: %s\n\tcomments: %s\n\tdate: %s\n", \ pI->sN, pI->sO, pI->sC, pI->sD); \ printf("\tbytes: %d\twidth: %d\theight: %d\tdepth: %d\tcompress: %d\n", \ pI->b, pI->x, pI->y, pI->d, pI->c); } typedef struct work_arg { pthread_t *thread_id; int server_num; char *server_name; rpc_binding_handle_t bind_handle; image *pImage; } work_arg_t; image *readImage(); FILE *fp; #define MAX_SERVERS 100 pthread_mutex_t WorkMutex; pthread_cond_t WorkCond; /* The single-arg wrapper routine around the list() RPC accessed by each * thread we ask to list - must be reentrant */ void list_wrapper(work_arg_t * work_arg_p) { imageList *pIL; image *pI; if (!(pIL = list(work_arg_p->bind_handle))) { fprintf(stderr, "remote call failed:"); pthread_exit((pthread_addr_t *)1); } else { for (pI = pIL->pImage; pIL->pNext; pIL = pIL->pNext, pI = pIL->pImage) PRINTHEAD(pI); iLFreeOne(pIL); } pthread_exit((pthread_addr_t *)0); } /* the wrapper around the add() RPC */ void add_wrapper(work_arg_t * work_arg_p) { pStr expectEmpty; /* a NULL if success, else an error string */ expectEmpty = add(work_arg_p->bind_handle, work_arg_p->pImage); if (expectEmpty[0] != '\0') { fprintf(stderr, "remote call failed: %s", expectEmpty); pthread_exit((pthread_addr_t *)1); } pthread_exit((pthread_addr_t *)0); } /* the wrapper around the delete() RPC */ void delete_wrapper(work_arg_t * work_arg_p) { pStr expectEmpty; /* a NULL if success, else an error string */ expectEmpty = delete(work_arg_p->bind_handle, work_arg_p->pImage->sN); if (expectEmpty[0] != '\0') { fprintf(stderr, "remote call failed: %s", expectEmpty); pthread_exit((pthread_addr_t *)1); } pthread_exit((pthread_addr_t *)0); } /* the wrapper around the extract() RPC */ void extract_wrapper(work_arg_t * work_arg_p) { image *pI; pStr expectEmpty; /* a NULL if success, else an error string */ expectEmpty = extract(work_arg_p->bind_handle, work_arg_p->pImage->sN, &pI); if (expectEmpty[0] != '\0') { fprintf(stderr, "remote call failed: %s", expectEmpty); pthread_exit((pthread_addr_t *)1); } else { (void) writeImage(pI, pI->sN); iFreeOne(pI); } pthread_exit((pthread_addr_t *)0); } main(argc, argv) int argc; char *argv[]; { int server_num, nservers; work_arg_t work_arg[MAX_SERVERS]; char *server_name[MAX_SERVERS]; rpc_binding_handle_t *binding; /* Check usage and initialize. */ if (argc < 2 || (nservers = argc - 1) > MAX_SERVERS) { fprintf(stderr, "Usage: %s server_name ...(up to %d server_name's)...\n", argv[0], MAX_SERVERS); exit(1); } for (server_num = 0; server_num < nservers; server_num += 1) { server_name[server_num] = (char *) argv[1 + server_num]; /* Import binding info from namespace and annotate handles for security. */ binding = importAuthBinding(rim_v1_0_c_ifspec, SERVER_PRINC_NAME, server_name[server_num], '\0', 1, rpc_c_protect_level_pkt_integ, rpc_c_authn_dce_secret, '\0', rpc_c_authz_name); } /* Initialize mutex and condition variable. */ printf("Client calling pthread_mutex_init...\n"); if (pthread_mutex_init(&WorkMutex, pthread_mutexattr_default) == -1) { dce_err(__FILE__, "pthread_mutex_init", (unsigned long) -1); exit(1); } printf("Client calling pthread_cond_init...\n"); if (pthread_cond_init(&WorkCond, pthread_condattr_default) == -1) { dce_err(__FILE__, "pthread_cond_init", (unsigned long) -1); exit(1); } /* Initialize work args that are constant throughout main loop. */ for (server_num = 0; server_num < nservers; server_num += 1) { work_arg[server_num].server_num = server_num; work_arg[server_num].server_name = server_name[server_num]; work_arg[server_num].bind_handle = binding[server_num]; work_arg[server_num].pImage = (image *) malloc(sizeof(image)); work_arg[server_num].thread_id = (pthread_t *) '\0'; } /* Transaction loop -- exits with a 'q' and reaps threads. */ while (1) { /* Per-loop initialization. We're single-threaded here, so locks and * reentrant code is unnecessary. For each server... */ char line[256]; char args[7][256]; int argc, argcc; void *local; /* scrape up to 7 args from the command line */ gets(line); argc = sscanf(line, "%s%s%s%s%s%s%s", args[0], args[1], args[2], args[3], args[4], args[5], args[6]); server_num = (server_num + 1) % nservers; /* NEXT! */ local = (void *)'\0'; switch (tolower(args[0][0])) { case 'a': argcc = 1; if ((argc != 7) || (!(work_arg[server_num].pImage = readImage(args, &argcc)))) USAGE() else local = &add_wrapper; break; case 'd': if (argc != 2) USAGE() else { work_arg[server_num].pImage->sN = (pStr) strdup(args[1]); local = &delete_wrapper; } break; case 'x': if (argc != 2) USAGE() else { work_arg[server_num].pImage->sN = (pStr) strdup(args[1]); local = &extract_wrapper; } break; case 'l': local = &list_wrapper; break; case 'q': /* If we ever started a thread for a server, wait for it to die if not already dead, print exit status. Note they have not been detached yet so we have status available */ for(server_num=0; server_numdata, 1, pImage->b, fp) != pImage->b) { fprintf(stderr, "error writing imageName \"%s\" data\n", sImageName); fclose(fp); return (1); } fclose(fp); return (0); } /* The next four routines are just image linked-list maint. stuff. */ image * iAllocOne() { /* allocate one image structure */ image *pI = (image *) calloc(sizeof(image), 1); pI->sN = (pStr) calloc(MAXSTR, 1); pI->sO = (pStr) calloc(MAXSTR, 1); pI->sC = (pStr) calloc(MAXSTR, 1); pI->sD = (pStr) calloc(MAXSTR, 1); return (pI); } imageList * iLAllocOne() { /* allocate one imageList structure */ imageList *pIL = (imageList *) malloc(sizeof(imageList)); pIL->pImage = iAllocOne(); pIL->pNext = '\0'; return (pIL); } iFreeOne(pI) image *pI; { cfree(pI->sN); cfree(pI->sO); cfree(pI->sC); cfree(pI->sD); cfree(pI); } iLFreeOne(pIL) imageList *pIL; { imageList *pil; imageList *pil_prev = '\0'; while (pIL) { for (pil = pIL; (pil->pNext) != '\0'; pil_prev = pil, pil = pil->pNext); iFreeOne(pil->pImage); cfree(pil); if (pil_prev) { pil_prev->pNext = '\0'; } if (pil == pIL) break; } } Listing Three /* rim_server.c - server intitialization and procedures for remote * image database service */ #include #include #include #include "rim.h" #include "rim_util.h" #define FGETS(ptr, max, fp) { fgets(ptr, max, fp); ptr[strlen(ptr)-1] = '\0'; } #define READHEADER(n, o, c, d) \ { FGETS(n,MAXSTR,fp); FGETS(o,MAXSTR,fp); \ FGETS(c,MAXSTR,fp); FGETS(d,MAXSTR,fp); } FILE *fp; imageList *iLAllocOne(); image *iAllocOne(); /* ref_mon()- reference monitor for rim. It checks generalities, then calls * is_authorized() to check specifics. */ int ref_mon(bind_handle) rpc_binding_handle_t bind_handle; { int ret; rpc_authz_handle_t privs; unsigned_char_t *client_princ_name, *server_princ_name; unsigned32 protect_level, authn_svc, authz_svc, status; /* Get client auth info. */ rpc_binding_inq_auth_client(bind_handle, &privs, &server_princ_name, &protect_level, &authn_svc, &authz_svc, &status); if (status != rpc_s_ok) { dce_err(__FILE__, "rpc_binding_inq_auth_client", status); return (0); } /* Check if selected authn service is acceptable to us. */ if (authn_svc != rpc_c_authn_dce_secret) { dce_err(__FILE__, "authn_svc check", (unsigned long) -1); return (0); } /* Check if selected protection level is acceptable to us. */ if (protect_level != rpc_c_protect_level_pkt_integ && protect_level != rpc_c_protect_level_pkt_privacy) { dce_err(__FILE__, "protect_level check", (unsigned long) -1); return (0); } /* Check if selected authz service is acceptable to us. */ if (authz_svc != rpc_c_authz_name) { dce_err(__FILE__, "authz_svc check", (unsigned long) -1); return (0); } /* If rpc_c_authz_dce were being used instead of rpc_c_authz_name, privs * would be a PAC (sec_id_pac_t *), not a name as it is here. */ client_princ_name = (unsigned_char_t *) privs; /* Check if selected server principal name is supported. */ if (strcmp(strrchr(server_princ_name, '/'), strrchr(SERVER_PRINC_NAME, '/')) != 0) { dce_err(__FILE__, "server_princ_name check", (unsigned long) -1); return (0); } /* Now that things seem generally OK, check the specifics. */ if (!is_authorized(client_princ_name)) { dce_err(__FILE__, "is_authorized", (unsigned long) -1); return (0); } /* Cleared all the authorization hurdles -- grant access. */ return (1); } /* is_authorized() - check authorization of client for this service. We could * check on a per-procedure basis, rather than once for the interface, to give * more control over access. Typically, an application (i.e., one using PACs & * ACLs) would be using sec_acl_mgr_is_authorized(). */ int is_authorized(client_princ_name) unsigned_char_t *client_princ_name; { /* Check if we want to let this client do this operation. A list or ACL would be better */ if (strcmp(strrchr(client_princ_name, '/'), strrchr(CLIENT_PRINC_NAME, '/')) == 0) { /* OK, we'll let this access happen. */ return (1); } return (0); } void die(rpc_binding_handle_t bind_handle) { printf("server answering the call...\n"); /* should de-register enpoints and directory info */ exit(0); } void restart(rpc_binding_handle_t bind_handle) { /* should de-register enpoints and directory info */ (void) execl(SERVERPATH, (char *) 0); } pStr add(rpc_binding_handle_t bind_handle, image *argp) { static pStr result; static idl_char msg[MAXSTR]; static char N[MAXSTR], O[MAXSTR], C[MAXSTR], D[MAXSTR]; char head[MAXSTR]; int fstat, b, x, y, d, c; time_t tloc; result = msg; msg[0] = '\0'; printf("server answering the call...\n"); if (!(fp = fopen(SERVERDB, "r"))) { sprintf(msg, "cannot open server database %s for reading\n", SERVERDB); return (result); } /* First make sure such an image isn't already archived. */ while ((fstat = fscanf(fp, "%d%d%d%d%d\n", &b, &x, &y, &d, &c)) == 5) { READHEADER(N, O, C, D); if (!strcmp(N, argp->sN)) break; fseek(fp, (long) b, 1); } switch (fstat) { case EOF: /* not found - that's good */ fclose(fp); if (!(fp = fopen(SERVERDB, "a"))) { sprintf(msg, "cannot open server database %s to append\n", SERVERDB); fclose(fp); return (result); } break; case 5: /* there already is one! */ sprintf(msg, "%s archive already has a \"%s\"\n", SERVERDB, argp->sN); fclose(fp); return (result); default: /* not a clean tail... tell user and try */ repairDB(msg); /* to recover */ fclose(fp); return (result); } CompressImage(1, argp); /* compress as specified */ /* Get the date, add the image header and data, then return. */ time(&tloc); sprintf(head, "%d %d %d %d %d\n%s\n%s\n%s\n%s", argp->b, argp->x, argp->y, argp->d, argp->c, argp->sN, argp->sO, argp->sC, (char *) ctime(&tloc)); if ((fwrite(head, 1, strlen(head), fp) != strlen(head)) || (fwrite(argp->data, 1, argp->b, fp) != argp->b)) sprintf(msg, "failed write to server database %s\n", SERVERDB); fclose(fp); return (result); } /* This is included for the sake of completeness but is brute-force. */ pStr delete(rpc_binding_handle_t bind_handle, pStr argp) { FILE *fpp; int fstat; static pStr result; static idl_char msg[MAXSTR]; char N[MAXSTR], O[MAXSTR], C[MAXSTR], D[MAXSTR]; char *buffer; int bufSize, bytesRead, b, x, y, d, c; int seekPt = 0; printf("server answering the call...\n"); if (!ref_mon(bind_handle)) { /* a simple monitor */ dce_err(__FILE__, "ref_mon - not allowed to delete", (unsigned long) -1); return; } msg[0] = '\0'; result = msg; if (!(fp = fopen(SERVERDB, "r"))) { sprintf(msg, "cannot open server database %s for reading\n", SERVERDB); return (result); } /* Look thru the DB for the named image. */ while ((fstat = fscanf(fp, "%d%d%d%d%d\n", &b, &x, &y, &d, &c)) == 5) { READHEADER(N, O, C, D); fseek(fp, (long) b, 1); /* fp stops at next entry */ if (!strcmp(N, argp)) break; seekPt = ftell(fp); } switch (fstat) { case EOF: /* not found */ sprintf(msg, "%s not found in archive\n", argp); break; case 5: /* This is the one! Remove it by copying the bottom up. */ bufSize = MIN(MAX(1, b), MAXBUF); buffer = (char *) malloc(bufSize); fpp = fopen(SERVERDB, "r+"); fseek(fpp, seekPt, 0); /* fpp is at selected image */ while (!feof(fp)) { bytesRead = fread(buffer, 1, bufSize, fp); fwrite(buffer, 1, bytesRead, fpp); } seekPt = ftell(fpp); fclose(fpp); truncate(SERVERDB, (off_t) seekPt); break; default: /* not a clean tail... */ repairDB(msg); } fclose(fp); return (result); } static image *pIm = '\0'; /* keep this around as we are interative now */ pStr extract(rpc_binding_handle_t bind_handle, pStr argp, image **ppIm) { int fstat; static pStr result; static idl_char msg[MAXSTR]; printf("server answering the call...\n"); result = msg; msg[0] = '\0'; if (!(fp = fopen(SERVERDB, "r"))) { sprintf(msg, "cannot open server database %s for reading\n", SERVERDB); return (result); } /* Free previously allocated memory. Look thru the DB for the named image. */ if (pIm != '\0') free(pIm); pIm = *ppIm = iAllocOne(); while ((fstat = fscanf(fp, "%d%d%d%d%d\n", &(pIm->b), &(pIm->x), &(pIm->y), &(pIm->d), &(pIm->c))) == 5) { READHEADER(pIm->sN, pIm->sO, pIm->sC, pIm->sD); if (!strcmp(pIm->sN, argp)) break; fseek(fp, (long) pIm->b, 1); } switch (fstat) { case EOF: /* not found */ sprintf(msg, "%s not found in archive\n", argp); break; case 5: /* this is the one! */ pIm->data = (idl_char *) malloc(pIm->b); if (fread(pIm->data, 1, pIm->b, fp) != pIm->b) { sprintf(msg, "couldn't read all of %s\n", argp); repairDB(msg); } break; default: /* not a clean tail... */ repairDB(msg); } fclose(fp); return (result); } static imageList *pIList = '\0';/* keep this around as we are interative now */ imageList * list(rpc_binding_handle_t bind_handle) { /* inconsistent - should return a string, but there's a reason... */ imageList *pIL; int fstat; printf("server answering the call...\n"); /* Free previously allocated memory. Build a list. */ if (pIList) iLFreeOne(pIList); pIL = pIList = iLAllocOne(); if (!(fp = fopen(SERVERDB, "r"))) { sprintf(pIL->pImage->sN, "cannot open server database %s for reading\n", SERVERDB); pIL->pNext = iLAllocOne(); /* needs a dangler...:-( */ return (pIList); } while ((fstat = fscanf(fp, "%d%d%d%d%d\n", &(pIL->pImage->b), &(pIL->pImage->x), &(pIL->pImage->y), &(pIL->pImage->d), &(pIL->pImage->c))) == 5) { READHEADER(pIL->pImage->sN, pIL->pImage->sO, pIL->pImage->sC, pIL->pImage->sD); fseek(fp, (long) pIL->pImage->b, 1); pIL->pNext = iLAllocOne(); /* hang an empty one on the end */ pIL = pIL->pNext; } if (fstat != EOF) { /* not a clean tail... */ repairDB(pIL->pImage->sN); } fclose(fp); return (pIList); } /* The next four routines are just image linked-list maint. stuff. */ imageList * iLAllocOne() { /* allocate one imageList structure */ imageList *pIL = (imageList *) malloc(sizeof(imageList)); pIL->pImage = iAllocOne(); pIL->pNext = '\0'; return (pIL); } image * iAllocOne() { /* allocate one image structure */ image *pI = (image *) calloc(sizeof(image), 1); pI->sN = (pStr) calloc(MAXSTR, 1); pI->sO = (pStr) calloc(MAXSTR, 1); pI->sC = (pStr) calloc(MAXSTR, 1); pI->sD = (pStr) calloc(MAXSTR, 1); return (pI); } iLFreeOne(pIL) imageList *pIL; { imageList *pil; imageList *pil_prev = '\0'; while (pIL) { for (pil = pIL; (pil->pNext) != '\0'; pil_prev = pil, pil = pil->pNext); iFreeOne(pil->pImage); cfree(pil); if (pil_prev) { pil_prev->pNext = '\0'; } if (pil == pIL) break; } } iFreeOne(pI) image *pI; { cfree(pI->sN); cfree(pI->sO); cfree(pI->sC); cfree(pI->sD); cfree(pI); } repairDB(s) /* doesn't do much, yet... */ pStr s; { sprintf(s, "server database %s data hosed, repaired\n", SERVERDB); } CompressImage(d, pIm) /* compression and decompression */ int d; image *pIm; { /* omitted */ } /******** server initialization starts here *********/ #ifndef LOCAL /* go LOCAL if you want to link with rim_client.c */ #include #define MAX_CONC_CALLS_PROTSEQ 5 /* max conc calls per protseq */ #define MAX_CONC_CALLS_TOTAL 10 /* max conc calls total */ /* definition, generated by IDL, are all that is necessariliy unique below */ #define SERVER_IF rim_v1_0_s_ifspec char *server_name; /* main() Get started; set up server how we want it, and call listen loop. */ int main(argc, argv) int argc; char *argv[]; { rpc_binding_vector_t *bind_vector_p; unsigned32 status; int i; /* Check usage and initialize. */ if (argc != 2) { fprintf(stderr, "Usage: %s namespace_server_name\n", argv[0]); exit(1); } server_name = argv[1]; /* Register interface with rpc runtime - no type_uuid/epv associations */ rpc_server_register_if(SERVER_IF, '\0', '\0', &status); if (status != rpc_s_ok) { dce_err(__FILE__, "rpc_server_register_if", status); exit(1); } /* Tell rpc runtime we want to use all supported protocol sequences. */ rpc_server_use_all_protseqs(MAX_CONC_CALLS_PROTSEQ, &status); if (status != rpc_s_ok) { dce_err(__FILE__, "rpc_server_use_all_protseqs", status); exit(1); } /* Ask the runtime which binding handle(s) it's going to let us use. */ rpc_server_inq_bindings(&bind_vector_p, &status); if (status != rpc_s_ok) { dce_err(__FILE__, "rpc_server_inq_bindings", status); exit(1); } /* Register authentication info with rpc runtime. */ rpc_server_register_auth_info(SERVER_PRINC_NAME, rpc_c_authn_dce_secret, '\0', KEYTABFILE, &status); if (status != rpc_s_ok) { dce_err(__FILE__, "rpc_server_register_auth_info", status); exit(1); } /* Register binding info with endpoint mapper. No object UUID vector */ rpc_ep_register(SERVER_IF, bind_vector_p, '\0', (unsigned_char_t *) "rim explicit secure server, version 1.0", &status); if (status != rpc_s_ok) { dce_err(__FILE__, "rpc_ep_register", status); exit(1); } /* Export binding info to the namespace. */ rpc_ns_binding_export(rpc_c_ns_syntax_dce, server_name, SERVER_IF, bind_vector_p, '\0', &status); if (status != rpc_s_ok) { dce_err(__FILE__, "rpc_ns_binding_export", status); exit(1); } /* Listen for service requests. */ fprintf(stdout, "server %s ready.\n", server_name); rpc_server_listen(MAX_CONC_CALLS_TOTAL, &status); if (status != rpc_s_ok) { dce_err(__FILE__, "rpc_server_listen", status); exit(1); } /* Not reached. */ } #endif Example 1: (a) bucks = getAcctBalance(acctName); (b) bucks = getAcctBalance(acctName, someRemoteBank);