Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
att
GitHub Repository: att/ast
Path: blob/master/src/cmd/mailx/local.c
1808 views
1
/***********************************************************************
2
* *
3
* This software is part of the BSD package *
4
*Copyright (c) 1978-2012 The Regents of the University of California an*
5
* *
6
* Redistribution and use in source and binary forms, with or *
7
* without modification, are permitted provided that the following *
8
* conditions are met: *
9
* *
10
* 1. Redistributions of source code must retain the above *
11
* copyright notice, this list of conditions and the *
12
* following disclaimer. *
13
* *
14
* 2. Redistributions in binary form must reproduce the above *
15
* copyright notice, this list of conditions and the *
16
* following disclaimer in the documentation and/or other *
17
* materials provided with the distribution. *
18
* *
19
* 3. Neither the name of The Regents of the University of California*
20
* names of its contributors may be used to endorse or *
21
* promote products derived from this software without *
22
* specific prior written permission. *
23
* *
24
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND *
25
* CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, *
26
* INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF *
27
* MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE *
28
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS *
29
* BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, *
30
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED *
31
* TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, *
32
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON *
33
* ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, *
34
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY *
35
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE *
36
* POSSIBILITY OF SUCH DAMAGE. *
37
* *
38
* Redistribution and use in source and binary forms, with or without *
39
* modification, are permitted provided that the following conditions *
40
* are met: *
41
* 1. Redistributions of source code must retain the above copyright *
42
* notice, this list of conditions and the following disclaimer. *
43
* 2. Redistributions in binary form must reproduce the above copyright *
44
* notice, this list of conditions and the following disclaimer in *
45
* the documentation and/or other materials provided with the *
46
* distribution. *
47
* 3. Neither the name of the University nor the names of its *
48
* contributors may be used to endorse or promote products derived *
49
* from this software without specific prior written permission. *
50
* *
51
* THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS "AS IS" *
52
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED *
53
* TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A *
54
* PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS *
55
* OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, *
56
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT *
57
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF *
58
* USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND *
59
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, *
60
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT *
61
* OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF *
62
* SUCH DAMAGE. *
63
* *
64
* Kurt Shoens (UCB) *
65
* gsf *
66
* *
67
***********************************************************************/
68
#pragma prototyped
69
/*
70
* Mail -- a mail program
71
*
72
* Local installation dependent routines.
73
*/
74
75
#include "mailx.h"
76
77
#include <pwd.h>
78
79
#if _PACKAGE_ast
80
#include <tm.h>
81
#endif
82
83
/*
84
* Locate the user's mailbox file (ie, the place where new, unread
85
* mail is queued).
86
*/
87
char*
88
mailbox(const char* user, const char* mail)
89
{
90
register char* s;
91
register int i;
92
int n = 0;
93
struct stat st;
94
95
static const char* dir[] = {
96
_PATH_MAILDIR,
97
"/var/spool/mail",
98
"/usr/spool/mail",
99
"/usr/mail"
100
};
101
102
if (!user || !*user || !stat(mail, &st) && S_ISREG(st.st_mode))
103
return (char*)mail;
104
if (mail) {
105
if (imap_name(mail))
106
return (char*)mail;
107
if (s = strrchr(mail, '/')) {
108
i = s - (char*)mail;
109
sfprintf(state.path.temp, "%-.*s/.", i, mail);
110
if (!access(struse(state.path.temp), F_OK))
111
n = i;
112
}
113
}
114
if (n == 0) {
115
for (i = 0;; i++) {
116
if (i >= elementsof(dir)) {
117
i = 0;
118
break;
119
}
120
sfprintf(state.path.temp, "%s/.", dir[i]);
121
if (!access(struse(state.path.temp), F_OK))
122
break;
123
}
124
mail = dir[i];
125
if ((n = strlen(mail)) > 0 && mail[n - 1] == '/')
126
n--;
127
}
128
sfprintf(state.path.temp, "%-.*s/%s", n, mail, user);
129
return savestr(struse(state.path.temp));
130
}
131
132
/*
133
* Get rid of the queued mail.
134
*/
135
void
136
demail(void)
137
{
138
if (state.var.keep || rm(state.path.mail) < 0)
139
close(open(state.path.mail, O_WRONLY|O_CREAT|O_TRUNC|O_BINARY|O_cloexec, MAILMODE));
140
}
141
142
/*
143
* Discover user login name.
144
*/
145
char*
146
username(void)
147
{
148
register char* s;
149
register char* t;
150
struct passwd* pw;
151
152
if ((!(s = getenv("USER")) || !*s) &&
153
(!(s = getenv("LOGIN")) || !*s) &&
154
(pw = getpwuid(getuid())))
155
s = pw->pw_name;
156
if (s)
157
{
158
if (t = strrchr(s, '/'))
159
s = t + 1;
160
if (!*s)
161
s = 0;
162
}
163
return s;
164
}
165
166
/*
167
* Convert name to a user id and return it.
168
* Mapping cached in state.userid.
169
* Return -1 on error.
170
*/
171
int
172
userid(char* name)
173
{
174
register struct name* up;
175
register struct passwd* pw;
176
177
up = dictsearch(&state.userid, name, INSERT);
178
if (!up->value)
179
up->flags = (pw = getpwnam(name)) ? pw->pw_uid : -1;
180
return up->flags;
181
}
182
183
/*
184
* Make sure the lock doesn't hang -- NFS anyone?
185
*/
186
static void
187
alarmed(int sig)
188
{
189
signal(sig, SIG_IGN);
190
state.hung = 1;
191
}
192
193
/*
194
* Lock (set!=0) or unlock (set==0) open file fp.
195
*/
196
int
197
filelock(const char* name, FILE* fp, int set)
198
{
199
#if defined(F_SETLKW) && defined(F_WRLCK) && defined(F_UNLCK) || defined(LOCK_EX) && defined(LOCK_UN)
200
int r;
201
sig_t savealrm;
202
203
if (state.var.lock)
204
{
205
state.hung = 0;
206
savealrm = signal(SIGALRM, alarmed);
207
if (!(r = strtol(state.var.lock, NiL, 10)))
208
r = 5;
209
alarm(r);
210
#if defined(F_SETLKW) && defined(F_WRLCK) && defined(F_UNLCK)
211
{
212
struct flock lck;
213
214
lck.l_type = set ? F_WRLCK : F_UNLCK;
215
lck.l_whence = SEEK_SET;
216
lck.l_start = 0;
217
lck.l_len = 0;
218
r = fcntl(fileno(fp), F_SETLKW, &lck);
219
}
220
#else
221
r = flock(fileno(fp), set ? LOCK_EX : LOCK_UN);
222
#endif
223
alarm(0);
224
signal(SIGALRM, savealrm);
225
if (state.hung)
226
note(0, "%s: file lock hung -- continuing without lock", name);
227
else if (r)
228
note(SYSTEM, "%s: continuing without lock", name);
229
}
230
#endif
231
return 0;
232
}
233
234
#if _PACKAGE_ast
235
236
/*
237
* Compute what the screen size for printing headers should be.
238
*/
239
240
void
241
setscreensize(void)
242
{
243
astwinsize(1, &state.realscreenheight, &state.screenwidth);
244
if (!state.realscreenheight)
245
state.realscreenheight = 24;
246
state.screenheight = state.realscreenheight;
247
if (!state.screenwidth)
248
state.screenwidth = 80;
249
}
250
251
/*
252
* check if date is valid with no trailing junk
253
*/
254
255
int
256
isdate(char* s)
257
{
258
char* t;
259
260
(void)tmdate(s, &t, NiL);
261
return *t == 0 && strmatch(s, "*[a-zA-Z]*[0-9]:[0-9]*");
262
}
263
264
#else /*_PACKAGE_ast*/
265
266
#include <termios.h>
267
268
#define CHUNK 32
269
270
char*
271
fmtesc(const char* as)
272
{
273
register unsigned char* s = (unsigned char*)as;
274
register char* b;
275
register int c;
276
277
static char* buf;
278
static int bufsiz;
279
280
c = 4 * (strlen((char*)s) + 1);
281
if (bufsiz < c)
282
{
283
bufsiz = ((c + CHUNK - 1) / CHUNK) * CHUNK;
284
if (!(buf = newof(buf, char, bufsiz, 0)))
285
return 0;
286
}
287
b = buf;
288
while (c = *s++)
289
{
290
if (iscntrl(c) || !isprint(c))
291
{
292
*b++ = '\\';
293
switch (c)
294
{
295
case '\007':
296
c = 'a';
297
break;
298
case '\b':
299
c = 'b';
300
break;
301
case '\f':
302
c = 'f';
303
break;
304
case '\n':
305
c = 'n';
306
break;
307
case '\r':
308
c = 'r';
309
break;
310
case '\t':
311
c = 't';
312
break;
313
case '\v':
314
c = 'v';
315
break;
316
case '\033':
317
c = 'E';
318
break;
319
default:
320
*b++ = '0' + ((c >> 6) & 07);
321
*b++ = '0' + ((c >> 3) & 07);
322
c = '0' + (c & 07);
323
break;
324
}
325
}
326
*b++ = c;
327
}
328
*b = 0;
329
return buf;
330
}
331
332
#define IDENT 01
333
#define USAGE 02
334
335
/*
336
* format what(1) and/or ident(1) string a
337
*/
338
339
char*
340
fmtident(const char* a)
341
{
342
register char* s = (char*)a;
343
register char* t;
344
char* buf;
345
int i;
346
347
i = 0;
348
for (;;)
349
{
350
while (isspace(*s))
351
s++;
352
if (s[0] == '[')
353
{
354
while (*++s && *s != '\n');
355
i |= USAGE;
356
}
357
else if (s[0] == '@' && s[1] == '(' && s[2] == '#' && s[3] == ')')
358
s += 4;
359
else if (s[0] == '$' && s[1] == 'I' && s[2] == 'd' && s[3] == ':' && isspace(s[4]))
360
{
361
s += 5;
362
i |= IDENT;
363
}
364
else
365
break;
366
}
367
if (i)
368
{
369
i &= IDENT;
370
for (t = s; isprint(*t) && *t != '\n'; t++)
371
if (i && t[0] == ' ' && t[1] == '$')
372
break;
373
while (t > s && isspace(t[-1]))
374
t--;
375
i = t - s;
376
if (!(buf = newof(buf, char, i, i)))
377
return s;
378
memcpy(buf, s, i);
379
s = buf;
380
s[i] = 0;
381
}
382
return s;
383
}
384
385
/*
386
* return the current shell path
387
*/
388
389
char*
390
pathshell(void)
391
{
392
char* shell;
393
394
if (!(shell = state.var.shell) || !*shell)
395
shell = _PATH_SHELL;
396
return shell;
397
}
398
399
/*
400
* Compute what the screen size for printing headers should be.
401
* We use the following algorithm for the height:
402
* If baud rate < 1200, use 9
403
* If baud rate = 1200, use 14
404
* If baud rate > 1200, use 24 or ws_row
405
* Width is either 80 or ws_col;
406
*/
407
408
void
409
setscreensize(void)
410
{
411
struct termios tbuf;
412
struct winsize ws;
413
int speed;
414
415
if (ioctl(1, TIOCGWINSZ, (char*)&ws) < 0)
416
ws.ws_col = ws.ws_row = 0;
417
if (tcgetattr(1, &tbuf) < 0)
418
speed = B9600;
419
else
420
speed = cfgetospeed(&tbuf);
421
if (speed < B1200)
422
state.screenheight = 9;
423
else if (speed == B1200)
424
state.screenheight = 14;
425
else if (ws.ws_row)
426
state.screenheight = ws.ws_row;
427
else
428
state.screenheight = 24;
429
if (!(state.realscreenheight = ws.ws_row))
430
state.realscreenheight = 24;
431
if (!(state.screenwidth = ws.ws_col))
432
state.screenwidth = 80;
433
}
434
435
/*
436
* signal critical region support
437
*/
438
439
static const struct
440
{
441
int sig;
442
int op;
443
}
444
signals[] = /* held inside critical region */
445
{
446
SIGINT, SIG_REG_EXEC,
447
#ifdef SIGQUIT
448
SIGQUIT, SIG_REG_EXEC,
449
#endif
450
#ifdef SIGHUP
451
SIGHUP, SIG_REG_EXEC,
452
#endif
453
#ifdef SIGPIPE
454
SIGPIPE, SIG_REG_EXEC,
455
#endif
456
#ifdef SIGCLD
457
SIGCLD, SIG_REG_PROC,
458
#endif
459
#ifdef SIGCHLD
460
SIGCHLD, SIG_REG_PROC,
461
#endif
462
#ifdef SIGTSTP
463
SIGTSTP, SIG_REG_TERM,
464
#endif
465
#ifdef SIGTTIN
466
SIGTTIN, SIG_REG_TERM,
467
#endif
468
#ifdef SIGTTOU
469
SIGTTOU, SIG_REG_TERM,
470
#endif
471
};
472
473
/*
474
* critical signal region handler
475
*
476
* op>0 new region according to SIG_REG_*, return region level
477
* op==0 pop region, return region level
478
* op<0 return non-zero if any signals held in current region
479
*
480
* signals[] held until region popped
481
*/
482
483
int
484
sigcritical(int op)
485
{
486
register int i;
487
sigset_t nmask;
488
489
static int region;
490
static int level;
491
static sigset_t mask;
492
493
if (op > 0)
494
{
495
if (!level++)
496
{
497
region = op;
498
if (op & SIG_REG_SET)
499
level--;
500
sigemptyset(&nmask);
501
for (i = 0; i < elementsof(signals); i++)
502
if (op & signals[i].op)
503
sigaddset(&nmask, signals[i].sig);
504
sigprocmask(SIG_BLOCK, &nmask, &mask);
505
}
506
return level;
507
}
508
else if (op < 0)
509
{
510
sigpending(&nmask);
511
for (i = 0; i < elementsof(signals); i++)
512
if (region & signals[i].op)
513
{
514
if (sigismember(&nmask, signals[i].sig))
515
return 1;
516
}
517
return 0;
518
}
519
else
520
{
521
/*
522
* a vfork() may have intervened so we
523
* allow apparent nesting mismatches
524
*/
525
526
if (--level < 0)
527
{
528
level = 0;
529
sigprocmask(SIG_SETMASK, &mask, NiL);
530
}
531
return level;
532
}
533
}
534
535
/*
536
* remove sig from the set of blocked signals
537
* sig==0 unblocks them all
538
*/
539
540
int
541
sigunblock(int sig)
542
{
543
int op;
544
sigset_t mask;
545
546
sigemptyset(&mask);
547
if (sig)
548
{
549
sigaddset(&mask, sig);
550
op = SIG_UNBLOCK;
551
}
552
else op = SIG_SETMASK;
553
return sigprocmask(op, &mask, NiL);
554
}
555
556
/*
557
* fork+execvp
558
*/
559
560
int
561
spawnvp(const char* cmd, char* const* argv)
562
{
563
int pid;
564
565
sigcritical(1);
566
if ((pid = fork()) < 0)
567
{
568
note(SYSTEM, "fork");
569
return -1;
570
}
571
sigcritical(0);
572
if (pid == 0)
573
{
574
execvp(cmd, argv);
575
note(SYSTEM, "%s", cmd);
576
_exit(1);
577
}
578
return pid;
579
}
580
581
/*
582
* return the next character in the string s
583
* \ character constants are converted
584
* p is updated to point to the next character in s
585
*/
586
587
int
588
chresc(register const char* s, char** p)
589
{
590
register const char* q;
591
register int c;
592
593
switch (c = *s++)
594
{
595
case 0:
596
s--;
597
break;
598
case '\\':
599
switch (c = *s++)
600
{
601
case '0': case '1': case '2': case '3':
602
case '4': case '5': case '6': case '7':
603
c -= '0';
604
q = s + 2;
605
while (s < q) switch (*s)
606
{
607
case '0': case '1': case '2': case '3':
608
case '4': case '5': case '6': case '7':
609
c = (c << 3) + *s++ - '0';
610
break;
611
default:
612
q = s;
613
break;
614
}
615
break;
616
case 'a':
617
c = '\007';
618
break;
619
case 'b':
620
c = '\b';
621
break;
622
case 'f':
623
c = '\f';
624
break;
625
case 'n':
626
c = '\n';
627
break;
628
case 'r':
629
c = '\r';
630
break;
631
case 's':
632
c = ' ';
633
break;
634
case 't':
635
c = '\t';
636
break;
637
case 'v':
638
c = '\013';
639
break;
640
case 'x':
641
c = 0;
642
q = s;
643
while (q) switch (*s)
644
{
645
case 'a': case 'b': case 'c': case 'd': case 'e': case 'f':
646
c = (c << 4) + *s++ - 'a' + 10;
647
break;
648
case 'A': case 'B': case 'C': case 'D': case 'E': case 'F':
649
c = (c << 4) + *s++ - 'A' + 10;
650
break;
651
case '0': case '1': case '2': case '3': case '4':
652
case '5': case '6': case '7': case '8': case '9':
653
c = (c << 4) + *s++ - '0';
654
break;
655
default:
656
q = 0;
657
break;
658
}
659
break;
660
case 'E':
661
c = '\033';
662
break;
663
case 0:
664
s--;
665
break;
666
}
667
break;
668
}
669
if (p) *p = (char*)s;
670
return c;
671
}
672
673
/*
674
* copy t into s, return a pointer to the end of s ('\0')
675
*/
676
677
char*
678
strcopy(register char* s, register const char* t)
679
{
680
if (!t)
681
return s;
682
while (*s++ = *t++);
683
return s - 1;
684
}
685
686
/*
687
* convert \x character constants in s in place
688
* the length of the converted s is returned (may have imbedded \0's)
689
*/
690
691
int
692
stresc(register char* s)
693
{
694
register char* t;
695
register int c;
696
char* b;
697
char* p;
698
699
b = t = s;
700
for (;;)
701
{
702
switch (c = *s++)
703
{
704
case '\\':
705
c = chresc(s - 1, &p);
706
s = p;
707
break;
708
case 0:
709
*t = 0;
710
return t - b;
711
}
712
*t++ = c;
713
}
714
}
715
716
/*
717
* return a pointer to the isalpha() identifier matching
718
* name in the sorted tab of num elements of
719
* size siz where the first member of each
720
* element is a char*
721
*
722
* [xxx] brackets optional identifier characters
723
*
724
* 0 returned if name not found
725
* otherwise if next!=0 then it points to the next
726
* unmatched char in name
727
*/
728
729
void*
730
strpsearch(const void* tab, size_t num, size_t siz, const char* name, char** next)
731
{
732
register char* lo = (char*)tab;
733
register char* hi = lo + (num - 1) * siz;
734
register char* mid;
735
register unsigned char* s;
736
register unsigned char* t;
737
register int c;
738
register int v;
739
int sequential = 0;
740
741
c = *((unsigned char*)name);
742
while (lo <= hi) {
743
mid = lo + (sequential ? 0 : (((hi - lo) / siz) / 2) * siz);
744
if (!(v = c - *(s = *((unsigned char**)mid))) || *s == '[' && !(v = c - *++s) && (v = 1)) {
745
t = (unsigned char*)name;
746
for (;;) {
747
if (!v && *s == '[') {
748
v = 1;
749
s++;
750
}
751
else if (v && *s == ']') {
752
v = 0;
753
s++;
754
}
755
else if (!isalpha(*t)) {
756
if (v || !*s) {
757
if (next)
758
*next = (char*)t;
759
return (void*)mid;
760
}
761
if (!sequential) {
762
while ((mid -= siz) >= lo && (c == *(s = *((unsigned char**)mid)) || *s == '[' && c == *(s + 1)));
763
sequential = 1;
764
}
765
v = 1;
766
break;
767
}
768
else if (*t != *s) {
769
v = *t - *s;
770
break;
771
}
772
else {
773
t++;
774
s++;
775
}
776
}
777
}
778
else if (sequential)
779
break;
780
if (v > 0)
781
lo = mid + siz;
782
else
783
hi = mid - siz;
784
}
785
return 0;
786
}
787
788
typedef int (*Compare_f)(const char*, const char*);
789
typedef int (*Compare_context_f)(const char*, const char*, void*);
790
791
/*
792
* return a pointer to the element matching
793
* name in the (*comparf*)() sorted tab of num elements of
794
* size siz where the first member of each
795
* element is a char*
796
*
797
* 0 returned if name not found
798
*/
799
800
void*
801
strsearch(const void* tab, size_t num, size_t siz, Compare_f comparf, const char* name, void* context)
802
{
803
register char* lo = (char*)tab;
804
register char* hi = lo + (num - 1) * siz;
805
register char* mid;
806
register int v;
807
808
while (lo <= hi)
809
{
810
mid = lo + (((hi - lo) / siz) / 2) * siz;
811
if (!(v = context ? (*(Compare_context_f)comparf)(name, *((char**)mid), context) : (*(Compare_f)comparf)(name, *((char**)mid))))
812
return (void*)mid;
813
else if (v > 0)
814
lo = mid + siz;
815
else hi = mid - siz;
816
}
817
return 0;
818
}
819
820
/*
821
* touch file access and modify times of file
822
* if force>0 then file will be created if it doesn't exist
823
* if force<0 then times are taken verbatim
824
* times have one second granularity
825
*
826
* (time_t)(-1) retain old time
827
* 0 use current time
828
*/
829
830
int
831
touch(const char* file, time_t atime, time_t mtime, int force)
832
{
833
struct stat st;
834
struct timeval tv[2];
835
time_t now;
836
837
now = time(NiL);
838
if (stat(file, &st))
839
{
840
if (!force || close(open(file, O_WRONLY|O_CREAT|O_TRUNC|O_BINARY|O_cloexec, S_IRUSR|S_IWUSR|S_IRGRP|S_IWGRP|S_IROTH|S_IWOTH)))
841
return -1;
842
st.st_mtime = st.st_atime = now;
843
}
844
if (force >= 0)
845
{
846
if (atime == (time_t)0)
847
atime = now;
848
else if (atime == (time_t)(-1))
849
atime = st.st_atime;
850
if (mtime == (time_t)0)
851
mtime = now;
852
else if (mtime == (time_t)(-1))
853
mtime = st.st_mtime;
854
}
855
tv[0].tv_sec = atime;
856
tv[1].tv_sec = mtime;
857
tv[0].tv_usec = tv[1].tv_usec = 0;
858
return utimes(file, tv);
859
}
860
861
/*
862
* check if date is valid with no trailing junk
863
*/
864
865
int
866
isdate(const char* s)
867
{
868
return 1;
869
}
870
871
#endif /*_PACKAGE_ast*/
872
873