n_go_input(): fix n_PS_READLINE bit in TTY case thus interactive compose mode
[s-mailx.git] / nam-a-grp.c
bloba4c689c91e986b9b9449025a6235a80ad5a1e1fe
1 /*@ S-nail - a mail user agent derived from Berkeley Mail.
2 *@ Name lists, alternates and groups: aliases, mailing lists, shortcuts.
3 *@ TODO Dynamic hashmaps; names and (these) groups have _nothing_ in common!
5 * Copyright (c) 2000-2004 Gunnar Ritter, Freiburg i. Br., Germany.
6 * Copyright (c) 2012 - 2017 Steffen (Daode) Nurpmeso <steffen@sdaoden.eu>.
7 */
8 /*
9 * Copyright (c) 1980, 1993
10 * The Regents of the University of California. All rights reserved.
12 * Redistribution and use in source and binary forms, with or without
13 * modification, are permitted provided that the following conditions
14 * are met:
15 * 1. Redistributions of source code must retain the above copyright
16 * notice, this list of conditions and the following disclaimer.
17 * 2. Redistributions in binary form must reproduce the above copyright
18 * notice, this list of conditions and the following disclaimer in the
19 * documentation and/or other materials provided with the distribution.
20 * 3. Neither the name of the University nor the names of its contributors
21 * may be used to endorse or promote products derived from this software
22 * without specific prior written permission.
24 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
25 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
26 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
27 * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
28 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
29 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
30 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
31 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
32 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
33 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
34 * SUCH DAMAGE.
36 #undef n_FILE
37 #define n_FILE nam_a_grp
39 #ifndef HAVE_AMALGAMATION
40 # include "nail.h"
41 #endif
43 enum a_nag_type{
44 /* Main types */
45 a_NAG_T_ALTERNATES = 1,
46 a_NAG_T_COMMANDALIAS,
47 a_NAG_T_ALIAS,
48 a_NAG_T_MLIST,
49 a_NAG_T_SHORTCUT,
50 a_NAG_T_CHARSETALIAS,
51 a_NAG_T_FILETYPE,
52 a_NAG_T_MASK = 0x1F,
54 /* Subtype bits and flags */
55 a_NAG_T_SUBSCRIBE = 1u<<6,
56 a_NAG_T_REGEX = 1u<<7,
58 /* Extended type mask to be able to reflect what we really have; i.e., mlist
59 * can have a_NAG_T_REGEX if they are subscribed or not, but `mlsubscribe'
60 * should print out only a_NAG_T_MLIST which have the a_NAG_T_SUBSCRIBE
61 * attribute set */
62 a_NAG_T_PRINT_MASK = a_NAG_T_MASK | a_NAG_T_SUBSCRIBE
64 n_CTA(a_NAG_T_MASK >= a_NAG_T_FILETYPE, "Mask does not cover necessary bits");
66 struct a_nag_group{
67 struct a_nag_group *ng_next;
68 ui32_t ng_subclass_off; /* of "subclass" in .ng_id (if any) */
69 ui16_t ng_id_len_sub; /* length of .ng_id: _subclass_off - this */
70 ui8_t ng_type; /* enum a_nag_type */
71 /* Identifying name, of variable size. Dependent on actual "subtype" more
72 * data follows thereafter, but note this is always used (i.e., for regular
73 * expression entries this is still set to the plain string) */
74 char ng_id[n_VFIELD_SIZE(1)];
76 #define a_NAG_GP_TO_SUBCLASS(X,G) \
77 do{\
78 union a_nag_group_subclass {void *gs_vp; char *gs_cp;} a__gs__;\
79 a__gs__.gs_cp = &((char*)n_UNCONST(G))[(G)->ng_subclass_off];\
80 (X) = a__gs__.gs_vp;\
81 }while(0)
83 struct a_nag_grp_names_head{
84 struct a_nag_grp_names *ngnh_head;
87 struct a_nag_grp_names{
88 struct a_nag_grp_names *ngn_next;
89 char ngn_id[n_VFIELD_SIZE(0)];
92 #ifdef HAVE_REGEX
93 struct a_nag_grp_regex{
94 struct a_nag_grp_regex *ngr_last;
95 struct a_nag_grp_regex *ngr_next;
96 struct a_nag_group *ngr_mygroup; /* xxx because lists use grp_regex*! ?? */
97 size_t ngr_hits; /* Number of times this group matched */
98 regex_t ngr_regex;
100 #endif
102 struct a_nag_cmd_alias{
103 struct str nca_expand;
106 struct a_nag_file_type{
107 struct str nft_load;
108 struct str nft_save;
111 struct a_nag_group_lookup{
112 struct a_nag_group **ngl_htable;
113 struct a_nag_group **ngl_slot;
114 struct a_nag_group *ngl_slot_last;
115 struct a_nag_group *ngl_group;
118 static struct n_file_type const a_nag_OBSOLETE_xz = { /* TODO v15 compat */
119 "xz", 2, "xz -cd", sizeof("xz -cd") -1, "xz -cz", sizeof("xz -cz") -1
120 }, a_nag_OBSOLETE_gz = {
121 "gz", 2, "gzip -cd", sizeof("gzip -cd") -1, "gzip -cz", sizeof("gzip -cz") -1
122 }, a_nag_OBSOLETE_bz2 = {
123 "bz2", 3, "bzip2 -cd", sizeof("bzip2 -cd") -1,
124 "bzip2 -cz", sizeof("bzip2 -cz") -1
127 /* `alternates' */
128 static struct a_nag_group *a_nag_alternates_heads[HSHSIZE];
130 /* `commandalias' */
131 static struct a_nag_group *a_nag_commandalias_heads[HSHSIZE];
133 /* `alias' */
134 static struct a_nag_group *a_nag_alias_heads[HSHSIZE];
136 /* `mlist', `mlsubscribe'. Anything is stored in the hashmap.. */
137 static struct a_nag_group *a_nag_mlist_heads[HSHSIZE];
139 /* ..but entries which have a_NAG_T_REGEX set are false lookups and will really
140 * be accessed via sequential lists instead, which are type-specific for better
141 * performance, but also to make it possible to have ".*@xy.org" as a mlist
142 * and "(one|two)@xy.org" as a mlsubscription.
143 * These lists use a bit of QOS optimization in that a matching group will
144 * become relinked as the new list head if its hit count is
145 * (>= ((xy_hits / _xy_size) >> 2))
146 * Note that the hit counts only count currently linked in nodes.. */
147 #ifdef HAVE_REGEX
148 static struct a_nag_grp_regex *a_nag_mlist_regex;
149 static struct a_nag_grp_regex *a_nag_mlsub_regex;
150 static size_t a_nag_mlist_size;
151 static size_t a_nag_mlist_hits;
152 static size_t a_nag_mlsub_size;
153 static size_t a_nag_mlsub_hits;
154 #endif
156 /* `shortcut' */
157 static struct a_nag_group *a_nag_shortcut_heads[HSHSIZE];
159 /* `charsetalias' */
160 static struct a_nag_group *a_nag_charsetalias_heads[HSHSIZE];
162 /* `filetype' */
163 static struct a_nag_group *a_nag_filetype_heads[HSHSIZE];
165 /* Same name, while taking care for *allnet*? */
166 static bool_t a_nag_is_same_name(char const *n1, char const *n2);
168 /* Mark all nodes with the given name */
169 static struct name *a_nag_namelist_mark_name(struct name *np, char const *name);
171 /* Grab a single name (liberal name) */
172 static char const *a_nag_yankname(char const *ap, char *wbuf,
173 char const *separators, int keepcomms);
175 /* Extraction multiplexer that splits an input line to names */
176 static struct name *a_nag_extract1(char const *line, enum gfield ntype,
177 char const *separators, bool_t keepcomms);
179 /* Recursively expand a alias name. Limit expansion to some fixed level.
180 * Direct recursion is not expanded for convenience */
181 static struct name *a_nag_gexpand(size_t level, struct name *nlist,
182 struct a_nag_group *ngp, bool_t metoo, int ntype);
184 /* elide() helper */
185 static int a_nag_elide_qsort(void const *s1, void const *s2);
187 /* Lookup a group, return it or NULL, fill in glp anyway */
188 static struct a_nag_group *a_nag_group_lookup(enum a_nag_type nt,
189 struct a_nag_group_lookup *nglp, char const *id);
191 /* Easier-to-use wrapper around _group_lookup() */
192 static struct a_nag_group *a_nag_group_find(enum a_nag_type nt, char const *id);
194 /* Iteration: go to the first group, which also inits the iterator. A valid
195 * iterator can be stepped via _next(). A NULL return means no (more) groups
196 * to be iterated exist, in which case only nglp->ngl_group is set (NULL) */
197 static struct a_nag_group *a_nag_group_go_first(enum a_nag_type nt,
198 struct a_nag_group_lookup *nglp);
199 static struct a_nag_group *a_nag_group_go_next(struct a_nag_group_lookup *nglp);
201 /* Fetch the group id, create it as necessary, fail with NULL if impossible */
202 static struct a_nag_group *a_nag_group_fetch(enum a_nag_type nt, char const *id,
203 size_t addsz);
205 /* "Intelligent" delete which handles a "*" id, too;
206 * returns a true boolean if a group was deleted, and always succeeds for "*" */
207 static bool_t a_nag_group_del(enum a_nag_type nt, char const *id);
209 static struct a_nag_group *a_nag__group_del(struct a_nag_group_lookup *nglp);
210 static void a_nag__names_del(struct a_nag_group *ngp);
212 /* Print all groups of the given type, alphasorted, or store in `vput' varname:
213 * only in this mode it can return failure */
214 static bool_t a_nag_group_print_all(enum a_nag_type nt,
215 char const *varname);
217 static int a_nag__group_print_qsorter(void const *a, void const *b);
219 /* Really print a group, actually. Or store in vputsp, if set.
220 * Return number of written lines */
221 static size_t a_nag_group_print(struct a_nag_group const *ngp, FILE *fo,
222 struct n_string *vputsp);
224 /* Multiplexers for list and subscribe commands */
225 static int a_nag_mlmux(enum a_nag_type nt, char const **argv);
226 static int a_nag_unmlmux(enum a_nag_type nt, char const **argv);
228 /* Relinkers for the sequential match lists */
229 #ifdef HAVE_REGEX
230 static void a_nag_mlmux_linkin(struct a_nag_group *ngp);
231 static void a_nag_mlmux_linkout(struct a_nag_group *ngp);
232 # define a_NAG_MLMUX_LINKIN(GP) \
233 do if((GP)->ng_type & a_NAG_T_REGEX) a_nag_mlmux_linkin(GP); while(0)
234 # define a_NAG_MLMUX_LINKOUT(GP) \
235 do if((GP)->ng_type & a_NAG_T_REGEX) a_nag_mlmux_linkout(GP); while(0)
236 #else
237 # define a_NAG_MLMUX_LINKIN(GP)
238 # define a_NAG_MLMUX_LINKOUT(GP)
239 #endif
241 static bool_t
242 a_nag_is_same_name(char const *n1, char const *n2){
243 bool_t rv;
244 char c1, c2, c1r, c2r;
245 NYD2_ENTER;
247 if(ok_blook(allnet)){
248 for(;; ++n1, ++n2){
249 c1 = *n1;
250 c1 = lowerconv(c1);
251 c1r = (c1 == '\0' || c1 == '@');
252 c2 = *n2;
253 c2 = lowerconv(c2);
254 c2r = (c2 == '\0' || c2 == '@');
256 if(c1r || c2r){
257 rv = (c1r == c2r);
258 break;
259 }else if(c1 != c2){
260 rv = FAL0;
261 break;
264 }else
265 rv = !asccasecmp(n1, n2);
266 NYD2_LEAVE;
267 return rv;
270 static struct name *
271 a_nag_namelist_mark_name(struct name *np, char const *name){
272 struct name *p;
273 NYD2_ENTER;
275 for(p = np; p != NULL; p = p->n_flink)
276 if(!(p->n_type & GDEL) && !(p->n_flags & (ui32_t)SI32_MIN) &&
277 a_nag_is_same_name(p->n_name, name))
278 p->n_flags |= (ui32_t)SI32_MIN;
279 NYD2_LEAVE;
280 return np;
283 static char const *
284 a_nag_yankname(char const *ap, char *wbuf, char const *separators,
285 int keepcomms)
287 char const *cp;
288 char *wp, c, inquote, lc, lastsp;
289 NYD_ENTER;
291 *(wp = wbuf) = '\0';
293 /* Skip over intermediate list trash, as in ".org> , <xy@zz.org>" */
294 for (c = *ap; blankchar(c) || c == ','; c = *++ap)
296 if (c == '\0') {
297 cp = NULL;
298 goto jleave;
301 /* Parse a full name: TODO RFC 5322
302 * - Keep everything in quotes, liberal handle *quoted-pair*s therein
303 * - Skip entire (nested) comments
304 * - In non-quote, non-comment, join adjacent space to a single SP
305 * - Understand separators only in non-quote, non-comment context,
306 * and only if not part of a *quoted-pair* (XXX too liberal) */
307 cp = ap;
308 for (inquote = lc = lastsp = 0;; lc = c, ++cp) {
309 c = *cp;
310 if (c == '\0')
311 break;
312 if (c == '\\')
313 goto jwpwc;
314 if (c == '"') {
315 if (lc != '\\')
316 inquote = !inquote;
317 #if 0 /* TODO when doing real RFC 5322 parsers - why have i done this? */
318 else
319 --wp;
320 #endif
321 goto jwpwc;
323 if (inquote || lc == '\\') {
324 jwpwc:
325 *wp++ = c;
326 lastsp = 0;
327 continue;
329 if (c == '(') {
330 ap = cp;
331 cp = skip_comment(cp + 1);
332 if (keepcomms)
333 while (ap < cp)
334 *wp++ = *ap++;
335 --cp;
336 lastsp = 0;
337 continue;
339 if (strchr(separators, c) != NULL)
340 break;
342 lc = lastsp;
343 lastsp = blankchar(c);
344 if (!lastsp || !lc)
345 *wp++ = c;
347 if (blankchar(lc))
348 --wp;
350 *wp = '\0';
351 jleave:
352 NYD_LEAVE;
353 return cp;
356 static struct name *
357 a_nag_extract1(char const *line, enum gfield ntype, char const *separators,
358 bool_t keepcomms)
360 struct name *topp, *np, *t;
361 char const *cp;
362 char *nbuf;
363 NYD_ENTER;
365 topp = NULL;
366 if (line == NULL || *line == '\0')
367 goto jleave;
369 np = NULL;
370 cp = line;
371 nbuf = n_alloc(strlen(line) +1);
372 while ((cp = a_nag_yankname(cp, nbuf, separators, keepcomms)) != NULL) {
373 t = nalloc(nbuf, ntype);
374 if (topp == NULL)
375 topp = t;
376 else
377 np->n_flink = t;
378 t->n_blink = np;
379 np = t;
381 n_free(nbuf);
382 jleave:
383 NYD_LEAVE;
384 return topp;
387 static struct name *
388 a_nag_gexpand(size_t level, struct name *nlist, struct a_nag_group *ngp,
389 bool_t metoo, int ntype){
390 struct a_nag_grp_names *ngnp;
391 struct name *nlist_tail;
392 char const *logname;
393 struct a_nag_grp_names_head *ngnhp;
394 NYD2_ENTER;
396 if(UICMP(z, level++, >, n_ALIAS_MAXEXP)){
397 n_err(_("Expanding alias to depth larger than %d\n"), n_ALIAS_MAXEXP);
398 goto jleave;
401 a_NAG_GP_TO_SUBCLASS(ngnhp, ngp);
402 logname = ok_vlook(LOGNAME);
404 for(ngnp = ngnhp->ngnh_head; ngnp != NULL; ngnp = ngnp->ngn_next){
405 struct a_nag_group *xngp;
406 char *cp;
408 cp = ngnp->ngn_id;
410 if(!strcmp(cp, ngp->ng_id))
411 goto jas_is;
413 if((xngp = a_nag_group_find(a_NAG_T_ALIAS, cp)) != NULL){
414 /* For S-nail(1), the "alias" may *be* the sender in that a name maps
415 * to a full address specification; aliases cannot be empty */
416 struct a_nag_grp_names_head *xngnhp;
418 a_NAG_GP_TO_SUBCLASS(xngnhp, xngp);
420 assert(xngnhp->ngnh_head != NULL);
421 if(metoo || xngnhp->ngnh_head->ngn_next != NULL ||
422 !a_nag_is_same_name(cp, logname))
423 nlist = a_nag_gexpand(level, nlist, xngp, metoo, ntype);
424 continue;
427 /* Here we should allow to expand to itself if only person in alias */
428 jas_is:
429 if(metoo || ngnhp->ngnh_head->ngn_next == NULL ||
430 !a_nag_is_same_name(cp, logname)){
431 struct name *np;
433 np = nalloc(cp, ntype | GFULL);
434 if((nlist_tail = nlist) != NULL){
435 while(nlist_tail->n_flink != NULL)
436 nlist_tail = nlist_tail->n_flink;
437 nlist_tail->n_flink = np;
438 np->n_blink = nlist_tail;
439 }else
440 nlist = np;
443 jleave:
444 NYD2_LEAVE;
445 return nlist;
448 static int
449 a_nag_elide_qsort(void const *s1, void const *s2){
450 struct name const * const *np1, * const *np2;
451 int rv;
452 NYD2_ENTER;
454 np1 = s1;
455 np2 = s2;
456 rv = asccasecmp((*np1)->n_name, (*np2)->n_name);
457 NYD2_LEAVE;
458 return rv;
461 static struct a_nag_group *
462 a_nag_group_lookup(enum a_nag_type nt, struct a_nag_group_lookup *nglp,
463 char const *id){
464 char c1;
465 struct a_nag_group *lngp, *ngp;
466 bool_t icase;
467 NYD2_ENTER;
469 icase = FAL0;
471 /* C99 */{
472 ui32_t h;
473 struct a_nag_group **ngpa;
475 switch((nt &= a_NAG_T_MASK)){
476 case a_NAG_T_ALTERNATES:
477 ngpa = a_nag_alternates_heads;
478 icase = TRU1;
479 break;
480 default:
481 case a_NAG_T_COMMANDALIAS:
482 ngpa = a_nag_commandalias_heads;
483 break;
484 case a_NAG_T_ALIAS:
485 ngpa = a_nag_alias_heads;
486 break;
487 case a_NAG_T_MLIST:
488 ngpa = a_nag_mlist_heads;
489 icase = TRU1;
490 break;
491 case a_NAG_T_SHORTCUT:
492 ngpa = a_nag_shortcut_heads;
493 break;
494 case a_NAG_T_CHARSETALIAS:
495 ngpa = a_nag_charsetalias_heads;
496 icase = TRU1;
497 break;
498 case a_NAG_T_FILETYPE:
499 ngpa = a_nag_filetype_heads;
500 icase = TRU1;
501 break;
504 nglp->ngl_htable = ngpa;
505 h = icase ? n_torek_hash(id) : n_torek_ihash(id);
506 ngp = *(nglp->ngl_slot = &ngpa[h % HSHSIZE]);
509 lngp = NULL;
510 c1 = *id++;
512 if(icase){
513 c1 = lowerconv(c1);
514 for(; ngp != NULL; lngp = ngp, ngp = ngp->ng_next)
515 if((ngp->ng_type & a_NAG_T_MASK) == nt && *ngp->ng_id == c1 &&
516 !asccasecmp(&ngp->ng_id[1], id))
517 break;
518 }else{
519 for(; ngp != NULL; lngp = ngp, ngp = ngp->ng_next)
520 if((ngp->ng_type & a_NAG_T_MASK) == nt && *ngp->ng_id == c1 &&
521 !strcmp(&ngp->ng_id[1], id))
522 break;
525 nglp->ngl_slot_last = lngp;
526 nglp->ngl_group = ngp;
527 NYD2_LEAVE;
528 return ngp;
531 static struct a_nag_group *
532 a_nag_group_find(enum a_nag_type nt, char const *id){
533 struct a_nag_group_lookup ngl;
534 struct a_nag_group *ngp;
535 NYD2_ENTER;
537 ngp = a_nag_group_lookup(nt, &ngl, id);
538 NYD2_LEAVE;
539 return ngp;
542 static struct a_nag_group *
543 a_nag_group_go_first(enum a_nag_type nt, struct a_nag_group_lookup *nglp){
544 size_t i;
545 struct a_nag_group **ngpa, *ngp;
546 NYD2_ENTER;
548 switch((nt &= a_NAG_T_MASK)){
549 case a_NAG_T_ALTERNATES:
550 ngpa = a_nag_alternates_heads;
551 break;
552 default:
553 case a_NAG_T_COMMANDALIAS:
554 ngpa = a_nag_commandalias_heads;
555 break;
556 case a_NAG_T_ALIAS:
557 ngpa = a_nag_alias_heads;
558 break;
559 case a_NAG_T_MLIST:
560 ngpa = a_nag_mlist_heads;
561 break;
562 case a_NAG_T_SHORTCUT:
563 ngpa = a_nag_shortcut_heads;
564 break;
565 case a_NAG_T_CHARSETALIAS:
566 ngpa = a_nag_charsetalias_heads;
567 break;
568 case a_NAG_T_FILETYPE:
569 ngpa = a_nag_filetype_heads;
570 break;
573 nglp->ngl_htable = ngpa;
575 for(i = 0; i < HSHSIZE; ++ngpa, ++i)
576 if((ngp = *ngpa) != NULL){
577 nglp->ngl_slot = ngpa;
578 nglp->ngl_group = ngp;
579 goto jleave;
582 nglp->ngl_group = ngp = NULL;
583 jleave:
584 nglp->ngl_slot_last = NULL;
585 NYD2_LEAVE;
586 return ngp;
589 static struct a_nag_group *
590 a_nag_group_go_next(struct a_nag_group_lookup *nglp){
591 struct a_nag_group *ngp, **ngpa;
592 NYD2_ENTER;
594 if((ngp = nglp->ngl_group->ng_next) != NULL)
595 nglp->ngl_slot_last = nglp->ngl_group;
596 else{
597 nglp->ngl_slot_last = NULL;
598 for(ngpa = &nglp->ngl_htable[HSHSIZE]; ++nglp->ngl_slot < ngpa;)
599 if((ngp = *nglp->ngl_slot) != NULL)
600 break;
602 nglp->ngl_group = ngp;
603 NYD2_LEAVE;
604 return ngp;
607 static struct a_nag_group *
608 a_nag_group_fetch(enum a_nag_type nt, char const *id, size_t addsz){
609 struct a_nag_group_lookup ngl;
610 struct a_nag_group *ngp;
611 size_t l, i;
612 NYD2_ENTER;
614 if((ngp = a_nag_group_lookup(nt, &ngl, id)) != NULL)
615 goto jleave;
617 l = strlen(id) +1;
618 if(UIZ_MAX - n_ALIGN(l) <=
619 n_ALIGN(n_VSTRUCT_SIZEOF(struct a_nag_group, ng_id)))
620 goto jleave;
622 i = n_ALIGN(n_VSTRUCT_SIZEOF(struct a_nag_group, ng_id) + l);
623 switch(nt & a_NAG_T_MASK){
624 case a_NAG_T_ALTERNATES:
625 case a_NAG_T_SHORTCUT:
626 case a_NAG_T_CHARSETALIAS:
627 default:
628 break;
629 case a_NAG_T_COMMANDALIAS:
630 addsz += sizeof(struct a_nag_cmd_alias);
631 break;
632 case a_NAG_T_ALIAS:
633 addsz += sizeof(struct a_nag_grp_names_head);
634 break;
635 case a_NAG_T_MLIST:
636 #ifdef HAVE_REGEX
637 if(n_is_maybe_regex(id)){
638 addsz = sizeof(struct a_nag_grp_regex);
639 nt |= a_NAG_T_REGEX;
641 #endif
642 break;
643 case a_NAG_T_FILETYPE:
644 addsz += sizeof(struct a_nag_file_type);
645 break;
647 if(UIZ_MAX - i < addsz || UI32_MAX <= i || UI16_MAX < i - l)
648 goto jleave;
650 ngp = n_alloc(i + addsz);
651 memcpy(ngp->ng_id, id, l);
652 ngp->ng_subclass_off = (ui32_t)i;
653 ngp->ng_id_len_sub = (ui16_t)(i - --l);
654 ngp->ng_type = nt;
655 switch(nt & a_NAG_T_MASK){
656 case a_NAG_T_ALTERNATES:
657 case a_NAG_T_MLIST:
658 case a_NAG_T_CHARSETALIAS:
659 case a_NAG_T_FILETYPE:{
660 char *cp, c;
662 for(cp = ngp->ng_id; (c = *cp) != '\0'; ++cp)
663 *cp = lowerconv(c);
664 } break;
665 default:
666 break;
669 if((nt & a_NAG_T_MASK) == a_NAG_T_ALIAS){
670 struct a_nag_grp_names_head *ngnhp;
672 a_NAG_GP_TO_SUBCLASS(ngnhp, ngp);
673 ngnhp->ngnh_head = NULL;
675 #ifdef HAVE_REGEX
676 else if(nt & a_NAG_T_REGEX){
677 int s;
678 struct a_nag_grp_regex *ngrp;
680 a_NAG_GP_TO_SUBCLASS(ngrp, ngp);
682 if((s = regcomp(&ngrp->ngr_regex, id,
683 REG_EXTENDED | REG_ICASE | REG_NOSUB)) != 0){
684 n_err(_("Invalid regular expression: %s: %s\n"),
685 n_shexp_quote_cp(id, FAL0),
686 n_regex_err_to_doc(&ngrp->ngr_regex, s));
687 n_free(ngp);
688 ngp = NULL;
689 goto jleave;
691 ngrp->ngr_mygroup = ngp;
692 a_nag_mlmux_linkin(ngp);
694 #endif /* HAVE_REGEX */
696 ngp->ng_next = *ngl.ngl_slot;
697 *ngl.ngl_slot = ngp;
698 jleave:
699 NYD2_LEAVE;
700 return ngp;
703 static bool_t
704 a_nag_group_del(enum a_nag_type nt, char const *id){
705 struct a_nag_group_lookup ngl;
706 struct a_nag_group *ngp;
707 enum a_nag_type xnt;
708 NYD2_ENTER;
710 xnt = nt & a_NAG_T_MASK;
712 /* Delete 'em all? */
713 if(id[0] == '*' && id[1] == '\0'){
714 for(ngp = a_nag_group_go_first(nt, &ngl); ngp != NULL;)
715 ngp = ((ngp->ng_type & a_NAG_T_MASK) == xnt) ? a_nag__group_del(&ngl)
716 : a_nag_group_go_next(&ngl);
717 ngp = (struct a_nag_group*)TRU1;
718 }else if((ngp = a_nag_group_lookup(nt, &ngl, id)) != NULL){
719 if(ngp->ng_type & xnt)
720 a_nag__group_del(&ngl);
721 else
722 ngp = NULL;
724 NYD2_LEAVE;
725 return (ngp != NULL);
728 static struct a_nag_group *
729 a_nag__group_del(struct a_nag_group_lookup *nglp){
730 struct a_nag_group *x, *ngp;
731 NYD2_ENTER;
733 /* Overly complicated: link off this node, step ahead to next.. */
734 x = nglp->ngl_group;
735 if((ngp = nglp->ngl_slot_last) != NULL)
736 ngp = (ngp->ng_next = x->ng_next);
737 else{
738 nglp->ngl_slot_last = NULL;
739 ngp = (*nglp->ngl_slot = x->ng_next);
741 if(ngp == NULL){
742 struct a_nag_group **ngpa;
744 for(ngpa = &nglp->ngl_htable[HSHSIZE]; ++nglp->ngl_slot < ngpa;)
745 if((ngp = *nglp->ngl_slot) != NULL)
746 break;
749 nglp->ngl_group = ngp;
751 if((x->ng_type & a_NAG_T_MASK) == a_NAG_T_ALIAS)
752 a_nag__names_del(x);
753 #ifdef HAVE_REGEX
754 else if(x->ng_type & a_NAG_T_REGEX){
755 struct a_nag_grp_regex *ngrp;
757 a_NAG_GP_TO_SUBCLASS(ngrp, x);
759 regfree(&ngrp->ngr_regex);
760 a_nag_mlmux_linkout(x);
762 #endif
764 n_free(x);
765 NYD2_LEAVE;
766 return ngp;
769 static void
770 a_nag__names_del(struct a_nag_group *ngp){
771 struct a_nag_grp_names_head *ngnhp;
772 struct a_nag_grp_names *ngnp;
773 NYD2_ENTER;
775 a_NAG_GP_TO_SUBCLASS(ngnhp, ngp);
777 for(ngnp = ngnhp->ngnh_head; ngnp != NULL;){
778 struct a_nag_grp_names *x;
780 x = ngnp;
781 ngnp = ngnp->ngn_next;
782 n_free(x);
784 NYD2_LEAVE;
787 static bool_t
788 a_nag_group_print_all(enum a_nag_type nt, char const *varname){
789 struct n_string s;
790 size_t lines;
791 FILE *fp;
792 char const **ida;
793 struct a_nag_group const *ngp;
794 ui32_t h, i;
795 struct a_nag_group **ngpa;
796 char const *tname;
797 enum a_nag_type xnt;
798 NYD_ENTER;
800 if(varname != NULL)
801 n_string_creat_auto(&s);
803 xnt = nt & a_NAG_T_PRINT_MASK;
805 switch(xnt & a_NAG_T_MASK){
806 case a_NAG_T_ALTERNATES:
807 tname = "alternates";
808 ngpa = a_nag_alternates_heads;
809 break;
810 default:
811 case a_NAG_T_COMMANDALIAS:
812 tname = "commandalias";
813 ngpa = a_nag_commandalias_heads;
814 break;
815 case a_NAG_T_ALIAS:
816 tname = "alias";
817 ngpa = a_nag_alias_heads;
818 break;
819 case a_NAG_T_MLIST:
820 tname = "mlist";
821 ngpa = a_nag_mlist_heads;
822 break;
823 case a_NAG_T_SHORTCUT:
824 tname = "shortcut";
825 ngpa = a_nag_shortcut_heads;
826 break;
827 case a_NAG_T_CHARSETALIAS:
828 tname = "charsetalias";
829 ngpa = a_nag_charsetalias_heads;
830 break;
831 case a_NAG_T_FILETYPE:
832 tname = "filetype";
833 ngpa = a_nag_filetype_heads;
834 break;
837 /* Count entries */
838 for(i = h = 0; h < HSHSIZE; ++h)
839 for(ngp = ngpa[h]; ngp != NULL; ngp = ngp->ng_next)
840 if((ngp->ng_type & a_NAG_T_PRINT_MASK) == xnt)
841 ++i;
842 if(i == 0){
843 if(varname == NULL)
844 fprintf(n_stdout, _("# no %s registered\n"), tname);
845 goto jleave;
847 ++i;
848 ida = n_autorec_alloc(i * sizeof *ida);
850 /* Create alpha sorted array of entries */
851 for(i = h = 0; h < HSHSIZE; ++h)
852 for(ngp = ngpa[h]; ngp != NULL; ngp = ngp->ng_next)
853 if((ngp->ng_type & a_NAG_T_PRINT_MASK) == xnt)
854 ida[i++] = ngp->ng_id;
855 if(i > 1)
856 qsort(ida, i, sizeof *ida, &a_nag__group_print_qsorter);
857 ida[i] = NULL;
859 if(varname != NULL)
860 fp = NULL;
861 else if((fp = Ftmp(NULL, "nagprint", OF_RDWR | OF_UNLINK | OF_REGISTER)
862 ) == NULL)
863 fp = n_stdout;
865 /* Create visual result */
866 lines = 0;
868 switch(xnt & a_NAG_T_MASK){
869 case a_NAG_T_ALTERNATES:
870 if(fp != NULL){
871 fputs(tname, fp);
872 lines = 1;
874 break;
875 default:
876 break;
879 for(i = 0; ida[i] != NULL; ++i)
880 lines += a_nag_group_print(a_nag_group_find(nt, ida[i]), fp, &s);
882 #ifdef HAVE_REGEX
883 if(varname == NULL && (nt & a_NAG_T_MASK) == a_NAG_T_MLIST){
884 if(nt & a_NAG_T_SUBSCRIBE)
885 i = (ui32_t)a_nag_mlsub_size, h = (ui32_t)a_nag_mlsub_hits;
886 else
887 i = (ui32_t)a_nag_mlist_size, h = (ui32_t)a_nag_mlist_hits;
889 if(i > 0 && (n_poption & n_PO_D_V)){
890 assert(fp != NULL);
891 fprintf(fp, _("# %s list regex(7) total: %u entries, %u hits\n"),
892 (nt & a_NAG_T_SUBSCRIBE ? _("Subscribed") : _("Non-subscribed")),
893 i, h);
894 ++lines;
897 #endif
899 switch(xnt & a_NAG_T_MASK){
900 case a_NAG_T_ALTERNATES:
901 if(fp != NULL){
902 putc('\n', fp);
903 assert(lines == 1);
905 break;
906 default:
907 break;
910 if(varname == NULL && fp != n_stdout){
911 assert(fp != NULL);
912 page_or_print(fp, lines);
913 Fclose(fp);
916 jleave:
917 if(varname != NULL){
918 tname = n_string_cp(&s);
919 if(n_var_vset(varname, (uintptr_t)tname))
920 varname = NULL;
921 else
922 n_pstate_err_no = n_ERR_NOTSUP;
924 NYD_LEAVE;
925 return (varname == NULL);
928 static int
929 a_nag__group_print_qsorter(void const *a, void const *b){
930 int rv;
931 NYD2_ENTER;
933 rv = strcmp(*(char**)n_UNCONST(a), *(char**)n_UNCONST(b));
934 NYD2_LEAVE;
935 return rv;
938 static size_t
939 a_nag_group_print(struct a_nag_group const *ngp, FILE *fo,
940 struct n_string *vputsp){
941 char const *cp;
942 size_t rv;
943 NYD2_ENTER;
945 rv = 1;
947 switch(ngp->ng_type & a_NAG_T_MASK){
948 case a_NAG_T_ALTERNATES:{
949 if(fo != NULL)
950 fprintf(fo, " %s", ngp->ng_id);
951 else{
952 if(vputsp->s_len > 0)
953 vputsp = n_string_push_c(vputsp, ' ');
954 /*vputsp =*/ n_string_push_cp(vputsp, ngp->ng_id);
956 rv = 0;
957 } break;
958 case a_NAG_T_COMMANDALIAS:{
959 struct a_nag_cmd_alias *ncap;
961 assert(fo != NULL); /* xxx no vput yet */
962 a_NAG_GP_TO_SUBCLASS(ncap, ngp);
963 fprintf(fo, "commandalias %s %s\n",
964 n_shexp_quote_cp(ngp->ng_id, TRU1),
965 n_shexp_quote_cp(ncap->nca_expand.s, TRU1));
966 } break;
967 case a_NAG_T_ALIAS:{
968 struct a_nag_grp_names_head *ngnhp;
969 struct a_nag_grp_names *ngnp;
971 assert(fo != NULL); /* xxx no vput yet */
972 fprintf(fo, "alias %s ", ngp->ng_id);
974 a_NAG_GP_TO_SUBCLASS(ngnhp, ngp);
975 if((ngnp = ngnhp->ngnh_head) != NULL) { /* xxx always 1+ entries */
977 struct a_nag_grp_names *x;
979 x = ngnp;
980 ngnp = ngnp->ngn_next;
981 fprintf(fo, " \"%s\"", string_quote(x->ngn_id)); /* TODO shexp */
982 }while(ngnp != NULL);
984 putc('\n', fo);
985 } break;
986 case a_NAG_T_MLIST:
987 assert(fo != NULL); /* xxx no vput yet */
988 #ifdef HAVE_REGEX
989 if((ngp->ng_type & a_NAG_T_REGEX) && (n_poption & n_PO_D_V)){
990 size_t i;
991 struct a_nag_grp_regex *lp, *ngrp;
993 lp = (ngp->ng_type & a_NAG_T_SUBSCRIBE ? a_nag_mlsub_regex
994 : a_nag_mlist_regex);
995 a_NAG_GP_TO_SUBCLASS(ngrp, ngp);
996 for(i = 1; lp != ngrp; lp = lp->ngr_next)
997 ++i;
998 fprintf(fo, "# regex(7): hits %" PRIuZ ", sort %" PRIuZ ".\n ",
999 ngrp->ngr_hits, i);
1000 ++rv;
1002 #endif
1003 fprintf(fo, "wysh %s %s\n",
1004 (ngp->ng_type & a_NAG_T_SUBSCRIBE ? "mlsubscribe" : "mlist"),
1005 n_shexp_quote_cp(ngp->ng_id, TRU1));
1006 break;
1007 case a_NAG_T_SHORTCUT:
1008 assert(fo != NULL); /* xxx no vput yet */
1009 a_NAG_GP_TO_SUBCLASS(cp, ngp);
1010 fprintf(fo, "wysh shortcut %s %s\n",
1011 ngp->ng_id, n_shexp_quote_cp(cp, TRU1));
1012 break;
1013 case a_NAG_T_CHARSETALIAS:
1014 assert(fo != NULL); /* xxx no vput yet */
1015 a_NAG_GP_TO_SUBCLASS(cp, ngp);
1016 fprintf(fo, "charsetalias %s %s\n",
1017 n_shexp_quote_cp(ngp->ng_id, TRU1), n_shexp_quote_cp(cp, TRU1));
1018 break;
1019 case a_NAG_T_FILETYPE:{
1020 struct a_nag_file_type *nftp;
1022 assert(fo != NULL); /* xxx no vput yet */
1023 a_NAG_GP_TO_SUBCLASS(nftp, ngp);
1024 fprintf(fo, "filetype %s %s %s\n",
1025 n_shexp_quote_cp(ngp->ng_id, TRU1),
1026 n_shexp_quote_cp(nftp->nft_load.s, TRU1),
1027 n_shexp_quote_cp(nftp->nft_save.s, TRU1));
1028 } break;
1030 NYD2_LEAVE;
1031 return rv;
1034 static int
1035 a_nag_mlmux(enum a_nag_type nt, char const **argv){
1036 struct a_nag_group *ngp;
1037 char const *ecp;
1038 int rv;
1039 NYD2_ENTER;
1041 rv = 0;
1042 n_UNINIT(ecp, NULL);
1044 if(*argv == NULL)
1045 a_nag_group_print_all(nt, NULL);
1046 else do{
1047 if((ngp = a_nag_group_find(nt, *argv)) != NULL){
1048 if(nt & a_NAG_T_SUBSCRIBE){
1049 if(!(ngp->ng_type & a_NAG_T_SUBSCRIBE)){
1050 a_NAG_MLMUX_LINKOUT(ngp);
1051 ngp->ng_type |= a_NAG_T_SUBSCRIBE;
1052 a_NAG_MLMUX_LINKIN(ngp);
1053 }else{
1054 ecp = N_("Mailing-list already `mlsubscribe'd: %s\n");
1055 goto jerr;
1057 }else{
1058 ecp = N_("Mailing-list already `mlist'ed: %s\n");
1059 goto jerr;
1061 }else if(a_nag_group_fetch(nt, *argv, 0) == NULL){
1062 ecp = N_("Failed to create storage for mailing-list: %s\n");
1063 jerr:
1064 n_err(V_(ecp), n_shexp_quote_cp(*argv, FAL0));
1065 rv = 1;
1067 }while(*++argv != NULL);
1069 NYD2_LEAVE;
1070 return rv;
1073 static int
1074 a_nag_unmlmux(enum a_nag_type nt, char const **argv){
1075 struct a_nag_group *ngp;
1076 int rv;
1077 NYD2_ENTER;
1079 rv = 0;
1081 for(; *argv != NULL; ++argv){
1082 if(nt & a_NAG_T_SUBSCRIBE){
1083 struct a_nag_group_lookup ngl;
1084 bool_t isaster;
1086 if(!(isaster = (**argv == '*')))
1087 ngp = a_nag_group_find(nt, *argv);
1088 else if((ngp = a_nag_group_go_first(nt, &ngl)) == NULL)
1089 continue;
1090 else if(ngp != NULL && !(ngp->ng_type & a_NAG_T_SUBSCRIBE))
1091 goto jaster_entry;
1093 if(ngp != NULL){
1094 jaster_redo:
1095 if(ngp->ng_type & a_NAG_T_SUBSCRIBE){
1096 a_NAG_MLMUX_LINKOUT(ngp);
1097 ngp->ng_type &= ~a_NAG_T_SUBSCRIBE;
1098 a_NAG_MLMUX_LINKIN(ngp);
1100 if(isaster){
1101 jaster_entry:
1102 while((ngp = a_nag_group_go_next(&ngl)) != NULL &&
1103 !(ngp->ng_type & a_NAG_T_SUBSCRIBE))
1105 if(ngp != NULL)
1106 goto jaster_redo;
1108 }else{
1109 n_err(_("Mailing-list not `mlsubscribe'd: %s\n"),
1110 n_shexp_quote_cp(*argv, FAL0));
1111 rv = 1;
1113 continue;
1115 }else if(a_nag_group_del(nt, *argv))
1116 continue;
1117 n_err(_("No such mailing-list: %s\n"), n_shexp_quote_cp(*argv, FAL0));
1118 rv = 1;
1120 NYD2_LEAVE;
1121 return rv;
1124 #ifdef HAVE_REGEX
1125 static void
1126 a_nag_mlmux_linkin(struct a_nag_group *ngp){
1127 struct a_nag_grp_regex **lpp, *ngrp, *lhp;
1128 NYD2_ENTER;
1130 if(ngp->ng_type & a_NAG_T_SUBSCRIBE){
1131 lpp = &a_nag_mlsub_regex;
1132 ++a_nag_mlsub_size;
1133 }else{
1134 lpp = &a_nag_mlist_regex;
1135 ++a_nag_mlist_size;
1138 a_NAG_GP_TO_SUBCLASS(ngrp, ngp);
1140 if((lhp = *lpp) != NULL){
1141 (ngrp->ngr_last = lhp->ngr_last)->ngr_next = ngrp;
1142 (ngrp->ngr_next = lhp)->ngr_last = ngrp;
1143 }else
1144 *lpp = ngrp->ngr_last = ngrp->ngr_next = ngrp;
1145 ngrp->ngr_hits = 0;
1146 NYD2_LEAVE;
1149 static void
1150 a_nag_mlmux_linkout(struct a_nag_group *ngp){
1151 struct a_nag_grp_regex *ngrp, **lpp;
1152 NYD2_ENTER;
1154 a_NAG_GP_TO_SUBCLASS(ngrp, ngp);
1156 if(ngp->ng_type & a_NAG_T_SUBSCRIBE){
1157 lpp = &a_nag_mlsub_regex;
1158 --a_nag_mlsub_size;
1159 a_nag_mlsub_hits -= ngrp->ngr_hits;
1160 }else{
1161 lpp = &a_nag_mlist_regex;
1162 --a_nag_mlist_size;
1163 a_nag_mlist_hits -= ngrp->ngr_hits;
1166 if(ngrp->ngr_next == ngrp)
1167 *lpp = NULL;
1168 else{
1169 (ngrp->ngr_last->ngr_next = ngrp->ngr_next)->ngr_last = ngrp->ngr_last;
1170 if(*lpp == ngrp)
1171 *lpp = ngrp->ngr_next;
1173 NYD2_LEAVE;
1175 #endif /* HAVE_REGEX */
1177 FL struct name *
1178 nalloc(char const *str, enum gfield ntype)
1180 struct n_addrguts ag;
1181 struct str in, out;
1182 struct name *np;
1183 NYD_ENTER;
1184 assert(!(ntype & GFULLEXTRA) || (ntype & GFULL) != 0);
1186 str = n_addrspec_with_guts(&ag, str,
1187 ((ntype & (GFULL | GSKIN | GREF)) != 0), FAL0);
1188 if(str == NULL){
1190 np = NULL; TODO We cannot return NULL,
1191 goto jleave; TODO thus handle failures in here!
1193 str = ag.ag_input;
1196 if (!(ag.ag_n_flags & NAME_NAME_SALLOC)) {
1197 ag.ag_n_flags |= NAME_NAME_SALLOC;
1198 np = n_autorec_alloc(sizeof(*np) + ag.ag_slen +1);
1199 memcpy(np + 1, ag.ag_skinned, ag.ag_slen +1);
1200 ag.ag_skinned = (char*)(np + 1);
1201 } else
1202 np = n_autorec_alloc(sizeof *np);
1204 np->n_flink = NULL;
1205 np->n_blink = NULL;
1206 np->n_type = ntype;
1207 np->n_flags = 0;
1209 np->n_fullname = np->n_name = ag.ag_skinned;
1210 np->n_fullextra = NULL;
1211 np->n_flags = ag.ag_n_flags;
1213 if (ntype & GFULL) {
1214 if (ag.ag_ilen == ag.ag_slen
1215 #ifdef HAVE_IDNA
1216 && !(ag.ag_n_flags & NAME_IDNA)
1217 #endif
1219 goto jleave;
1220 if (ag.ag_n_flags & NAME_ADDRSPEC_ISFILEORPIPE)
1221 goto jleave;
1223 /* n_fullextra is only the complete name part without address.
1224 * Beware of "-r '<abc@def>'", don't treat that as FULLEXTRA */
1225 if ((ntype & GFULLEXTRA) && ag.ag_ilen > ag.ag_slen + 2) {
1226 size_t s = ag.ag_iaddr_start, e = ag.ag_iaddr_aend, i;
1227 char const *cp;
1229 if (s == 0 || str[--s] != '<' || str[e++] != '>')
1230 goto jskipfullextra;
1231 i = ag.ag_ilen - e;
1232 in.s = n_lofi_alloc(s + 1 + i +1);
1233 while(s > 0 && blankchar(str[s - 1]))
1234 --s;
1235 memcpy(in.s, str, s);
1236 if (i > 0) {
1237 in.s[s++] = ' ';
1238 while (blankchar(str[e])) {
1239 ++e;
1240 if (--i == 0)
1241 break;
1243 if (i > 0)
1244 memcpy(&in.s[s], &str[e], i);
1246 s += i;
1247 in.s[in.l = s] = '\0';
1248 mime_fromhdr(&in, &out, TD_ISPR | TD_ICONV);
1250 for (cp = out.s, i = out.l; i > 0 && spacechar(*cp); --i, ++cp)
1252 while (i > 0 && spacechar(cp[i - 1]))
1253 --i;
1254 np->n_fullextra = savestrbuf(cp, i);
1256 n_lofi_free(in.s);
1257 n_free(out.s);
1259 jskipfullextra:
1261 /* n_fullname depends on IDNA conversion */
1262 #ifdef HAVE_IDNA
1263 if (!(ag.ag_n_flags & NAME_IDNA)) {
1264 #endif
1265 in.s = n_UNCONST(str);
1266 in.l = ag.ag_ilen;
1267 #ifdef HAVE_IDNA
1268 } else {
1269 /* The domain name was IDNA and has been converted. We also have to
1270 * ensure that the domain name in .n_fullname is replaced with the
1271 * converted version, since MIME doesn't perform encoding of addrs */
1272 /* TODO This definetely doesn't belong here! */
1273 size_t l = ag.ag_iaddr_start,
1274 lsuff = ag.ag_ilen - ag.ag_iaddr_aend;
1275 in.s = n_lofi_alloc(l + ag.ag_slen + lsuff +1);
1276 memcpy(in.s, str, l);
1277 memcpy(in.s + l, ag.ag_skinned, ag.ag_slen);
1278 l += ag.ag_slen;
1279 memcpy(in.s + l, str + ag.ag_iaddr_aend, lsuff);
1280 l += lsuff;
1281 in.s[l] = '\0';
1282 in.l = l;
1284 #endif
1285 mime_fromhdr(&in, &out, TD_ISPR | TD_ICONV);
1286 np->n_fullname = savestr(out.s);
1287 n_free(out.s);
1288 #ifdef HAVE_IDNA
1289 if (ag.ag_n_flags & NAME_IDNA)
1290 n_lofi_free(in.s);
1291 #endif
1292 np->n_flags |= NAME_FULLNAME_SALLOC;
1294 jleave:
1295 NYD_LEAVE;
1296 return np;
1299 FL struct name *
1300 ndup(struct name *np, enum gfield ntype)
1302 struct name *nnp;
1303 NYD_ENTER;
1305 if ((ntype & (GFULL | GSKIN)) && !(np->n_flags & NAME_SKINNED)) {
1306 nnp = nalloc(np->n_name, ntype);
1307 goto jleave;
1310 nnp = n_autorec_alloc(sizeof *np);
1311 nnp->n_flink = nnp->n_blink = NULL;
1312 nnp->n_type = ntype;
1313 nnp->n_flags = (np->n_flags & ~(NAME_NAME_SALLOC | NAME_FULLNAME_SALLOC)) |
1314 NAME_NAME_SALLOC;
1315 nnp->n_name = savestr(np->n_name);
1316 if (np->n_name == np->n_fullname || !(ntype & (GFULL | GSKIN))) {
1317 nnp->n_fullname = nnp->n_name;
1318 nnp->n_fullextra = NULL;
1319 } else {
1320 nnp->n_flags |= NAME_FULLNAME_SALLOC;
1321 nnp->n_fullname = savestr(np->n_fullname);
1322 nnp->n_fullextra = (np->n_fullextra == NULL) ? NULL
1323 : savestr(np->n_fullextra);
1325 jleave:
1326 NYD_LEAVE;
1327 return nnp;
1330 FL struct name *
1331 cat(struct name *n1, struct name *n2){
1332 struct name *tail;
1333 NYD2_ENTER;
1335 tail = n2;
1336 if(n1 == NULL)
1337 goto jleave;
1338 tail = n1;
1339 if(n2 == NULL || (n2->n_type & GDEL))
1340 goto jleave;
1342 while(tail->n_flink != NULL)
1343 tail = tail->n_flink;
1344 tail->n_flink = n2;
1345 n2->n_blink = tail;
1346 tail = n1;
1347 jleave:
1348 NYD2_LEAVE;
1349 return tail;
1352 FL struct name *
1353 namelist_dup(struct name const *np, enum gfield ntype){
1354 struct name *nlist, *xnp;
1355 NYD2_ENTER;
1357 for(nlist = xnp = NULL; np != NULL; np = np->n_flink){
1358 struct name *x;
1360 if(!(np->n_type & GDEL)){
1361 x = ndup(n_UNCONST(np), (np->n_type & ~GMASK) | ntype);
1362 if((x->n_blink = xnp) == NULL)
1363 nlist = x;
1364 else
1365 xnp->n_flink = x;
1366 xnp = x;
1369 NYD2_LEAVE;
1370 return nlist;
1373 FL ui32_t
1374 count(struct name const *np)
1376 ui32_t c;
1377 NYD_ENTER;
1379 for (c = 0; np != NULL; np = np->n_flink)
1380 if (!(np->n_type & GDEL))
1381 ++c;
1382 NYD_LEAVE;
1383 return c;
1386 FL ui32_t
1387 count_nonlocal(struct name const *np)
1389 ui32_t c;
1390 NYD_ENTER;
1392 for (c = 0; np != NULL; np = np->n_flink)
1393 if (!(np->n_type & GDEL) && !(np->n_flags & NAME_ADDRSPEC_ISFILEORPIPE))
1394 ++c;
1395 NYD_LEAVE;
1396 return c;
1399 FL struct name *
1400 extract(char const *line, enum gfield ntype)
1402 struct name *rv;
1403 NYD_ENTER;
1405 rv = a_nag_extract1(line, ntype, " \t,", 0);
1406 NYD_LEAVE;
1407 return rv;
1410 FL struct name *
1411 lextract(char const *line, enum gfield ntype)
1413 struct name *rv;
1414 NYD_ENTER;
1416 rv = ((line != NULL && strpbrk(line, ",\"\\(<|"))
1417 ? a_nag_extract1(line, ntype, ",", 1) : extract(line, ntype));
1418 NYD_LEAVE;
1419 return rv;
1422 FL char *
1423 detract(struct name *np, enum gfield ntype)
1425 char *topp, *cp;
1426 struct name *p;
1427 int flags, s;
1428 NYD_ENTER;
1430 topp = NULL;
1431 if (np == NULL)
1432 goto jleave;
1434 flags = ntype & (GCOMMA | GNAMEONLY);
1435 ntype &= ~(GCOMMA | GNAMEONLY);
1436 s = 0;
1438 for (p = np; p != NULL; p = p->n_flink) {
1439 if (ntype && (p->n_type & GMASK) != ntype)
1440 continue;
1441 s += strlen(flags & GNAMEONLY ? p->n_name : p->n_fullname) +1;
1442 if (flags & GCOMMA)
1443 ++s;
1445 if (s == 0)
1446 goto jleave;
1448 s += 2;
1449 topp = n_autorec_alloc(s);
1450 cp = topp;
1451 for (p = np; p != NULL; p = p->n_flink) {
1452 if (ntype && (p->n_type & GMASK) != ntype)
1453 continue;
1454 cp = sstpcpy(cp, (flags & GNAMEONLY ? p->n_name : p->n_fullname));
1455 if ((flags & GCOMMA) && p->n_flink != NULL)
1456 *cp++ = ',';
1457 *cp++ = ' ';
1459 *--cp = 0;
1460 if ((flags & GCOMMA) && *--cp == ',')
1461 *cp = 0;
1462 jleave:
1463 NYD_LEAVE;
1464 return topp;
1467 FL struct name *
1468 grab_names(enum n_go_input_flags gif, char const *field, struct name *np,
1469 int comma, enum gfield gflags)
1471 struct name *nq;
1472 NYD_ENTER;
1474 jloop:
1475 np = lextract(n_go_input_cp(gif, field, detract(np, comma)), gflags);
1476 for (nq = np; nq != NULL; nq = nq->n_flink)
1477 if (is_addr_invalid(nq, EACM_NONE))
1478 goto jloop;
1479 NYD_LEAVE;
1480 return np;
1483 FL bool_t
1484 name_is_same_domain(struct name const *n1, struct name const *n2)
1486 char const *d1, *d2;
1487 bool_t rv;
1488 NYD_ENTER;
1490 d1 = strrchr(n1->n_name, '@');
1491 d2 = strrchr(n2->n_name, '@');
1493 rv = (d1 != NULL && d2 != NULL) ? !asccasecmp(++d1, ++d2) : FAL0;
1495 NYD_LEAVE;
1496 return rv;
1499 FL struct name *
1500 checkaddrs(struct name *np, enum expand_addr_check_mode eacm,
1501 si8_t *set_on_error)
1503 struct name *n;
1504 NYD_ENTER;
1506 for (n = np; n != NULL; n = n->n_flink) {
1507 si8_t rv;
1509 if ((rv = is_addr_invalid(n, eacm)) != 0) {
1510 if (set_on_error != NULL)
1511 *set_on_error |= rv; /* don't loose -1! */
1512 else if (eacm & EAF_MAYKEEP) /* TODO HACK! See definition! */
1513 continue;
1514 if (n->n_blink)
1515 n->n_blink->n_flink = n->n_flink;
1516 if (n->n_flink)
1517 n->n_flink->n_blink = n->n_blink;
1518 if (n == np)
1519 np = n->n_flink;
1522 NYD_LEAVE;
1523 return np;
1526 FL struct name *
1527 namelist_vaporise_head(struct header *hp, enum expand_addr_check_mode eacm,
1528 bool_t metoo, si8_t *set_on_error)
1530 /* TODO namelist_vaporise_head() is incredibly expensive and redundant */
1531 struct name *tolist, *np, **npp;
1532 NYD_ENTER;
1534 tolist = cat(hp->h_to, cat(hp->h_cc, hp->h_bcc));
1535 hp->h_to = hp->h_cc = hp->h_bcc = NULL;
1537 tolist = usermap(tolist, metoo);
1538 tolist = n_alternates_remove(tolist, TRU1);
1539 tolist = elide(checkaddrs(tolist, eacm, set_on_error));
1541 for (np = tolist; np != NULL; np = np->n_flink) {
1542 switch (np->n_type & (GDEL | GMASK)) {
1543 case GTO: npp = &hp->h_to; break;
1544 case GCC: npp = &hp->h_cc; break;
1545 case GBCC: npp = &hp->h_bcc; break;
1546 default: continue;
1548 *npp = cat(*npp, ndup(np, np->n_type | GFULL));
1550 NYD_LEAVE;
1551 return tolist;
1554 FL struct name *
1555 usermap(struct name *names, bool_t force_metoo){
1556 struct a_nag_group *ngp;
1557 struct name *nlist, *nlist_tail, *np, *cp;
1558 int metoo;
1559 NYD_ENTER;
1561 metoo = (force_metoo || ok_blook(metoo));
1562 nlist = nlist_tail = NULL;
1563 np = names;
1565 for(; np != NULL; np = cp){
1566 assert(!(np->n_type & GDEL)); /* TODO legacy */
1567 cp = np->n_flink;
1569 if(is_fileorpipe_addr(np) ||
1570 (ngp = a_nag_group_find(a_NAG_T_ALIAS, np->n_name)) == NULL){
1571 if((np->n_blink = nlist_tail) != NULL)
1572 nlist_tail->n_flink = np;
1573 else
1574 nlist = np;
1575 nlist_tail = np;
1576 np->n_flink = NULL;
1577 }else{
1578 nlist = a_nag_gexpand(0, nlist, ngp, metoo, np->n_type);
1579 if((nlist_tail = nlist) != NULL)
1580 while(nlist_tail->n_flink != NULL)
1581 nlist_tail = nlist_tail->n_flink;
1584 NYD_LEAVE;
1585 return nlist;
1588 FL struct name *
1589 elide(struct name *names)
1591 size_t i, j, k;
1592 struct name *nlist, *np, **nparr;
1593 NYD_ENTER;
1595 nlist = NULL;
1597 if(names == NULL)
1598 goto jleave;
1600 /* Throw away all deleted nodes */
1601 for(np = NULL, i = 0; names != NULL; names = names->n_flink)
1602 if(!(names->n_type & GDEL)){
1603 names->n_blink = np;
1604 if(np != NULL)
1605 np->n_flink = names;
1606 else
1607 nlist = names;
1608 np = names;
1609 ++i;
1611 if(nlist == NULL || i == 1)
1612 goto jleave;
1613 np->n_flink = NULL;
1615 /* Create a temporay array and sort that */
1616 nparr = n_lofi_alloc(sizeof(*nparr) * i);
1618 for(i = 0, np = nlist; np != NULL; np = np->n_flink)
1619 nparr[i++] = np;
1621 qsort(nparr, i, sizeof *nparr, &a_nag_elide_qsort);
1623 /* Remove duplicates XXX speedup, or list_uniq()! */
1624 for(j = 0, --i; j < i;){
1625 if(asccasecmp(nparr[j]->n_name, nparr[k = j + 1]->n_name))
1626 ++j;
1627 else{
1628 for(; k < i; ++k)
1629 nparr[k] = nparr[k + 1];
1630 --i;
1634 /* Throw away all list members which are not part of the array.
1635 * Note this keeps the original, possibly carefully crafted, order of the
1636 * addressees, thus */
1637 for(np = nlist; np != NULL; np = np->n_flink){
1638 for(j = 0; j <= i; ++j)
1639 if(np == nparr[j]){
1640 nparr[j] = NULL;
1641 goto jiter;
1643 /* Drop it */
1644 if(np == nlist){
1645 nlist = np->n_flink;
1646 np->n_blink = NULL;
1647 }else
1648 np->n_blink->n_flink = np->n_flink;
1649 if(np->n_flink != NULL)
1650 np->n_flink->n_blink = np->n_blink;
1651 jiter:;
1654 n_lofi_free(nparr);
1655 jleave:
1656 NYD_LEAVE;
1657 return nlist;
1660 FL int
1661 c_alternates(void *vp){
1662 struct a_nag_group *ngp;
1663 char const *varname, *ccp;
1664 char **argv;
1665 NYD_ENTER;
1667 n_pstate_err_no = n_ERR_NONE;
1669 argv = vp;
1670 varname = (n_pstate & n_PS_ARGMOD_VPUT) ? *argv++ : NULL;
1672 if(*argv == NULL){
1673 if(!a_nag_group_print_all(a_NAG_T_ALTERNATES, varname))
1674 vp = NULL;
1675 }else{
1676 if(varname != NULL)
1677 n_err(_("`alternates': `vput' only supported for show mode\n"));
1679 /* Delete the old set to "declare a list", if *posix* */
1680 if(ok_blook(posix))
1681 a_nag_group_del(a_NAG_T_ALTERNATES, n_star);
1683 while((ccp = *argv++) != NULL){
1684 size_t l;
1685 struct name *np;
1687 if((np = lextract(ccp, GSKIN)) == NULL || np->n_flink != NULL ||
1688 (np = checkaddrs(np, EACM_STRICT, NULL)) == NULL){
1689 n_err(_("Invalid `alternates' argument: %s\n"),
1690 n_shexp_quote_cp(ccp, FAL0));
1691 n_pstate_err_no = n_ERR_INVAL;
1692 vp = NULL;
1693 continue;
1695 ccp = np->n_name;
1697 l = strlen(ccp) +1;
1698 if((ngp = a_nag_group_fetch(a_NAG_T_ALTERNATES, ccp, l)) == NULL){
1699 n_err(_("Failed to create storage for alternates: %s\n"),
1700 n_shexp_quote_cp(ccp, FAL0));
1701 n_pstate_err_no = n_ERR_NOMEM;
1702 vp = NULL;
1706 NYD_LEAVE;
1707 return (vp != NULL ? 0 : 1);
1710 FL int
1711 c_unalternates(void *vp){
1712 char **argv;
1713 int rv;
1714 NYD_ENTER;
1716 rv = 0;
1717 argv = vp;
1719 do if(!a_nag_group_del(a_NAG_T_ALTERNATES, *argv)){
1720 n_err(_("No such `alternates': %s\n"), n_shexp_quote_cp(*argv, FAL0));
1721 rv = 1;
1722 }while(*++argv != NULL);
1723 NYD_LEAVE;
1724 return rv;
1727 FL struct name *
1728 n_alternates_remove(struct name *np, bool_t keep_single){
1729 /* XXX keep a single pointer, initial null, and immediate remove nodes
1730 * XXX on successful match unless keep single and that pointer null! */
1731 struct a_nag_group_lookup ngl;
1732 struct a_nag_group *ngp;
1733 struct name *xp, *newnp;
1734 NYD_ENTER;
1736 /* Delete the temporary bit from all */
1737 for(xp = np; xp != NULL; xp = xp->n_flink)
1738 xp->n_flags &= ~(ui32_t)SI32_MIN;
1740 /* Mark all possible alternate names (xxx sic: instead walk over namelist
1741 * and hash-lookup alternate instead (unless *allnet*) */
1742 for(ngp = a_nag_group_go_first(a_NAG_T_ALTERNATES, &ngl); ngp != NULL;
1743 ngp = a_nag_group_go_next(&ngl))
1744 np = a_nag_namelist_mark_name(np, ngp->ng_id);
1746 np = a_nag_namelist_mark_name(np, ok_vlook(LOGNAME));
1748 for(xp = lextract(ok_vlook(from), GEXTRA | GSKIN); xp != NULL;
1749 xp = xp->n_flink)
1750 np = a_nag_namelist_mark_name(np, xp->n_name);
1752 for(xp = extract(ok_vlook(sender), GEXTRA | GSKIN); xp != NULL;
1753 xp = xp->n_flink)
1754 np = a_nag_namelist_mark_name(np, xp->n_name);
1756 for(xp = lextract(ok_vlook(replyto), GEXTRA | GSKIN); xp != NULL;
1757 xp = xp->n_flink)
1758 np = a_nag_namelist_mark_name(np, xp->n_name);
1760 /* Clean the list by throwing away all deleted or marked (but one) nodes */
1761 for(xp = newnp = NULL; np != NULL; np = np->n_flink){
1762 if(np->n_type & GDEL)
1763 continue;
1764 if(np->n_flags & (ui32_t)SI32_MIN){
1765 if(!keep_single)
1766 continue;
1767 keep_single = FAL0;
1770 np->n_blink = xp;
1771 if(xp != NULL)
1772 xp->n_flink = np;
1773 else
1774 newnp = np;
1775 xp = np;
1776 xp->n_flags &= ~(ui32_t)SI32_MIN;
1778 if(xp != NULL)
1779 xp->n_flink = NULL;
1780 np = newnp;
1782 NYD_LEAVE;
1783 return np;
1786 FL bool_t
1787 n_is_myname(char const *name){
1788 struct a_nag_group_lookup ngl;
1789 struct a_nag_group *ngp;
1790 struct name *xp;
1791 NYD_ENTER;
1793 if(a_nag_is_same_name(ok_vlook(LOGNAME), name))
1794 goto jleave;
1796 if(!ok_blook(allnet)){
1797 if(a_nag_group_lookup(a_NAG_T_ALTERNATES, &ngl, name) != NULL)
1798 goto jleave;
1799 }else{
1800 for(ngp = a_nag_group_go_first(a_NAG_T_ALTERNATES, &ngl); ngp != NULL;
1801 ngp = a_nag_group_go_next(&ngl))
1802 if(a_nag_is_same_name(ngp->ng_id, name))
1803 goto jleave;
1806 for(xp = lextract(ok_vlook(from), GEXTRA | GSKIN); xp != NULL;
1807 xp = xp->n_flink)
1808 if(a_nag_is_same_name(xp->n_name, name))
1809 goto jleave;
1811 for(xp = lextract(ok_vlook(replyto), GEXTRA | GSKIN); xp != NULL;
1812 xp = xp->n_flink)
1813 if(a_nag_is_same_name(xp->n_name, name))
1814 goto jleave;
1816 for(xp = extract(ok_vlook(sender), GEXTRA | GSKIN); xp != NULL;
1817 xp = xp->n_flink)
1818 if(a_nag_is_same_name(xp->n_name, name))
1819 goto jleave;
1821 name = NULL;
1822 jleave:
1823 NYD_LEAVE;
1824 return (name != NULL);
1827 FL int
1828 c_addrcodec(void *vp){
1829 struct n_addrguts ag;
1830 struct n_string s_b, *sp;
1831 size_t alen;
1832 int mode;
1833 char const **argv, *varname, *act, *cp;
1834 NYD_ENTER;
1836 sp = n_string_creat_auto(&s_b);
1837 argv = vp;
1838 varname = (n_pstate & n_PS_ARGMOD_VPUT) ? *argv++ : NULL;
1840 act = *argv;
1841 for(cp = act; *cp != '\0' && !blankspacechar(*cp); ++cp)
1843 mode = 0;
1844 if(*act == '+')
1845 mode = 1, ++act;
1846 if(*act == '+')
1847 mode = 2, ++act;
1848 if(*act == '+')
1849 mode = 3, ++act;
1850 if(act >= cp)
1851 goto jesynopsis;
1852 alen = PTR2SIZE(cp - act);
1853 if(*cp != '\0')
1854 ++cp;
1856 /* C99 */{
1857 size_t i;
1859 i = strlen(cp);
1860 if(i <= UIZ_MAX / 4)
1861 i <<= 1;
1862 sp = n_string_reserve(sp, i);
1865 n_pstate_err_no = n_ERR_NONE;
1867 if(is_ascncaseprefix(act, "encode", alen)){
1868 /* This function cannot be a simple nalloc() wrapper even later on, since
1869 * we may need to turn any ", () or \ into quoted-pairs */
1870 char c;
1872 while((c = *cp++) != '\0'){
1873 if(((c == '(' || c == ')') && mode < 1) || (c == '"' && mode < 2) ||
1874 (c == '\\' && mode < 3))
1875 sp = n_string_push_c(sp, '\\');
1876 sp = n_string_push_c(sp, c);
1879 if(n_addrspec_with_guts(&ag, n_string_cp(sp), TRU1, TRU1) == NULL ||
1880 (ag.ag_n_flags & (NAME_ADDRSPEC_ISADDR | NAME_ADDRSPEC_INVALID)
1881 ) != NAME_ADDRSPEC_ISADDR){
1882 cp = sp->s_dat;
1883 n_pstate_err_no = n_ERR_INVAL;
1884 vp = NULL;
1885 }else{
1886 struct name *np;
1888 np = nalloc(ag.ag_input, GTO | GFULL | GSKIN);
1889 cp = np->n_fullname;
1891 }else if(mode == 0){
1892 if(is_ascncaseprefix(act, "decode", alen)){
1893 char c;
1895 while((c = *cp++) != '\0'){
1896 switch(c){
1897 case '(':
1898 sp = n_string_push_c(sp, '(');
1899 act = skip_comment(cp);
1900 if(--act > cp)
1901 sp = n_string_push_buf(sp, cp, PTR2SIZE(act - cp));
1902 sp = n_string_push_c(sp, ')');
1903 cp = ++act;
1904 break;
1905 case '"':
1906 while(*cp != '\0'){
1907 if((c = *cp++) == '"')
1908 break;
1909 if(c == '\\' && (c = *cp) != '\0')
1910 ++cp;
1911 sp = n_string_push_c(sp, c);
1913 break;
1914 default:
1915 if(c == '\\' && (c = *cp++) == '\0')
1916 break;
1917 sp = n_string_push_c(sp, c);
1918 break;
1921 cp = n_string_cp(sp);
1922 }else if(is_ascncaseprefix(act, "skin", alen)){
1923 /* Let's just use the is-single-address hack for this one, too.. */
1924 if(n_addrspec_with_guts(&ag, cp, TRU1, TRU1) == NULL ||
1925 (ag.ag_n_flags & (NAME_ADDRSPEC_ISADDR | NAME_ADDRSPEC_INVALID)
1926 ) != NAME_ADDRSPEC_ISADDR){
1927 n_pstate_err_no = n_ERR_INVAL;
1928 vp = NULL;
1929 }else{
1930 struct name *np;
1932 np = nalloc(ag.ag_input, GTO | GFULL | GSKIN);
1933 cp = np->n_name;
1935 }else
1936 goto jesynopsis;
1937 }else
1938 goto jesynopsis;
1940 if(varname == NULL){
1941 if(fprintf(n_stdout, "%s\n", cp) < 0){
1942 n_pstate_err_no = n_err_no;
1943 vp = NULL;
1945 }else if(!n_var_vset(varname, (uintptr_t)cp)){
1946 n_pstate_err_no = n_ERR_NOTSUP;
1947 vp = NULL;
1950 jleave:
1951 NYD_LEAVE;
1952 return (vp != NULL ? 0 : 1);
1953 jesynopsis:
1954 n_err(_("Synopsis: addrcodec: <[+[+[+]]]e[ncode]|d[ecode]|s[kin]> "
1955 "<rest-of-line>\n"));
1956 n_pstate_err_no = n_ERR_INVAL;
1957 vp = NULL;
1958 goto jleave;
1961 FL int
1962 c_commandalias(void *vp){
1963 struct a_nag_group *ngp;
1964 char const **argv, *ccp;
1965 int rv;
1966 NYD_ENTER;
1968 rv = 0;
1969 argv = vp;
1971 if((ccp = *argv) == NULL){
1972 a_nag_group_print_all(a_NAG_T_COMMANDALIAS, NULL);
1973 goto jleave;
1976 /* Verify the name is a valid one, and not a command modifier */
1977 if(*ccp == '\0' || *n_cmd_isolate(ccp) != '\0' ||
1978 !asccasecmp(ccp, "ignerr") || !asccasecmp(ccp, "wysh") ||
1979 !asccasecmp(ccp, "u") || !asccasecmp(ccp, "vput")){
1980 n_err(_("`commandalias': not a valid command name: %s\n"),
1981 n_shexp_quote_cp(ccp, FAL0));
1982 rv = 1;
1983 goto jleave;
1986 if(argv[1] == NULL){
1987 if((ngp = a_nag_group_find(a_NAG_T_COMMANDALIAS, ccp)) != NULL)
1988 a_nag_group_print(ngp, n_stdout, NULL);
1989 else{
1990 n_err(_("No such commandalias: %s\n"), n_shexp_quote_cp(ccp, FAL0));
1991 rv = 1;
1993 }else{
1994 /* Because one hardly ever redefines, anything is stored in one chunk */
1995 char *cp;
1996 size_t i, len;
1998 /* Delete the old one, if any; don't get fooled to remove them all */
1999 if(ccp[0] != '*' || ccp[1] != '\0')
2000 a_nag_group_del(a_NAG_T_COMMANDALIAS, ccp);
2002 for(i = len = 0, ++argv; argv[i] != NULL; ++i)
2003 len += strlen(argv[i]) + 1;
2004 if(len == 0)
2005 len = 1;
2007 if((ngp = a_nag_group_fetch(a_NAG_T_COMMANDALIAS, ccp, len)) == NULL){
2008 n_err(_("Failed to create storage for commandalias: %s\n"),
2009 n_shexp_quote_cp(ccp, FAL0));
2010 rv = 1;
2011 }else{
2012 struct a_nag_cmd_alias *ncap;
2014 a_NAG_GP_TO_SUBCLASS(ncap, ngp);
2015 a_NAG_GP_TO_SUBCLASS(cp, ngp);
2016 cp += sizeof *ncap;
2017 ncap->nca_expand.s = cp;
2018 ncap->nca_expand.l = len - 1;
2020 for(len = 0; (ccp = *argv++) != NULL;)
2021 if((i = strlen(ccp)) > 0){
2022 if(len++ != 0)
2023 *cp++ = ' ';
2024 memcpy(cp, ccp, i);
2025 cp += i;
2027 *cp = '\0';
2030 jleave:
2031 NYD_LEAVE;
2032 return rv;
2035 FL int
2036 c_uncommandalias(void *vp){
2037 char **argv;
2038 int rv;
2039 NYD_ENTER;
2041 rv = 0;
2042 argv = vp;
2044 do if(!a_nag_group_del(a_NAG_T_COMMANDALIAS, *argv)){
2045 n_err(_("No such `commandalias': %s\n"), n_shexp_quote_cp(*argv, FAL0));
2046 rv = 1;
2047 }while(*++argv != NULL);
2048 NYD_LEAVE;
2049 return rv;
2052 FL char const *
2053 n_commandalias_exists(char const *name, struct str const **expansion_or_null){
2054 struct a_nag_group *ngp;
2055 NYD_ENTER;
2057 if((ngp = a_nag_group_find(a_NAG_T_COMMANDALIAS, name)) != NULL){
2058 name = ngp->ng_id;
2060 if(expansion_or_null != NULL){
2061 struct a_nag_cmd_alias *ncap;
2063 a_NAG_GP_TO_SUBCLASS(ncap, ngp);
2064 *expansion_or_null = &ncap->nca_expand;
2066 }else
2067 name = NULL;
2068 NYD_LEAVE;
2069 return name;
2072 FL bool_t
2073 n_alias_is_valid_name(char const *name){
2074 char c;
2075 char const *cp;
2076 bool_t rv;
2077 NYD2_ENTER;
2079 for(rv = TRU1, cp = name++; (c = *cp++) != '\0';)
2080 /* User names, plus things explicitly mentioned in Postfix aliases(5).
2081 * As an extension, allow period: [[:alnum:]_#:@.-]+$? */
2082 if(!alnumchar(c) && c != '_' && c != '-' &&
2083 c != '#' && c != ':' && c != '@' &&
2084 c != '.'){
2085 if(c == '$' && cp != name && *cp == '\0')
2086 break;
2087 rv = FAL0;
2088 break;
2090 NYD2_LEAVE;
2091 return rv;
2094 FL int
2095 c_alias(void *v)
2097 char const *ecp;
2098 char **argv;
2099 struct a_nag_group *ngp;
2100 int rv;
2101 NYD_ENTER;
2103 rv = 0;
2104 argv = v;
2105 n_UNINIT(ecp, NULL);
2107 if(*argv == NULL)
2108 a_nag_group_print_all(a_NAG_T_ALIAS, NULL);
2109 else if(!n_alias_is_valid_name(*argv)){
2110 ecp = N_("Not a valid alias name: %s\n");
2111 goto jerr;
2112 }else if(argv[1] == NULL){
2113 if((ngp = a_nag_group_find(a_NAG_T_ALIAS, *argv)) != NULL)
2114 a_nag_group_print(ngp, n_stdout, NULL);
2115 else{
2116 ecp = N_("No such alias: %s\n");
2117 goto jerr;
2119 }else if((ngp = a_nag_group_fetch(a_NAG_T_ALIAS, *argv, 0)) == NULL){
2120 ecp = N_("Failed to create alias storage for: %s\n");
2121 jerr:
2122 n_err(V_(ecp), n_shexp_quote_cp(*argv, FAL0));
2123 rv = 1;
2124 }else{
2125 struct a_nag_grp_names *ngnp_tail, *ngnp;
2126 struct a_nag_grp_names_head *ngnhp;
2128 a_NAG_GP_TO_SUBCLASS(ngnhp, ngp);
2130 if((ngnp_tail = ngnhp->ngnh_head) != NULL)
2131 while((ngnp = ngnp_tail->ngn_next) != NULL)
2132 ngnp_tail = ngnp;
2134 for(++argv; *argv != NULL; ++argv){
2135 size_t i;
2137 i = strlen(*argv) +1;
2138 ngnp = n_alloc(n_VSTRUCT_SIZEOF(struct a_nag_grp_names, ngn_id) + i);
2139 if(ngnp_tail != NULL)
2140 ngnp_tail->ngn_next = ngnp;
2141 else
2142 ngnhp->ngnh_head = ngnp;
2143 ngnp_tail = ngnp;
2144 ngnp->ngn_next = NULL;
2145 memcpy(ngnp->ngn_id, *argv, i);
2148 NYD_LEAVE;
2149 return rv;
2152 FL int
2153 c_unalias(void *v){
2154 char **argv;
2155 int rv;
2156 NYD_ENTER;
2158 rv = 0;
2159 argv = v;
2161 do if(!a_nag_group_del(a_NAG_T_ALIAS, *argv)){
2162 n_err(_("No such alias: %s\n"), *argv);
2163 rv = 1;
2164 }while(*++argv != NULL);
2165 NYD_LEAVE;
2166 return rv;
2169 FL int
2170 c_mlist(void *v){
2171 int rv;
2172 NYD_ENTER;
2174 rv = a_nag_mlmux(a_NAG_T_MLIST, v);
2175 NYD_LEAVE;
2176 return rv;
2179 FL int
2180 c_unmlist(void *v){
2181 int rv;
2182 NYD_ENTER;
2184 rv = a_nag_unmlmux(a_NAG_T_MLIST, v);
2185 NYD_LEAVE;
2186 return rv;
2189 FL int
2190 c_mlsubscribe(void *v){
2191 int rv;
2192 NYD_ENTER;
2194 rv = a_nag_mlmux(a_NAG_T_MLIST | a_NAG_T_SUBSCRIBE, v);
2195 NYD_LEAVE;
2196 return rv;
2199 FL int
2200 c_unmlsubscribe(void *v){
2201 int rv;
2202 NYD_ENTER;
2204 rv = a_nag_unmlmux(a_NAG_T_MLIST | a_NAG_T_SUBSCRIBE, v);
2205 NYD_LEAVE;
2206 return rv;
2209 FL enum mlist_state
2210 is_mlist(char const *name, bool_t subscribed_only){
2211 struct a_nag_group *ngp;
2212 #ifdef HAVE_REGEX
2213 struct a_nag_grp_regex **lpp, *ngrp;
2214 bool_t re2;
2215 #endif
2216 enum mlist_state rv;
2217 NYD_ENTER;
2219 ngp = a_nag_group_find(a_NAG_T_MLIST, name);
2220 rv = (ngp != NULL) ? MLIST_KNOWN : MLIST_OTHER;
2222 if(rv == MLIST_KNOWN){
2223 if(ngp->ng_type & a_NAG_T_SUBSCRIBE)
2224 rv = MLIST_SUBSCRIBED;
2225 else if(subscribed_only)
2226 rv = MLIST_OTHER;
2227 /* Of course, if that is a regular expression it doesn't mean a thing */
2228 #ifdef HAVE_REGEX
2229 if(ngp->ng_type & a_NAG_T_REGEX)
2230 rv = MLIST_OTHER;
2231 else
2232 #endif
2233 goto jleave;
2236 /* Not in the hashmap (as something matchable), walk the lists */
2237 #ifdef HAVE_REGEX
2238 re2 = FAL0;
2239 lpp = &a_nag_mlsub_regex;
2241 jregex_redo:
2242 if((ngrp = *lpp) != NULL){
2243 do if(regexec(&ngrp->ngr_regex, name, 0,NULL, 0) != REG_NOMATCH){
2244 /* Relink as the head of this list if the hit count of this group is
2245 * >= 25% of the average hit count */
2246 size_t i;
2248 if(!re2)
2249 i = ++a_nag_mlsub_hits / a_nag_mlsub_size;
2250 else
2251 i = ++a_nag_mlist_hits / a_nag_mlist_size;
2252 i >>= 2;
2254 if(++ngrp->ngr_hits >= i && *lpp != ngrp && ngrp->ngr_next != ngrp){
2255 ngrp->ngr_last->ngr_next = ngrp->ngr_next;
2256 ngrp->ngr_next->ngr_last = ngrp->ngr_last;
2257 (ngrp->ngr_last = (*lpp)->ngr_last)->ngr_next = ngrp;
2258 (ngrp->ngr_next = *lpp)->ngr_last = ngrp;
2259 *lpp = ngrp;
2261 rv = !re2 ? MLIST_SUBSCRIBED : MLIST_KNOWN;
2262 goto jleave;
2263 }while((ngrp = ngrp->ngr_next) != *lpp);
2266 if(!re2 && !subscribed_only){
2267 re2 = TRU1;
2268 lpp = &a_nag_mlist_regex;
2269 goto jregex_redo;
2271 assert(rv == MLIST_OTHER);
2272 #endif /* HAVE_REGEX */
2274 jleave:
2275 NYD_LEAVE;
2276 return rv;
2279 FL int
2280 c_shortcut(void *vp){
2281 struct a_nag_group *ngp;
2282 char **argv;
2283 int rv;
2284 NYD_ENTER;
2286 rv = 0;
2287 argv = vp;
2289 if(*argv == NULL)
2290 a_nag_group_print_all(a_NAG_T_SHORTCUT, NULL);
2291 else if(argv[1] == NULL){
2292 if((ngp = a_nag_group_find(a_NAG_T_SHORTCUT, *argv)) != NULL)
2293 a_nag_group_print(ngp, n_stdout, NULL);
2294 else{
2295 n_err(_("No such shortcut: %s\n"), n_shexp_quote_cp(*argv, FAL0));
2296 rv = 1;
2298 }else for(; *argv != NULL; argv += 2){
2299 /* Because one hardly ever redefines, anything is stored in one chunk */
2300 size_t l;
2301 char *cp;
2303 if(argv[1] == NULL){
2304 n_err(_("Synopsis: shortcut: <shortcut> <expansion>\n"));
2305 rv = 1;
2306 break;
2308 if(a_nag_group_find(a_NAG_T_SHORTCUT, *argv) != NULL)
2309 a_nag_group_del(a_NAG_T_SHORTCUT, *argv);
2311 l = strlen(argv[1]) +1;
2312 if((ngp = a_nag_group_fetch(a_NAG_T_SHORTCUT, *argv, l)) == NULL){
2313 n_err(_("Failed to create storage for shortcut: %s\n"),
2314 n_shexp_quote_cp(*argv, FAL0));
2315 rv = 1;
2316 }else{
2317 a_NAG_GP_TO_SUBCLASS(cp, ngp);
2318 memcpy(cp, argv[1], l);
2321 NYD_LEAVE;
2322 return rv;
2325 FL int
2326 c_unshortcut(void *vp){
2327 char **argv;
2328 int rv;
2329 NYD_ENTER;
2331 rv = 0;
2332 argv = vp;
2334 do if(!a_nag_group_del(a_NAG_T_SHORTCUT, *argv)){
2335 n_err(_("No such shortcut: %s\n"), *argv);
2336 rv = 1;
2337 }while(*++argv != NULL);
2338 NYD_LEAVE;
2339 return rv;
2342 FL char const *
2343 shortcut_expand(char const *str){
2344 struct a_nag_group *ngp;
2345 NYD_ENTER;
2347 if((ngp = a_nag_group_find(a_NAG_T_SHORTCUT, str)) != NULL)
2348 a_NAG_GP_TO_SUBCLASS(str, ngp);
2349 else
2350 str = NULL;
2351 NYD_LEAVE;
2352 return str;
2355 FL int
2356 c_charsetalias(void *vp){
2357 struct a_nag_group *ngp;
2358 char **argv;
2359 int rv;
2360 NYD_ENTER;
2362 rv = 0;
2363 argv = vp;
2365 if(*argv == NULL)
2366 a_nag_group_print_all(a_NAG_T_CHARSETALIAS, NULL);
2367 else if(argv[1] == NULL){
2368 if((ngp = a_nag_group_find(a_NAG_T_CHARSETALIAS, *argv)) != NULL)
2369 a_nag_group_print(ngp, n_stdout, NULL);
2370 else{
2371 n_err(_("No such charsetalias: %s\n"), n_shexp_quote_cp(*argv, FAL0));
2372 rv = 1;
2374 }else for(; *argv != NULL; argv += 2){
2375 /* Because one hardly ever redefines, anything is stored in one chunk */
2376 char const *ccp;
2377 char *cp, c;
2378 size_t l;
2380 if(argv[1] == NULL){
2381 n_err(_("Synopsis: charsetalias: <charset> <charset-alias>\n"));
2382 rv = 1;
2383 break;
2386 /* Delete the old one, if any; don't get fooled to remove them all */
2387 ccp = argv[0];
2388 if(ccp[0] != '*' || ccp[1] != '\0')
2389 a_nag_group_del(a_NAG_T_CHARSETALIAS, ccp);
2391 l = strlen(argv[1]) +1;
2392 if((ngp = a_nag_group_fetch(a_NAG_T_CHARSETALIAS, ccp, l)) == NULL){
2393 n_err(_("Failed to create storage for charsetalias: %s\n"),
2394 n_shexp_quote_cp(ccp, FAL0));
2395 rv = 1;
2396 }else{
2397 a_NAG_GP_TO_SUBCLASS(cp, ngp);
2399 for(ccp = argv[1]; (c = *ccp++) != '\0';)
2400 *cp++ = lowerconv(c);
2401 *cp = '\0';
2404 NYD_LEAVE;
2405 return rv;
2408 FL int
2409 c_uncharsetalias(void *vp){
2410 char **argv;
2411 int rv;
2412 NYD_ENTER;
2414 rv = 0;
2415 argv = vp;
2417 do if(!a_nag_group_del(a_NAG_T_CHARSETALIAS, *argv)){
2418 n_err(_("No such `charsetalias': %s\n"), n_shexp_quote_cp(*argv, FAL0));
2419 rv = 1;
2420 }while(*++argv != NULL);
2421 NYD_LEAVE;
2422 return rv;
2425 FL char const *
2426 n_charsetalias_expand(char const *cp){
2427 struct a_nag_group *ngp;
2428 size_t i;
2429 char const *cp_orig;
2430 NYD_ENTER;
2432 cp_orig = cp;
2434 for(i = 0; (ngp = a_nag_group_find(a_NAG_T_CHARSETALIAS, cp)) != NULL;){
2435 a_NAG_GP_TO_SUBCLASS(cp, ngp);
2436 if(++i == 8) /* XXX Magic (same as for `ghost' expansion) */
2437 break;
2440 if(cp != cp_orig)
2441 cp = savestr(cp);
2442 NYD_LEAVE;
2443 return cp;
2446 FL int
2447 c_filetype(void *vp){ /* TODO support automatic chains: .tar.gz -> .gz + .tar */
2448 struct a_nag_group *ngp;
2449 char **argv; /* TODO While there: let ! prefix mean: direct execlp(2) */
2450 int rv;
2451 NYD_ENTER;
2453 rv = 0;
2454 argv = vp;
2456 if(*argv == NULL)
2457 a_nag_group_print_all(a_NAG_T_FILETYPE, NULL);
2458 else if(argv[1] == NULL){
2459 if((ngp = a_nag_group_find(a_NAG_T_FILETYPE, *argv)) != NULL)
2460 a_nag_group_print(ngp, n_stdout, NULL);
2461 else{
2462 n_err(_("No such filetype: %s\n"), n_shexp_quote_cp(*argv, FAL0));
2463 rv = 1;
2465 }else for(; *argv != NULL; argv += 3){
2466 /* Because one hardly ever redefines, anything is stored in one chunk */
2467 char const *ccp;
2468 char *cp, c;
2469 size_t llc, lsc;
2471 if(argv[1] == NULL || argv[2] == NULL){
2472 n_err(_("Synopsis: filetype: <extension> <load-cmd> <save-cmd>\n"));
2473 rv = 1;
2474 break;
2477 /* Delete the old one, if any; don't get fooled to remove them all */
2478 ccp = argv[0];
2479 if(ccp[0] != '*' || ccp[1] != '\0')
2480 a_nag_group_del(a_NAG_T_FILETYPE, ccp);
2482 /* Lowercase it all (for display purposes) */
2483 cp = savestr(ccp);
2484 ccp = cp;
2485 while((c = *cp) != '\0')
2486 *cp++ = lowerconv(c);
2488 llc = strlen(argv[1]) +1;
2489 lsc = strlen(argv[2]) +1;
2490 if(UIZ_MAX - llc <= lsc)
2491 goto jenomem;
2493 if((ngp = a_nag_group_fetch(a_NAG_T_FILETYPE, ccp, llc + lsc)) == NULL){
2494 jenomem:
2495 n_err(_("Failed to create storage for filetype: %s\n"),
2496 n_shexp_quote_cp(argv[0], FAL0));
2497 rv = 1;
2498 }else{
2499 struct a_nag_file_type *nftp;
2501 a_NAG_GP_TO_SUBCLASS(nftp, ngp);
2502 a_NAG_GP_TO_SUBCLASS(cp, ngp);
2503 cp += sizeof *nftp;
2504 memcpy(nftp->nft_load.s = cp, argv[1], llc);
2505 cp += llc;
2506 nftp->nft_load.l = --llc;
2507 memcpy(nftp->nft_save.s = cp, argv[2], lsc);
2508 /*cp += lsc;*/
2509 nftp->nft_save.l = --lsc;
2512 NYD_LEAVE;
2513 return rv;
2516 FL int
2517 c_unfiletype(void *vp){
2518 char **argv;
2519 int rv;
2520 NYD_ENTER;
2522 rv = 0;
2523 argv = vp;
2525 do if(!a_nag_group_del(a_NAG_T_FILETYPE, *argv)){
2526 n_err(_("No such `filetype': %s\n"), n_shexp_quote_cp(*argv, FAL0));
2527 rv = 1;
2528 }while(*++argv != NULL);
2529 NYD_LEAVE;
2530 return rv;
2533 FL bool_t
2534 n_filetype_trial(struct n_file_type *res_or_null, char const *file){
2535 struct stat stb;
2536 struct a_nag_group_lookup ngl;
2537 struct n_string s, *sp;
2538 struct a_nag_group const *ngp;
2539 ui32_t l;
2540 NYD2_ENTER;
2542 sp = n_string_creat_auto(&s);
2543 sp = n_string_assign_cp(sp, file);
2544 sp = n_string_push_c(sp, '.');
2545 l = sp->s_len;
2547 for(ngp = a_nag_group_go_first(a_NAG_T_FILETYPE, &ngl); ngp != NULL;
2548 ngp = a_nag_group_go_next(&ngl)){
2549 sp = n_string_trunc(sp, l);
2550 sp = n_string_push_buf(sp, ngp->ng_id,
2551 ngp->ng_subclass_off - ngp->ng_id_len_sub);
2553 if(!stat(n_string_cp(sp), &stb) && S_ISREG(stb.st_mode)){
2554 if(res_or_null != NULL){
2555 struct a_nag_file_type *nftp;
2557 a_NAG_GP_TO_SUBCLASS(nftp, ngp);
2558 res_or_null->ft_ext_dat = ngp->ng_id;
2559 res_or_null->ft_ext_len = ngp->ng_subclass_off - ngp->ng_id_len_sub;
2560 res_or_null->ft_load_dat = nftp->nft_load.s;
2561 res_or_null->ft_load_len = nftp->nft_load.l;
2562 res_or_null->ft_save_dat = nftp->nft_save.s;
2563 res_or_null->ft_save_len = nftp->nft_save.l;
2565 goto jleave; /* TODO after v15 legacy drop: break; */
2569 /* TODO v15 legacy code: automatic file hooks for .{bz2,gz,xz},
2570 * TODO but NOT supporting *file-hook-{load,save}-EXTENSION* */
2571 ngp = (struct a_nag_group*)0x1;
2573 sp = n_string_trunc(sp, l);
2574 sp = n_string_push_buf(sp, a_nag_OBSOLETE_xz.ft_ext_dat,
2575 a_nag_OBSOLETE_xz.ft_ext_len);
2576 if(!stat(n_string_cp(sp), &stb) && S_ISREG(stb.st_mode)){
2577 n_OBSOLETE(".xz support will vanish, please use the `filetype' command");
2578 if(res_or_null != NULL)
2579 *res_or_null = a_nag_OBSOLETE_xz;
2580 goto jleave;
2583 sp = n_string_trunc(sp, l);
2584 sp = n_string_push_buf(sp, a_nag_OBSOLETE_gz.ft_ext_dat,
2585 a_nag_OBSOLETE_gz.ft_ext_len);
2586 if(!stat(n_string_cp(sp), &stb) && S_ISREG(stb.st_mode)){
2587 n_OBSOLETE(".gz support will vanish, please use the `filetype' command");
2588 if(res_or_null != NULL)
2589 *res_or_null = a_nag_OBSOLETE_gz;
2590 goto jleave;
2593 sp = n_string_trunc(sp, l);
2594 sp = n_string_push_buf(sp, a_nag_OBSOLETE_bz2.ft_ext_dat,
2595 a_nag_OBSOLETE_bz2.ft_ext_len);
2596 if(!stat(n_string_cp(sp), &stb) && S_ISREG(stb.st_mode)){
2597 n_OBSOLETE(".bz2 support will vanish, please use the `filetype' command");
2598 if(res_or_null != NULL)
2599 *res_or_null = a_nag_OBSOLETE_bz2;
2600 goto jleave;
2603 ngp = NULL;
2605 jleave:
2606 NYD2_LEAVE;
2607 return (ngp != NULL);
2610 FL bool_t
2611 n_filetype_exists(struct n_file_type *res_or_null, char const *file){
2612 char const *ext, *lext;
2613 NYD2_ENTER;
2615 if((ext = strrchr(file, '/')) != NULL)
2616 file = ++ext;
2618 for(lext = NULL; (ext = strchr(file, '.')) != NULL; lext = file = ext){
2619 struct a_nag_group const *ngp;
2621 if((ngp = a_nag_group_find(a_NAG_T_FILETYPE, ++ext)) != NULL){
2622 lext = ext;
2623 if(res_or_null != NULL){
2624 struct a_nag_file_type *nftp;
2626 a_NAG_GP_TO_SUBCLASS(nftp, ngp);
2627 res_or_null->ft_ext_dat = ngp->ng_id;
2628 res_or_null->ft_ext_len = ngp->ng_subclass_off - ngp->ng_id_len_sub;
2629 res_or_null->ft_load_dat = nftp->nft_load.s;
2630 res_or_null->ft_load_len = nftp->nft_load.l;
2631 res_or_null->ft_save_dat = nftp->nft_save.s;
2632 res_or_null->ft_save_len = nftp->nft_save.l;
2634 goto jleave; /* TODO after v15 legacy drop: break; */
2638 /* TODO v15 legacy code: automatic file hooks for .{bz2,gz,xz},
2639 * TODO as well as supporting *file-hook-{load,save}-EXTENSION* */
2640 if(lext == NULL)
2641 goto jleave;
2643 if(!asccasecmp(lext, "xz")){
2644 n_OBSOLETE(".xz support will vanish, please use the `filetype' command");
2645 if(res_or_null != NULL)
2646 *res_or_null = a_nag_OBSOLETE_xz;
2647 goto jleave;
2648 }else if(!asccasecmp(lext, "gz")){
2649 n_OBSOLETE(".gz support will vanish, please use the `filetype' command");
2650 if(res_or_null != NULL)
2651 *res_or_null = a_nag_OBSOLETE_gz;
2652 goto jleave;
2653 }else if(!asccasecmp(lext, "bz2")){
2654 n_OBSOLETE(".bz2 support will vanish, please use the `filetype' command");
2655 if(res_or_null != NULL)
2656 *res_or_null = a_nag_OBSOLETE_bz2;
2657 goto jleave;
2658 }else{
2659 char const *cload, *csave;
2660 char *vbuf;
2661 size_t l;
2663 #undef a_X1
2664 #define a_X1 "file-hook-load-"
2665 #undef a_X2
2666 #define a_X2 "file-hook-save-"
2667 l = strlen(lext);
2668 vbuf = n_lofi_alloc(l + n_MAX(sizeof(a_X1), sizeof(a_X2)));
2670 memcpy(vbuf, a_X1, sizeof(a_X1) -1);
2671 memcpy(&vbuf[sizeof(a_X1) -1], lext, l);
2672 vbuf[sizeof(a_X1) -1 + l] = '\0';
2673 cload = n_var_vlook(vbuf, FAL0);
2675 memcpy(vbuf, a_X2, sizeof(a_X2) -1);
2676 memcpy(&vbuf[sizeof(a_X2) -1], lext, l);
2677 vbuf[sizeof(a_X2) -1 + l] = '\0';
2678 csave = n_var_vlook(vbuf, FAL0);
2680 #undef a_X2
2681 #undef a_X1
2682 n_lofi_free(vbuf);
2684 if((csave != NULL) | (cload != NULL)){
2685 n_OBSOLETE("*file-hook-{load,save}-EXTENSION* will vanish, "
2686 "please use the `filetype' command");
2688 if(((csave != NULL) ^ (cload != NULL)) == 0){
2689 if(res_or_null != NULL){
2690 res_or_null->ft_ext_dat = lext;
2691 res_or_null->ft_ext_len = l;
2692 res_or_null->ft_load_dat = cload;
2693 res_or_null->ft_load_len = strlen(cload);
2694 res_or_null->ft_save_dat = csave;
2695 res_or_null->ft_save_len = strlen(csave);
2697 goto jleave;
2698 }else
2699 n_alert(_("Incomplete *file-hook-{load,save}-EXTENSION* for: .%s"),
2700 lext);
2704 lext = NULL;
2706 jleave:
2707 NYD2_LEAVE;
2708 return (lext != NULL);
2711 /* s-it-mode */