/* * ======================================================================== * Copyright 2013-2022 Eduardo Chappa * Copyright 2006-2007 University of Washington * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * ======================================================================== */ #include "../pith/headers.h" /* for os-dep and pith defs/includes */ #include "../pith/abdlc.h" #include "../pith/state.h" #include "../pith/conf.h" #include "../pith/status.h" #include "../pith/ldap.h" #include "../pith/tempfile.h" /* * Internal prototypes */ void initialize_dlc_cache(void); int dlc_siblings(DL_CACHE_S *, DL_CACHE_S *); DL_CACHE_S *dlc_mgr(long, DlMgrOps, DL_CACHE_S *); void free_cache_array(DL_CACHE_S **, int); DL_CACHE_S *dlc_prev(DL_CACHE_S *, DL_CACHE_S *); DL_CACHE_S *dlc_next(DL_CACHE_S *, DL_CACHE_S *); DL_CACHE_S *get_global_top_dlc(DL_CACHE_S *); DL_CACHE_S *get_global_bottom_dlc(DL_CACHE_S *); DL_CACHE_S *get_top_dl_of_adrbk(int, DL_CACHE_S *); DL_CACHE_S *get_bottom_dl_of_adrbk(int, DL_CACHE_S *); #define NO_PERMISSION _("[ Permission Denied ]") /* TRANSLATORS: This is a heading referring to something that is readable but not writeable. */ #define READONLY _(" (ReadOnly)") /* TRANSLATORS: Not readable */ #define NOACCESS _(" (Un-readable)") /* TRANSLATORS: Directories, as in LDAP Directories. A heading. */ #define OLDSTYLE_DIR_TITLE _("Directories") /* data for the display list cache */ static DL_CACHE_S *cache_array = (DL_CACHE_S *)NULL; static long valid_low, valid_high; static int index_of_low, size_of_cache, n_cached; void initialize_dlc_cache(void) { dprint((11, "- initialize_dlc_cache -\n")); (void)dlc_mgr(NO_LINE, Initialize, (DL_CACHE_S *)NULL); } void done_with_dlc_cache(void) { dprint((11, "- done_with_dlc_cache -\n")); (void)dlc_mgr(NO_LINE, DoneWithCache, (DL_CACHE_S *)NULL); } /* * Returns 1 if the dlc's are related to each other, 0 otherwise. * * The idea is that if you are going to flush one of these dlcs from the * cache you should also flush its partners. For example, if you flush one * Listent from a list you should flush the entire entry including all the * Listents and the ListHead. If you flush a DlcTitle, you should also * flush the SubTitle and the TitleBlankTop. */ int dlc_siblings(DL_CACHE_S *dlc1, DL_CACHE_S *dlc2) { if(!dlc1 || !dlc2 || dlc1->adrbk_num != dlc2->adrbk_num) return 0; switch(dlc1->type){ case DlcSimple: case DlcListHead: case DlcListBlankTop: case DlcListBlankBottom: case DlcListEnt: case DlcListClickHere: case DlcListEmpty: switch(dlc2->type){ case DlcSimple: case DlcListHead: case DlcListBlankTop: case DlcListBlankBottom: case DlcListEnt: case DlcListClickHere: case DlcListEmpty: return(dlc1->dlcelnum == dlc2->dlcelnum); default: return 0; } break; case DlcDirDelim1: case DlcDirDelim2: switch(dlc2->type){ case DlcDirDelim1: case DlcDirDelim2: return 1; default: return 0; } break; case DlcGlobDelim1: case DlcGlobDelim2: switch(dlc2->type){ case DlcGlobDelim1: case DlcGlobDelim2: return 1; default: return 0; } break; case DlcTitle: case DlcTitleNoPerm: case DlcSubTitle: case DlcTitleBlankTop: switch(dlc2->type){ case DlcTitle: case DlcTitleNoPerm: case DlcSubTitle: case DlcTitleBlankTop: return 1; default: return 0; } break; case DlcDirAccess: case DlcDirSubTitle: case DlcDirBlankTop: switch(dlc2->type){ case DlcDirAccess: case DlcDirSubTitle: case DlcDirBlankTop: return 1; default: return 0; } break; case DlcTitleDashTopCmb: case DlcTitleCmb: case DlcTitleDashBottomCmb: case DlcTitleBlankBottomCmb: case DlcClickHereCmb: case DlcTitleBlankTopCmb: switch(dlc2->type){ case DlcTitleDashTopCmb: case DlcTitleCmb: case DlcTitleDashBottomCmb: case DlcTitleBlankBottomCmb: case DlcClickHereCmb: case DlcTitleBlankTopCmb: return 1; default: return 0; } break; default: return 0; } /*NOTREACHED*/ } /* * Manage the display list cache. * * The cache is a circular array of DL_CACHE_S elements. It always * contains a contiguous set of display lines. * The lowest numbered line in the cache is * valid_low, and the highest is valid_high. Everything in between is * also valid. Index_of_low is where to look * for the valid_low element in the circular array. * * We make calls to dlc_prev and dlc_next to get new entries for the cache. * We need a starting value before we can do that. * * This returns a pointer to a dlc for the desired row. If you want the * actual display list line you call dlist(row) instead of dlc_mgr. */ DL_CACHE_S * dlc_mgr(long int row, DlMgrOps op, DL_CACHE_S *dlc_start) { int new_index, known_index, next_index = 0; DL_CACHE_S *dlc = (DL_CACHE_S *)NULL; long next_row = 0L; long prev_row = 0L; if(op == Lookup){ if(row >= valid_low && row <= valid_high){ /* already cached */ new_index = ((row - valid_low) + index_of_low) % size_of_cache; dlc = &cache_array[new_index]; } else if(row > valid_high){ /* row is past where we've looked */ known_index = ((valid_high - valid_low) + index_of_low) % size_of_cache; next_row = valid_high + 1L; /* we'll usually be looking for row = valid_high + 1 */ while(next_row <= row){ new_index = (known_index + 1) % size_of_cache; dlc = dlc_next(&cache_array[known_index], &cache_array[new_index]); /* * This means somebody changed the file out from underneath * us. This would happen if dlc_next needed to ask for an * abe to figure out what the type of the next row is, but * adrbk_get_ae returned a NULL. I don't think that can * happen, but if it does... */ if(dlc->type == DlcNotSet){ dprint((1, "dlc_next returned DlcNotSet\n")); goto panic_abook_corrupt; } if(n_cached == size_of_cache){ /* replaced low cache entry */ valid_low++; index_of_low = (index_of_low + 1) % size_of_cache; } else n_cached++; valid_high++; next_row++; known_index = new_index; /* for next time through loop */ } } else if(row < valid_low){ /* row is back up the screen */ known_index = index_of_low; prev_row = valid_low - 1L; while(prev_row >= row){ new_index = (known_index - 1 + size_of_cache) % size_of_cache; dlc = dlc_prev(&cache_array[known_index], &cache_array[new_index]); if(dlc->type == DlcNotSet){ dprint((1, "dlc_prev returned DlcNotSet (1)\n")); goto panic_abook_corrupt; } if(n_cached == size_of_cache) /* replaced high cache entry */ valid_high--; else n_cached++; valid_low--; index_of_low = (index_of_low - 1 + size_of_cache) % size_of_cache; prev_row--; known_index = new_index; } } } else if(op == Initialize){ n_cached = 0; if(!cache_array || size_of_cache != 3 * MAX(as.l_p_page,1)){ if(cache_array) free_cache_array(&cache_array, size_of_cache); size_of_cache = 3 * MAX(as.l_p_page,1); cache_array = (DL_CACHE_S *)fs_get(size_of_cache * sizeof(DL_CACHE_S)); memset((void *)cache_array, 0, size_of_cache * sizeof(DL_CACHE_S)); } /* this will return NULL below and the caller should ignore that */ } /* * Flush all rows for a particular addrbook entry from the cache, but * keep the cache alive and anchored in the same place. The particular * entry is the one that dlc_start is one of the rows of. */ else if(op == FlushDlcFromCache){ long low_entry; next_row = dlc_start->global_row - 1; for(; next_row >= valid_low; next_row--){ next_index = ((next_row - valid_low) + index_of_low) % size_of_cache; if(!dlc_siblings(dlc_start, &cache_array[next_index])) break; } low_entry = next_row + 1L; /* * If low_entry now points one past a ListBlankBottom, delete that, * too, since it may not make sense anymore. */ if(low_entry > valid_low){ next_index = ((low_entry -1L - valid_low) + index_of_low) % size_of_cache; if(cache_array[next_index].type == DlcListBlankBottom) low_entry--; } if(low_entry > valid_low){ /* invalidate everything >= this */ n_cached -= (valid_high - (low_entry - 1L)); valid_high = low_entry - 1L; } else{ /* * This is the tough case. That entry was the first thing cached, * so we need to invalidate the whole cache. However, we also * need to keep at least one thing cached for an anchor, so * we need to get the dlc before this one and it should be a * dlc not related to this same addrbook entry. */ known_index = index_of_low; prev_row = valid_low - 1L; for(;;){ new_index = (known_index - 1 + size_of_cache) % size_of_cache; dlc = dlc_prev(&cache_array[known_index], &cache_array[new_index]); if(dlc->type == DlcNotSet){ dprint((1, "dlc_prev returned DlcNotSet (2)\n")); goto panic_abook_corrupt; } valid_low--; index_of_low = (index_of_low - 1 + size_of_cache) % size_of_cache; if(!dlc_siblings(dlc_start, dlc)) break; known_index = new_index; } n_cached = 1; valid_high = valid_low; } } /* * We have to anchor ourselves at a first element. * Here's how we start at the top. */ else if(op == FirstEntry){ initialize_dlc_cache(); n_cached++; dlc = &cache_array[0]; dlc = get_global_top_dlc(dlc); dlc->global_row = row; index_of_low = 0; valid_low = row; valid_high = row; } /* And here's how we start from the bottom. */ else if(op == LastEntry){ initialize_dlc_cache(); n_cached++; dlc = &cache_array[0]; dlc = get_global_bottom_dlc(dlc); dlc->global_row = row; index_of_low = 0; valid_low = row; valid_high = row; } /* * And here's how we start from an arbitrary position in the middle. * We root the cache at display line row, so it helps if row is close * to where we're going to be starting so that things are easy to find. * The dl that goes with line row is dl_start from addrbook number * adrbk_num_start. */ else if(op == ArbitraryStartingPoint){ AddrScrn_Disp dl; initialize_dlc_cache(); n_cached++; dlc = &cache_array[0]; /* * Save this in case fill_in_dl_field needs to free the text * it points to. */ dl = dlc->dl; *dlc = *dlc_start; dlc->dl = dl; dlc->global_row = row; index_of_low = 0; valid_low = row; valid_high = row; } else if(op == DoneWithCache){ n_cached = 0; if(cache_array) free_cache_array(&cache_array, size_of_cache); } return(dlc); panic_abook_corrupt: q_status_message(SM_ORDER | SM_DING, 5, 10, _("Addrbook changed unexpectedly, re-syncing...")); dprint((1, _("addrbook changed while we had it open?, re-sync\n"))); dprint((2, "valid_low=%ld valid_high=%ld index_of_low=%d size_of_cache=%d\n", valid_low, valid_high, index_of_low, size_of_cache)); dprint((2, "n_cached=%d new_index=%d known_index=%d next_index=%d\n", n_cached, new_index, known_index, next_index)); dprint((2, "next_row=%ld prev_row=%ld row=%ld\n", next_row, prev_row, row)); /* jump back to a safe starting point */ longjmp(addrbook_changed_unexpectedly, 1); /*NOTREACHED*/ } void free_cache_array(DL_CACHE_S **c_array, int size) { DL_CACHE_S *dlc; int i; for(i = 0; i < size; i++){ dlc = &(*c_array)[i]; /* free any allocated space */ switch(dlc->dl.type){ case Text: case Title: case TitleCmb: case AskServer: if(dlc->dl.usst) fs_give((void **)&dlc->dl.usst); break; default: break; } } fs_give((void **)c_array); } /* * Get the dlc element that comes before "old". The function that calls this * function is the one that keeps a cache and checks in the cache before * calling here. New is a passed in pointer to a buffer where we fill in * the answer. */ DL_CACHE_S * dlc_prev(DL_CACHE_S *old, DL_CACHE_S *new) { PerAddrBook *pab; AdrBk_Entry *abe; adrbk_cntr_t list_count; new->adrbk_num = -2; new->dlcelnum = NO_NEXT; new->dlcoffset = NO_NEXT; new->type = DlcNotSet; pab = &as.adrbks[old->adrbk_num]; switch(old->type){ case DlcTitle: case DlcTitleNoPerm: if(old->adrbk_num == 0 && as.config && as.how_many_personals == 0) new->type = DlcGlobDelim2; else if(old->adrbk_num == 0) new->type = DlcOneBeforeBeginning; else if(old->adrbk_num == as.how_many_personals) new->type = DlcGlobDelim2; else new->type = DlcTitleBlankTop; break; case DlcSubTitle: if(pab->access == NoAccess) new->type = DlcTitleNoPerm; else new->type = DlcTitle; break; case DlcTitleBlankTop: new->adrbk_num = old->adrbk_num - 1; new->type = DlcSubTitle; break; case DlcEmpty: case DlcZoomEmpty: case DlcNoPermission: case DlcNoAbooks: if(F_ON(F_CMBND_ABOOK_DISP,ps_global)){ if(as.n_addrbk == 1 && as.n_serv == 0) new->type = DlcOneBeforeBeginning; else new->type = DlcTitleBlankBottomCmb; } else new->type = DlcOneBeforeBeginning; break; case DlcSimple: { adrbk_cntr_t el; long i; i = old->dlcelnum; i--; el = old->dlcelnum - 1; while(i >= 0L){ if(!as.zoomed || entry_is_selected(pab->address_book->selects, (a_c_arg_t)el)) break; el--; i--; } if(i >= 0){ new->dlcelnum = el; abe = adrbk_get_ae(pab->address_book, (a_c_arg_t) new->dlcelnum); if(abe && abe->tag == Single) new->type = DlcSimple; else if(abe && abe->tag == List) new->type = DlcListBlankBottom; } else if(F_ON(F_CMBND_ABOOK_DISP,ps_global)){ if(as.n_addrbk == 1 && as.n_serv == 0) new->type = DlcOneBeforeBeginning; else new->type = DlcTitleBlankBottomCmb; } else new->type = DlcOneBeforeBeginning; } break; case DlcListHead: { adrbk_cntr_t el; long i; i = old->dlcelnum; i--; el = old->dlcelnum - 1; while(i >= 0L){ if(!as.zoomed || entry_is_selected(pab->address_book->selects, (a_c_arg_t)el)) break; el--; i--; } if(i >= 0){ new->dlcelnum = el; abe = adrbk_get_ae(pab->address_book, (a_c_arg_t) new->dlcelnum); if(abe && abe->tag == Single){ new->type = DlcListBlankTop; new->dlcelnum = old->dlcelnum; } else if(abe && abe->tag == List) new->type = DlcListBlankBottom; } else if(F_ON(F_CMBND_ABOOK_DISP,ps_global)){ if(as.n_addrbk == 1 && as.n_serv == 0) new->type = DlcOneBeforeBeginning; else new->type = DlcTitleBlankBottomCmb; } else new->type = DlcOneBeforeBeginning; } break; case DlcListEnt: if(old->dlcoffset > 0){ new->type = DlcListEnt; new->dlcelnum = old->dlcelnum; new->dlcoffset = old->dlcoffset - 1; } else{ new->type = DlcListHead; new->dlcelnum = old->dlcelnum; } break; case DlcListClickHere: case DlcListEmpty: new->type = DlcListHead; new->dlcelnum = old->dlcelnum; break; case DlcListBlankTop: /* can only occur between a Simple and a List */ new->type = DlcSimple; { adrbk_cntr_t el; long i; i = old->dlcelnum; i--; el = old->dlcelnum - 1; while(i >= 0L){ if(!as.zoomed || entry_is_selected(pab->address_book->selects, (a_c_arg_t)el)) break; el--; i--; } if(i >= 0) new->dlcelnum = el; else{ dprint((1, "Bug in addrbook: case ListBlankTop with no selected entry\n")); goto oops; } } break; case DlcListBlankBottom: new->dlcelnum = old->dlcelnum; abe = adrbk_get_ae(pab->address_book, (a_c_arg_t) new->dlcelnum); if(F_ON(F_EXPANDED_DISTLISTS,ps_global) || exp_is_expanded(pab->address_book->exp, (a_c_arg_t)new->dlcelnum)){ list_count = listmem_count_from_abe(abe); if(list_count == 0) new->type = DlcListEmpty; else{ new->type = DlcListEnt; new->dlcoffset = list_count - 1; } } else new->type = DlcListClickHere; break; case DlcGlobDelim1: if(as.how_many_personals == 0) new->type = DlcPersAdd; else{ new->adrbk_num = as.how_many_personals - 1; new->type = DlcSubTitle; } break; case DlcGlobDelim2: new->type = DlcGlobDelim1; break; case DlcPersAdd: new->type = DlcOneBeforeBeginning; break; case DlcGlobAdd: new->type = DlcGlobDelim2; break; case DlcDirAccess: if(old->adrbk_num == 0 && as.n_addrbk == 0) new->type = DlcOneBeforeBeginning; else if(old->adrbk_num == 0) new->type = DlcDirDelim2; else new->type = DlcDirBlankTop; break; case DlcDirSubTitle: new->type = DlcDirAccess; break; case DlcDirBlankTop: new->adrbk_num = old->adrbk_num -1; new->type = DlcDirSubTitle; break; case DlcDirDelim1: new->adrbk_num = as.n_addrbk - 1; if(as.n_addrbk == 0) new->type = DlcNoAbooks; else new = get_bottom_dl_of_adrbk(new->adrbk_num, new); break; case DlcDirDelim1a: new->type = DlcDirDelim1; break; case DlcDirDelim1b: new->type = DlcDirDelim1a; break; case DlcDirDelim1c: new->type = DlcDirDelim1b; break; case DlcDirDelim2: if(F_ON(F_CMBND_ABOOK_DISP,ps_global)) new->type = DlcDirDelim1c; else new->type = DlcDirDelim1; break; case DlcTitleDashTopCmb: if(old->adrbk_num == 0) new->type = DlcOneBeforeBeginning; else new->type = DlcTitleBlankTopCmb; break; case DlcTitleCmb: new->type = DlcTitleDashTopCmb; break; case DlcTitleDashBottomCmb: new->type = DlcTitleCmb; break; case DlcTitleBlankBottomCmb: new->type = DlcTitleDashBottomCmb; break; case DlcClickHereCmb: if(as.n_addrbk == 1 && as.n_serv == 0) new->type = DlcOneBeforeBeginning; else new->type = DlcTitleBlankBottomCmb; break; case DlcTitleBlankTopCmb: new->adrbk_num = old->adrbk_num - 1; new = get_bottom_dl_of_adrbk(new->adrbk_num, new); break; case DlcBeginning: case DlcTwoBeforeBeginning: new->type = DlcBeginning; break; case DlcOneBeforeBeginning: new->type = DlcTwoBeforeBeginning; break; oops: default: q_status_message(SM_ORDER | SM_DING, 5, 10, _("Bug in addrbook, not supposed to happen, re-syncing...")); dprint((1, "Bug in addrbook, impossible case (%d) in dlc_prev, re-sync\n", old->type)); /* jump back to a safe starting point */ longjmp(addrbook_changed_unexpectedly, 1); /*NOTREACHED*/ } new->global_row = old->global_row - 1L; if(new->adrbk_num == -2) new->adrbk_num = old->adrbk_num; return(new); } /* * Get the dlc element that comes after "old". The function that calls this * function is the one that keeps a cache and checks in the cache before * calling here. */ DL_CACHE_S * dlc_next(DL_CACHE_S *old, DL_CACHE_S *new) { PerAddrBook *pab; AdrBk_Entry *abe; adrbk_cntr_t ab_count; adrbk_cntr_t list_count; new->adrbk_num = -2; new->dlcelnum = NO_NEXT; new->dlcoffset = NO_NEXT; new->type = DlcNotSet; pab = &as.adrbks[old->adrbk_num]; switch(old->type){ case DlcTitle: case DlcTitleNoPerm: new->type = DlcSubTitle; break; case DlcSubTitle: if((old->adrbk_num == as.how_many_personals - 1) && (as.config || as.n_addrbk > as.how_many_personals)) new->type = DlcGlobDelim1; else if(as.n_serv && !as.config && (old->adrbk_num == as.n_addrbk - 1)) new->type = DlcDirDelim1; else if(old->adrbk_num == as.n_addrbk - 1) new->type = DlcEnd; else{ new->adrbk_num = old->adrbk_num + 1; new->type = DlcTitleBlankTop; } break; case DlcTitleBlankTop: if(pab->access == NoAccess) new->type = DlcTitleNoPerm; else new->type = DlcTitle; break; case DlcEmpty: case DlcZoomEmpty: case DlcNoPermission: case DlcClickHereCmb: if(F_ON(F_CMBND_ABOOK_DISP,ps_global)){ if(old->adrbk_num < as.n_addrbk - 1){ new->adrbk_num = old->adrbk_num + 1; new->type = DlcTitleBlankTopCmb; } else if(as.n_serv) new->type = DlcDirDelim1; else new->type = DlcEnd; } else new->type = DlcEnd; break; case DlcNoAbooks: case DlcGlobAdd: case DlcEnd: new->type = DlcEnd; break; case DlcSimple: { adrbk_cntr_t el; long i; ab_count = adrbk_count(pab->address_book); i = old->dlcelnum; i++; el = old->dlcelnum + 1; while(i < ab_count){ if(!as.zoomed || entry_is_selected(pab->address_book->selects, (a_c_arg_t)el)) break; el++; i++; } if(i < ab_count){ new->dlcelnum = el; abe = adrbk_get_ae(pab->address_book, (a_c_arg_t) new->dlcelnum); if(abe && abe->tag == Single) new->type = DlcSimple; else if(abe && abe->tag == List) new->type = DlcListBlankTop; } else if(F_ON(F_CMBND_ABOOK_DISP,ps_global)){ if(old->adrbk_num < as.n_addrbk - 1){ new->adrbk_num = old->adrbk_num + 1; new->type = DlcTitleBlankTopCmb; } else if(as.n_serv) new->type = DlcDirDelim1; else new->type = DlcEnd; } else new->type = DlcEnd; } break; case DlcListHead: new->dlcelnum = old->dlcelnum; abe = adrbk_get_ae(pab->address_book, (a_c_arg_t) new->dlcelnum); if(F_ON(F_EXPANDED_DISTLISTS,ps_global) || exp_is_expanded(pab->address_book->exp, (a_c_arg_t)new->dlcelnum)){ list_count = listmem_count_from_abe(abe); if(list_count == 0) new->type = DlcListEmpty; else{ new->type = DlcListEnt; new->dlcoffset = 0; } } else new->type = DlcListClickHere; break; case DlcListEnt: new->dlcelnum = old->dlcelnum; abe = adrbk_get_ae(pab->address_book, (a_c_arg_t) new->dlcelnum); list_count = listmem_count_from_abe(abe); if(old->dlcoffset == list_count - 1){ /* last member of list */ adrbk_cntr_t el; long i; ab_count = adrbk_count(pab->address_book); i = old->dlcelnum; i++; el = old->dlcelnum + 1; while(i < ab_count){ if(!as.zoomed || entry_is_selected(pab->address_book->selects, (a_c_arg_t)el)) break; el++; i++; } if(i < ab_count) new->type = DlcListBlankBottom; else if(F_ON(F_CMBND_ABOOK_DISP,ps_global)){ if(old->adrbk_num < as.n_addrbk - 1){ new->adrbk_num = old->adrbk_num + 1; new->type = DlcTitleBlankTopCmb; } else if(as.n_serv) new->type = DlcDirDelim1; else new->type = DlcEnd; } else new->type = DlcEnd; } else{ new->type = DlcListEnt; new->dlcoffset = old->dlcoffset + 1; } break; case DlcListClickHere: case DlcListEmpty: { adrbk_cntr_t el; long i; new->dlcelnum = old->dlcelnum; ab_count = adrbk_count(pab->address_book); i = old->dlcelnum; i++; el = old->dlcelnum + 1; while(i < ab_count){ if(!as.zoomed || entry_is_selected(pab->address_book->selects, (a_c_arg_t)el)) break; el++; i++; } if(i < ab_count) new->type = DlcListBlankBottom; else if(F_ON(F_CMBND_ABOOK_DISP,ps_global)){ if(old->adrbk_num < as.n_addrbk - 1){ new->adrbk_num = old->adrbk_num + 1; new->type = DlcTitleBlankTopCmb; } else if(as.n_serv) new->type = DlcDirDelim1; else new->type = DlcEnd; } else new->type = DlcEnd; } break; case DlcListBlankTop: new->type = DlcListHead; new->dlcelnum = old->dlcelnum; break; case DlcListBlankBottom: { adrbk_cntr_t el; long i; ab_count = adrbk_count(pab->address_book); i = old->dlcelnum; i++; el = old->dlcelnum + 1; while(i < ab_count){ if(!as.zoomed || entry_is_selected(pab->address_book->selects, (a_c_arg_t)el)) break; el++; i++; } if(i < ab_count){ new->dlcelnum = el; abe = adrbk_get_ae(pab->address_book, (a_c_arg_t) new->dlcelnum); if(abe && abe->tag == Single) new->type = DlcSimple; else if(abe && abe->tag == List) new->type = DlcListHead; } else /* can't happen */ new->type = DlcEnd; } break; case DlcGlobDelim1: new->type = DlcGlobDelim2; break; case DlcGlobDelim2: if(as.config && as.how_many_personals == as.n_addrbk) new->type = DlcGlobAdd; else{ new->adrbk_num = as.how_many_personals; pab = &as.adrbks[new->adrbk_num]; if(pab->access == NoAccess) new->type = DlcTitleNoPerm; else new->type = DlcTitle; } break; case DlcDirDelim1: if(F_ON(F_CMBND_ABOOK_DISP,ps_global)) new->type = DlcDirDelim1a; else new->type = DlcDirDelim2; new->adrbk_num = 0; break; case DlcDirDelim1a: new->type = DlcDirDelim1b; break; case DlcDirDelim1b: new->type = DlcDirDelim1c; break; case DlcDirDelim1c: new->type = DlcDirDelim2; new->adrbk_num = 0; break; case DlcDirDelim2: new->type = DlcDirAccess; new->adrbk_num = 0; break; case DlcPersAdd: new->type = DlcGlobDelim1; break; case DlcDirAccess: new->type = DlcDirSubTitle; break; case DlcDirSubTitle: if(old->adrbk_num == as.n_serv - 1) new->type = DlcEnd; else{ new->type = DlcDirBlankTop; new->adrbk_num = old->adrbk_num + 1; } break; case DlcDirBlankTop: new->type = DlcDirAccess; break; case DlcTitleDashTopCmb: new->type = DlcTitleCmb; break; case DlcTitleCmb: new->type = DlcTitleDashBottomCmb; break; case DlcTitleDashBottomCmb: new->type = DlcTitleBlankBottomCmb; break; case DlcTitleBlankBottomCmb: pab = &as.adrbks[old->adrbk_num]; if(pab->ostatus != Open && pab->access != NoAccess) new->type = DlcClickHereCmb; else new = get_top_dl_of_adrbk(old->adrbk_num, new); break; case DlcTitleBlankTopCmb: new->type = DlcTitleDashTopCmb; break; case DlcOneBeforeBeginning: new = get_global_top_dlc(new); break; case DlcTwoBeforeBeginning: new->type = DlcOneBeforeBeginning; break; default: q_status_message(SM_ORDER | SM_DING, 5, 10, _("Bug in addrbook, not supposed to happen, re-syncing...")); dprint((1, "Bug in addrbook, impossible case (%d) in dlc_next, re-sync\n", old->type)); /* jump back to a safe starting point */ longjmp(addrbook_changed_unexpectedly, 1); /*NOTREACHED*/ } new->global_row = old->global_row + 1L; if(new->adrbk_num == -2) new->adrbk_num = old->adrbk_num; return(new); } /* * Get the display line at the very top of whole addrbook screen display. */ DL_CACHE_S * get_global_top_dlc(DL_CACHE_S *new) /* fill in answer here */ { PerAddrBook *pab; new->dlcelnum = NO_NEXT; new->dlcoffset = NO_NEXT; new->type = DlcNotSet; if(F_ON(F_CMBND_ABOOK_DISP,ps_global) && !as.config){ new->adrbk_num = 0; if(as.n_addrbk > 1 || (as.n_serv && as.n_addrbk == 1)) new->type = DlcTitleDashTopCmb; else if(as.n_addrbk == 1) new = get_top_dl_of_adrbk(new->adrbk_num, new); else if(as.n_serv > 0){ /* 1st directory */ new->type = DlcDirAccess; new->adrbk_num = 0; } else new->type = DlcNoAbooks; } else{ new->adrbk_num = 0; if(as.config){ if(as.how_many_personals == 0) /* no personals */ new->type = DlcPersAdd; else{ pab = &as.adrbks[new->adrbk_num]; /* 1st personal */ if(pab->access == NoAccess) new->type = DlcTitleNoPerm; else new->type = DlcTitle; } } else if(any_ab_open()){ new->adrbk_num = as.cur; new = get_top_dl_of_adrbk(new->adrbk_num, new); } else if(as.n_addrbk > 0){ /* 1st addrbook */ pab = &as.adrbks[new->adrbk_num]; if(pab->access == NoAccess) new->type = DlcTitleNoPerm; else new->type = DlcTitle; } else if(as.n_serv > 0){ /* 1st directory */ new->type = DlcDirAccess; new->adrbk_num = 0; } else new->type = DlcNoAbooks; } return(new); } /* * Get the last display line for the whole address book screen. * This gives us a way to start at the end and move back up. */ DL_CACHE_S * get_global_bottom_dlc(DL_CACHE_S *new) /* fill in answer here */ { new->dlcelnum = NO_NEXT; new->dlcoffset = NO_NEXT; new->type = DlcNotSet; if(F_ON(F_CMBND_ABOOK_DISP,ps_global) && !as.config){ if(as.n_serv){ new->type = DlcDirSubTitle; new->adrbk_num = as.n_serv - 1; } else if(as.n_addrbk > 0){ new->adrbk_num = as.n_addrbk - 1; new = get_bottom_dl_of_adrbk(new->adrbk_num, new); } else new->type = DlcNoAbooks; } else{ new->adrbk_num = MAX(as.n_addrbk - 1, 0); if(as.config){ if(as.how_many_personals == as.n_addrbk) /* no globals */ new->type = DlcGlobAdd; else new->type = DlcSubTitle; } else if(any_ab_open()){ new->adrbk_num = as.cur; new = get_bottom_dl_of_adrbk(new->adrbk_num, new); } else if(as.n_serv){ new->type = DlcDirSubTitle; new->adrbk_num = as.n_serv - 1; } else{ /* !config && !opened && !n_serv */ if(as.n_addrbk) new->type = DlcSubTitle; else new->type = DlcNoAbooks; } } return(new); } /* * First dl in a particular addrbook, not counting title lines. * Assumes as.opened. */ DL_CACHE_S * get_top_dl_of_adrbk(int adrbk_num, DL_CACHE_S *new) /* fill in answer here */ { PerAddrBook *pab; AdrBk_Entry *abe; adrbk_cntr_t ab_count; pab = &as.adrbks[adrbk_num]; new->adrbk_num = adrbk_num; if(pab->access == NoAccess) new->type = DlcNoPermission; else{ adrbk_cntr_t el; long i; ab_count = adrbk_count(pab->address_book); i = 0L; el = 0; /* find first displayed entry */ while(i < ab_count){ if(!as.zoomed || entry_is_selected(pab->address_book->selects, (a_c_arg_t)el)) break; el++; i++; } if(i < ab_count){ new->dlcelnum = el; abe = adrbk_get_ae(pab->address_book, (a_c_arg_t) new->dlcelnum); if(abe && abe->tag == Single) new->type = DlcSimple; else if(abe && abe->tag == List) new->type = DlcListHead; } else if(as.zoomed) new->type = DlcZoomEmpty; else new->type = DlcEmpty; } return(new); } /* * Find the last display line for addrbook number adrbk_num. * Assumes as.opened (unless OLD_ABOOK_DISP). */ DL_CACHE_S * get_bottom_dl_of_adrbk(int adrbk_num, DL_CACHE_S *new) /* fill in answer here */ { PerAddrBook *pab; AdrBk_Entry *abe; adrbk_cntr_t ab_count; adrbk_cntr_t list_count; pab = &as.adrbks[adrbk_num]; new->adrbk_num = adrbk_num; if(F_ON(F_CMBND_ABOOK_DISP,ps_global) && pab->ostatus != Open){ if(pab->access == NoAccess) new->type = DlcNoPermission; else new->type = DlcClickHereCmb; } else if(F_OFF(F_CMBND_ABOOK_DISP,ps_global) && pab->ostatus != Open){ new->type = DlcSubTitle; } else{ if(pab->access == NoAccess) new->type = DlcNoPermission; else{ adrbk_cntr_t el; long i; ab_count = adrbk_count(pab->address_book); i = ab_count - 1; el = ab_count - 1; /* find last displayed entry */ while(i >= 0L){ if(!as.zoomed || entry_is_selected(pab->address_book->selects, (a_c_arg_t)el)) break; el--; i--; } if(i >= 0){ new->dlcelnum = el; abe = adrbk_get_ae(pab->address_book, (a_c_arg_t)new->dlcelnum); if(abe && abe->tag == Single) new->type = DlcSimple; else if(abe && abe->tag == List){ if(F_ON(F_EXPANDED_DISTLISTS,ps_global) || exp_is_expanded(pab->address_book->exp, (a_c_arg_t)new->dlcelnum)){ list_count = listmem_count_from_abe(abe); if(list_count == 0) new->type = DlcListEmpty; else{ new->type = DlcListEnt; new->dlcoffset = list_count - 1; } } else new->type = DlcListClickHere; } } else if(as.zoomed) new->type = DlcZoomEmpty; else new->type = DlcEmpty; } } return(new); } /* * This returns the actual dlc instead of the dl within the dlc. */ DL_CACHE_S * get_dlc(long int row) { dprint((11, "- get_dlc(%ld) -\n", row)); return(dlc_mgr(row, Lookup, (DL_CACHE_S *)NULL)); } /* * Move to new_dlc and give it row number row_number_to_assign_it. * We copy the passed in dlc in case the caller passed us a pointer into * the cache. */ void warp_to_dlc(DL_CACHE_S *new_dlc, long int row_number_to_assign_it) { DL_CACHE_S dlc; dprint((9, "- warp_to_dlc(%ld) -\n", row_number_to_assign_it)); dlc = *new_dlc; /* * Make sure we can move forward and backward from these * types that we may wish to warp to. The caller may not have known * to set adrbk_num for these types. */ switch(dlc.type){ case DlcPersAdd: dlc.adrbk_num = 0; break; case DlcGlobAdd: dlc.adrbk_num = as.n_addrbk; break; default: break; } (void)dlc_mgr(row_number_to_assign_it, ArbitraryStartingPoint, &dlc); } /* * Move to first dlc and give it row number 0. */ void warp_to_beginning(void) { dprint((9, "- warp_to_beginning -\n")); (void)dlc_mgr(0L, FirstEntry, (DL_CACHE_S *)NULL); } /* * Move to first dlc in abook_num and give it row number 0. */ void warp_to_top_of_abook(int abook_num) { DL_CACHE_S dlc; dprint((9, "- warp_to_top_of_abook(%d) -\n", abook_num)); (void)get_top_dl_of_adrbk(abook_num, &dlc); warp_to_dlc(&dlc, 0L); } /* * Move to last dlc and give it row number 0. */ void warp_to_end(void) { dprint((9, "- warp_to_end -\n")); (void)dlc_mgr(0L, LastEntry, (DL_CACHE_S *)NULL); } /* * This flushes all of the cache that is related to this dlc. */ void flush_dlc_from_cache(DL_CACHE_S *dlc_to_flush) { dprint((11, "- flush_dlc_from_cache -\n")); (void)dlc_mgr(NO_LINE, FlushDlcFromCache, dlc_to_flush); } /* * Returns 1 if the dlc's match, 0 otherwise. */ int matching_dlcs(DL_CACHE_S *dlc1, DL_CACHE_S *dlc2) { if(!dlc1 || !dlc2 || dlc1->type != dlc2->type || dlc1->adrbk_num != dlc2->adrbk_num) return 0; switch(dlc1->type){ case DlcSimple: case DlcListHead: case DlcListBlankTop: case DlcListBlankBottom: case DlcListClickHere: case DlcListEmpty: return(dlc1->dlcelnum == dlc2->dlcelnum); case DlcListEnt: return(dlc1->dlcelnum == dlc2->dlcelnum && dlc1->dlcoffset == dlc2->dlcoffset); case DlcTitle: case DlcSubTitle: case DlcTitleNoPerm: case DlcTitleBlankTop: case DlcEmpty: case DlcZoomEmpty: case DlcNoPermission: case DlcPersAdd: case DlcGlobAdd: case DlcGlobDelim1: case DlcGlobDelim2: case DlcDirAccess: case DlcDirDelim1: case DlcDirDelim2: case DlcTitleDashTopCmb: case DlcTitleCmb: case DlcTitleDashBottomCmb: case DlcTitleBlankBottomCmb: case DlcClickHereCmb: case DlcTitleBlankTopCmb: return 1; case DlcNotSet: case DlcBeginning: case DlcOneBeforeBeginning: case DlcTwoBeforeBeginning: case DlcEnd: case DlcNoAbooks: return 0; default: break; } /*NOTREACHED*/ return 0; } /* * Uses information in new to fill in new->dl. */ void fill_in_dl_field(DL_CACHE_S *new) { AddrScrn_Disp *dl; PerAddrBook *pab; char buf[6*MAX_SCREEN_COLS + 1]; char buf2[6*1024]; char hostbuf[128]; char *folder; char *q; unsigned screen_width = ps_global->ttyo->screen_cols; unsigned got_width, need_width, cellwidth; screen_width = MIN(MAX_SCREEN_COLS, screen_width); dl = &(new->dl); /* free any previously allocated space */ switch(dl->type){ case Text: case Title: case TitleCmb: case AskServer: if(dl->usst) fs_give((void **)&dl->usst); default: break; } /* set up new dl */ switch(new->type){ case DlcListBlankTop: case DlcListBlankBottom: case DlcGlobDelim1: case DlcGlobDelim2: case DlcDirDelim1: case DlcDirDelim2: case DlcTitleBlankTop: case DlcDirBlankTop: case DlcTitleBlankBottomCmb: case DlcTitleBlankTopCmb: dl->type = Text; dl->usst = cpystr(""); break; case DlcTitleDashTopCmb: case DlcTitleDashBottomCmb: case DlcDirDelim1a: case DlcDirDelim1c: /* line of dashes in txt field */ dl->type = Text; memset((void *)buf, '-', screen_width * sizeof(char)); buf[screen_width] = '\0'; dl->usst = cpystr(buf); break; case DlcNoPermission: dl->type = Text; dl->usst = cpystr(NO_PERMISSION); break; case DlcTitle: case DlcTitleNoPerm: dl->type = Title; pab = &as.adrbks[new->adrbk_num]; /* title for this addrbook */ snprintf(buf, sizeof(buf), " %s", pab->abnick ? pab->abnick : pab->filename); cellwidth = utf8_width(buf); if(cellwidth > screen_width) cellwidth = utf8_truncate(buf, screen_width); /* add space padding */ if(cellwidth < screen_width) snprintf(buf+strlen(buf), sizeof(buf)-strlen(buf), "%*.*s", screen_width - cellwidth, screen_width - cellwidth, ""); buf[sizeof(buf)-1] = '\0'; if(as.ro_warning && (pab->access == NoAccess || pab->access == ReadOnly)){ char *str; if(pab->access == NoAccess) str = NOACCESS; else str = READONLY; need_width = utf8_width(str); if(screen_width > need_width && (q = utf8_count_back_width(buf, buf+strlen(buf), need_width, &got_width)) != NULL) (void) utf8_pad_to_width(q, str, sizeof(buf)-(q-buf), need_width, 0); } buf[sizeof(buf)-1] = '\0'; dl->usst = cpystr(buf); break; case DlcSubTitle: dl->type = Text; pab = &as.adrbks[new->adrbk_num]; /* Find a hostname to put in title */ hostbuf[0] = '\0'; folder = NULL; if(pab->type & REMOTE_VIA_IMAP && pab->filename){ char *start, *end = NULL; start = strindex(pab->filename, '{'); if(start) end = strindex(start+1, '}'); if(start && end){ strncpy(hostbuf, start + 1, MIN(end - start - 1, sizeof(hostbuf)-1)); hostbuf[MIN(end - start - 1, sizeof(hostbuf)-1)] = '\0'; if(*(end+1)) folder = end+1; } } if(!folder) folder = pab->filename; /* * Just trying to find the name relative to the home directory * to display in an OS-independent way. */ if(folder && in_dir(ps_global->home_dir, folder)){ char *p, *new_folder = NULL, *savep = NULL; int l, saveval = 0; l = strlen(ps_global->home_dir); while(!new_folder && (p = last_cmpnt(folder)) != NULL){ if(savep){ *savep = saveval; savep = NULL; } if(folder + l == p || folder + l + 1 == p) new_folder = p; else{ savep = --p; saveval = *savep; *savep = '\0'; } } if(savep) *savep = saveval; if(new_folder) folder = new_folder; } snprintf(buf, sizeof(buf), " %s AddressBook%s%s in %s", (pab->type & GLOBAL) ? "Global" : "Personal", (pab->type & REMOTE_VIA_IMAP && *hostbuf) ? " on " : "", (pab->type & REMOTE_VIA_IMAP && *hostbuf) ? hostbuf : "", (folder && *folder) ? folder : ""); cellwidth = utf8_width(buf); if(cellwidth > screen_width) cellwidth = utf8_truncate(buf, screen_width); /* add space padding */ if(cellwidth < screen_width) snprintf(buf+strlen(buf), sizeof(buf)-strlen(buf), "%*.*s", screen_width - cellwidth, screen_width - cellwidth, ""); buf[sizeof(buf)-1] = '\0'; dl->usst = cpystr(buf); break; case DlcTitleCmb: dl->type = TitleCmb; pab = &as.adrbks[new->adrbk_num]; /* title for this addrbook */ snprintf(buf, sizeof(buf), "%s AddressBook <%s>", (new->adrbk_num < as.how_many_personals) ? "Personal" : "Global", pab->abnick ? pab->abnick : pab->filename); cellwidth = utf8_width(buf); if(cellwidth > screen_width) cellwidth = utf8_truncate(buf, screen_width); /* add space padding */ if(cellwidth < screen_width) snprintf(buf+strlen(buf), sizeof(buf)-strlen(buf), "%*.*s", screen_width - cellwidth, screen_width - cellwidth, ""); buf[sizeof(buf)-1] = '\0'; if(as.ro_warning && (pab->access == NoAccess || pab->access == ReadOnly)){ char *str; if(pab->access == NoAccess) str = NOACCESS; else str = READONLY; need_width = utf8_width(str); if(screen_width > need_width && (q = utf8_count_back_width(buf, buf+strlen(buf), need_width, &got_width)) != NULL) (void) utf8_pad_to_width(q, str, sizeof(buf)-(q-buf), need_width, 0); } buf[sizeof(buf)-1] = '\0'; dl->usst = cpystr(buf); break; case DlcDirDelim1b: dl->type = Text; utf8_snprintf(buf, sizeof(buf), "%-*.*w", screen_width, screen_width, OLDSTYLE_DIR_TITLE); dl->usst = cpystr(buf); break; case DlcClickHereCmb: dl->type = ClickHereCmb; break; case DlcListClickHere: dl->type = ListClickHere; dl->elnum = new->dlcelnum; break; case DlcListEmpty: dl->type = ListEmpty; dl->elnum = new->dlcelnum; break; case DlcEmpty: dl->type = Empty; break; case DlcZoomEmpty: dl->type = ZoomEmpty; break; case DlcNoAbooks: dl->type = NoAbooks; break; case DlcPersAdd: dl->type = AddFirstPers; break; case DlcGlobAdd: dl->type = AddFirstGlob; break; case DlcDirAccess: dl->type = AskServer; #ifdef ENABLE_LDAP { LDAP_SERV_S *info; info = break_up_ldap_server(ps_global->VAR_LDAP_SERVERS[new->adrbk_num]); snprintf(buf2, sizeof(buf2), " %s", (info && info->nick && *info->nick) ? info->nick : (info && info->serv && *info->serv) ? info->serv : comatose(new->adrbk_num + 1)); if(info) free_ldap_server_info(&info); } #else snprintf(buf2, sizeof(buf2), " %s", comatose(new->adrbk_num + 1)); #endif utf8_snprintf(buf, sizeof(buf), "%-*.*w", screen_width, screen_width, buf2); dl->usst = cpystr(buf); break; case DlcDirSubTitle: dl->type = Text; #ifdef ENABLE_LDAP { LDAP_SERV_S *info; info = break_up_ldap_server(ps_global->VAR_LDAP_SERVERS[new->adrbk_num]); if(info && info->port >= 0) snprintf(buf2, sizeof(buf2), " Directory Server on %s:%d", (info && info->serv && *info->serv) ? info->serv : "", info->port); else snprintf(buf2, sizeof(buf2), " Directory Server on %s", (info && info->serv && *info->serv) ? info->serv : ""); if(info) free_ldap_server_info(&info); } #else snprintf(buf2, sizeof(buf2), " Directory Server %s", comatose(new->adrbk_num + 1)); #endif utf8_snprintf(buf, sizeof(buf), "%-*.*w", screen_width, screen_width, buf2); dl->usst = cpystr(buf); break; case DlcSimple: dl->type = Simple; dl->elnum = new->dlcelnum; break; case DlcListHead: dl->type = ListHead; dl->elnum = new->dlcelnum; break; case DlcListEnt: dl->type = ListEnt; dl->elnum = new->dlcelnum; dl->l_offset = new->dlcoffset; break; case DlcBeginning: case DlcOneBeforeBeginning: case DlcTwoBeforeBeginning: dl->type = Beginning; break; case DlcEnd: dl->type = End; break; default: q_status_message(SM_ORDER | SM_DING, 5, 10, _("Bug in addrbook, not supposed to happen, re-syncing...")); dprint((1, "Bug in addrbook, impossible dflt in fill_in_dl (%d)\n", new->type)); /* jump back to a safe starting point */ longjmp(addrbook_changed_unexpectedly, 1); /*NOTREACHED*/ } } /* * Returns a pointer to the member_number'th list member of the list * associated with this display line. */ char * listmem(long int row) { PerAddrBook *pab; AddrScrn_Disp *dl; dl = dlist(row); if(dl->type != ListEnt) return((char *)NULL); pab = &as.adrbks[adrbk_num_from_lineno(row)]; return(listmem_from_dl(pab->address_book, dl)); } /* * Returns a pointer to the list member * associated with this display line. */ char * listmem_from_dl(AdrBk *address_book, AddrScrn_Disp *dl) { AdrBk_Entry *abe; char **p = (char **)NULL; /* This shouldn't happen */ if(dl->type != ListEnt) return((char *)NULL); abe = adrbk_get_ae(address_book, (a_c_arg_t) dl->elnum); /* * If we wanted to be more careful, We'd go through the list making sure * we don't pass the end. We'll count on the caller being careful * instead. */ if(abe && abe->tag == List){ p = abe->addr.list; p += dl->l_offset; } return((p && *p) ? *p : (char *)NULL); } /* * How many members in list? */ adrbk_cntr_t listmem_count_from_abe(AdrBk_Entry *abe) { char **p; if(abe->tag != List) return 0; for(p = abe->addr.list; p != NULL && *p != NULL; p++) ;/* do nothing */ return((adrbk_cntr_t)(p - abe->addr.list)); } /* * Return a ptr to the row'th line of the global disp_list. * Line numbers count up but you can't count on knowing which line number * goes with the first or the last row. That is, row 0 is not necessarily * special. It could be before the rows that make up the display list, after * them, or anywhere in between. You can't tell what the last row is * numbered, but a dl with type End is returned when you get past the end. * You can't tell what the number of the first row is, but if you go past * the first row a dl of type Beginning will be returned. Row numbers can * be positive or negative. Their values have no meaning other than how * they line up relative to other row numbers. */ AddrScrn_Disp * dlist(long int row) { DL_CACHE_S *dlc = (DL_CACHE_S *)NULL; dlc = get_dlc(row); if(dlc){ fill_in_dl_field(dlc); return(&dlc->dl); } else{ q_status_message(SM_ORDER | SM_DING, 5, 10, _("Bug in addrbook, not supposed to happen, re-syncing...")); dprint((1, "Bug in addrbook (null dlc in dlist(%ld), not supposed to happen\n", row)); /* jump back to a safe starting point */ dprint((1, "dlist: panic: initialized %d, n_addrbk %d, cur_row %d, top_ent %ld, ro_warning %d, no_op_possbl %d\n", as.initialized, as.n_addrbk, as.cur_row, as.top_ent, as.ro_warning, as.no_op_possbl)); longjmp(addrbook_changed_unexpectedly, 1); /*NOTREACHED*/ } } /* * Returns the index of the current address book. */ int adrbk_num_from_lineno(long int lineno) { DL_CACHE_S *dlc; dlc = get_dlc(lineno); return(dlc->adrbk_num); }