LCLINT pass 0.
[tools/librpm-tizen.git] / lib / query.c
1 #include "system.h"
2
3 #ifndef PATH_MAX
4 # define PATH_MAX 255
5 #endif
6
7 #include "build/rpmbuild.h"
8 #include "popt/popt.h"
9 #include "url.h"
10
11 static char * permsString(int mode);
12 static void printHeader(Header h, int queryFlags, char * queryFormat);
13 static void showMatches(rpmdb db, dbiIndexSet matches, int queryFlags, 
14                         char * queryFormat);
15 static void printFileInfo(char * name, unsigned int size, unsigned short mode,
16                           unsigned int mtime, unsigned short rdev,
17                           char * owner, char * group, int uid, int gid,
18                           char * linkto);
19
20 #define POPT_QUERYFORMAT        1000
21 #define POPT_WHATREQUIRES       1001
22 #define POPT_WHATPROVIDES       1002
23 #define POPT_QUERYBYNUMBER      1003
24 #define POPT_TRIGGEREDBY        1004
25 #define POPT_DUMP               1005
26
27 static void queryArgCallback(poptContext con, enum poptCallbackReason reason,
28                              const struct poptOption * opt, const char * arg, 
29                              struct rpmQueryArguments * data);
30
31 struct poptOption rpmQuerySourcePoptTable[] = {
32         { NULL, '\0', POPT_ARG_CALLBACK | POPT_CBFLAG_INC_DATA, 
33                 queryArgCallback, 0, NULL, NULL },
34         { "file", 'f', 0, 0, 'f', "query package owning file", "FILE" },
35         { "group", 'g', 0, 0, 'g', "query packages in group", "GROUP" },
36         { "package", 'p', 0, 0, 'p', "query a package file" },
37         { "triggeredby", '\0', 0, 0, POPT_TRIGGEREDBY, 
38                 "query the pacakges triggered by the package", "PACKAGE" },
39         { "whatrequires", '\0', 0, 0, POPT_WHATREQUIRES, 
40                 "query the packages which require a capability", "CAPABILITY" },
41         { "whatprovides", '\0', 0, 0, POPT_WHATPROVIDES, 
42                 "query the packages which provide a capability", "CAPABILITY" },
43         { 0, 0, 0, 0, 0 }
44 };
45
46 struct poptOption rpmQueryPoptTable[] = {
47         { NULL, '\0', POPT_ARG_CALLBACK | POPT_CBFLAG_INC_DATA, 
48                 queryArgCallback, 0, NULL, NULL },
49         { "configfiles", 'c', 0, 0, 'c', "list all configuration files" },
50         { "docfiles", 'd', 0, 0, 'd', "list all documetnation files" },
51         { "dump", '\0', 0, 0, POPT_DUMP, "dump basic file information" },
52         { "list", 'l', 0, 0, 'l', "list files in package" },
53         { "qf", '\0', POPT_ARG_STRING | POPT_ARGFLAG_DOC_HIDDEN, 0, 
54                 POPT_QUERYFORMAT },
55         { "querybynumber", '\0', POPT_ARGFLAG_DOC_HIDDEN, 0, 
56                 POPT_QUERYBYNUMBER },
57         { "queryformat", '\0', POPT_ARG_STRING, 0, POPT_QUERYFORMAT,
58                 "use the following query format", "QUERYFORMAT" },
59         { "state", 's', 0, 0, 's', "display the states of the listed files" },
60         { "verbose", 'v', 0, 0, 'v', "display a verbose filelisting" },
61         { 0, 0, 0, 0, 0 }
62 };
63
64 static void queryArgCallback(poptContext con, enum poptCallbackReason reason,
65                              const struct poptOption * opt, const char * arg, 
66                              struct rpmQueryArguments * data) {
67     int len;
68
69     switch (opt->val) {
70       case 'c': data->flags |= QUERY_FOR_CONFIG | QUERY_FOR_LIST; break;
71       case 'd': data->flags |= QUERY_FOR_DOCS | QUERY_FOR_LIST; break;
72       case 'l': data->flags |= QUERY_FOR_LIST; break;
73       case 's': data->flags |= QUERY_FOR_STATE | QUERY_FOR_LIST; break;
74       case POPT_DUMP: data->flags |= QUERY_FOR_DUMPFILES | QUERY_FOR_LIST; break;
75
76       case 'a': data->source |= QUERY_ALL; data->sourceCount++; break;
77       case 'f': data->source |= QUERY_PATH; data->sourceCount++; break;
78       case 'g': data->source |= QUERY_GROUP; data->sourceCount++; break;
79       case 'p': data->source |= QUERY_RPM; data->sourceCount++; break;
80       case POPT_WHATPROVIDES: data->source |= QUERY_WHATPROVIDES; 
81                               data->sourceCount++; break;
82       case POPT_WHATREQUIRES: data->source |= QUERY_WHATREQUIRES; 
83                               data->sourceCount++; break;
84       case POPT_QUERYBYNUMBER: data->source |= QUERY_DBOFFSET; 
85                               data->sourceCount++; break;
86       case POPT_TRIGGEREDBY: data->source |= QUERY_TRIGGEREDBY;
87                               data->sourceCount++; break;
88
89       case POPT_QUERYFORMAT:
90         if (data->queryFormat) {
91             len = strlen(data->queryFormat) + strlen(arg) + 1;
92             data->queryFormat = realloc(data->queryFormat, len);
93             strcat(data->queryFormat, arg);
94         } else {
95             data->queryFormat = malloc(strlen(arg) + 1);
96             strcpy(data->queryFormat, arg);
97         }
98         break;
99     }
100 }
101
102 static int queryHeader(Header h, char * chptr) {
103     char * str;
104     char * error;
105
106     str = headerSprintf(h, chptr, rpmTagTable, rpmHeaderFormats, &error);
107     if (!str) {
108         fprintf(stderr, _("error in format: %s\n"), error);
109         return 1;
110     }
111
112     fputs(str, stdout);
113
114     return 0;
115 }
116
117 static void printHeader(Header h, int queryFlags, char * queryFormat) {
118     char * name, * version, * release;
119     int_32 count, type;
120     char * prefix = NULL;
121     char ** fileList, ** fileMD5List;
122     char * fileStatesList;
123     char ** fileOwnerList = NULL;
124     char ** fileGroupList = NULL;
125     char ** fileLinktoList;
126     int_32 * fileFlagsList, * fileMTimeList, * fileSizeList;
127     int_32 * fileUIDList, * fileGIDList;
128     uint_16 * fileModeList;
129     uint_16 * fileRdevList;
130     int i;
131
132     headerGetEntry(h, RPMTAG_NAME, &type, (void **) &name, &count);
133     headerGetEntry(h, RPMTAG_VERSION, &type, (void **) &version, &count);
134     headerGetEntry(h, RPMTAG_RELEASE, &type, (void **) &release, &count);
135
136     if (!queryFormat && !queryFlags) {
137         fprintf(stdout, "%s-%s-%s\n", name, version, release);
138     } else {
139         if (queryFormat)
140             queryHeader(h, queryFormat);
141
142         if (queryFlags & QUERY_FOR_LIST) {
143             if (!headerGetEntry(h, RPMTAG_FILENAMES, &type, (void **) &fileList, 
144                  &count)) {
145                 fputs(_("(contains no files)"), stdout);
146                 fputs("\n", stdout);
147             } else {
148                 if (!headerGetEntry(h, RPMTAG_FILESTATES, &type, 
149                          (void **) &fileStatesList, &count)) {
150                     fileStatesList = NULL;
151                 }
152                 headerGetEntry(h, RPMTAG_FILEFLAGS, &type, 
153                          (void **) &fileFlagsList, &count);
154                 headerGetEntry(h, RPMTAG_FILESIZES, &type, 
155                          (void **) &fileSizeList, &count);
156                 headerGetEntry(h, RPMTAG_FILEMODES, &type, 
157                          (void **) &fileModeList, &count);
158                 headerGetEntry(h, RPMTAG_FILEMTIMES, &type, 
159                          (void **) &fileMTimeList, &count);
160                 headerGetEntry(h, RPMTAG_FILERDEVS, &type, 
161                          (void **) &fileRdevList, &count);
162                 headerGetEntry(h, RPMTAG_FILELINKTOS, &type, 
163                          (void **) &fileLinktoList, &count);
164                 headerGetEntry(h, RPMTAG_FILEMD5S, &type, 
165                          (void **) &fileMD5List, &count);
166
167                 if (!headerGetEntry(h, RPMTAG_FILEUIDS, &type, 
168                          (void **) &fileUIDList, &count)) {
169                     fileUIDList = NULL;
170                 } else {
171                     headerGetEntry(h, RPMTAG_FILEGIDS, &type, 
172                              (void **) &fileGIDList, &count);
173                 }
174
175                 if (!headerGetEntry(h, RPMTAG_FILEUSERNAME, &type, 
176                          (void **) &fileOwnerList, &count)) {
177                     fileOwnerList = NULL;
178                 } else {
179                     headerGetEntry(h, RPMTAG_FILEGROUPNAME, &type, 
180                              (void **) &fileGroupList, &count);
181                 }
182
183                 for (i = 0; i < count; i++) {
184                     if (!((queryFlags & QUERY_FOR_DOCS) || 
185                           (queryFlags & QUERY_FOR_CONFIG)) 
186                         || ((queryFlags & QUERY_FOR_DOCS) && 
187                             (fileFlagsList[i] & RPMFILE_DOC))
188                         || ((queryFlags & QUERY_FOR_CONFIG) && 
189                             (fileFlagsList[i] & RPMFILE_CONFIG))) {
190
191                         if (!rpmIsVerbose())
192                             prefix ? fputs(prefix, stdout) : 0;
193
194                         if (queryFlags & QUERY_FOR_STATE) {
195                             if (fileStatesList) {
196                                 switch (fileStatesList[i]) {
197                                   case RPMFILE_STATE_NORMAL:
198                                     fputs(_("normal        "), stdout); break;
199                                   case RPMFILE_STATE_REPLACED:
200                                     fputs(_("replaced      "), stdout); break;
201                                   case RPMFILE_STATE_NETSHARED:
202                                     fputs(_("net shared    "), stdout); break;
203                                   case RPMFILE_STATE_NOTINSTALLED:
204                                     fputs(_("not installed "), stdout); break;
205                                   default:
206                                     fprintf(stdout, _("(unknown %3d) "), 
207                                           fileStatesList[i]);
208                                 }
209                             } else {
210                                 fputs(    _("(no state)    "), stdout);
211                             }
212                         }
213                             
214                         if (queryFlags & QUERY_FOR_DUMPFILES) {
215                             fprintf(stdout, "%s %d %d %s 0%o ", fileList[i],
216                                    fileSizeList[i], fileMTimeList[i],
217                                    fileMD5List[i], fileModeList[i]);
218
219                             if (fileOwnerList)
220                                 fprintf(stdout, "%s %s", fileOwnerList[i], 
221                                                 fileGroupList[i]);
222                             else if (fileUIDList)
223                                 fprintf(stdout, "%d %d", fileUIDList[i], 
224                                                 fileGIDList[i]);
225                             else {
226                                 rpmError(RPMERR_INTERNAL, _("package has "
227                                         "neither file owner or id lists"));
228                             }
229
230                             fprintf(stdout, " %s %s %u ", 
231                                  fileFlagsList[i] & RPMFILE_CONFIG ? "1" : "0",
232                                  fileFlagsList[i] & RPMFILE_DOC ? "1" : "0",
233                                  (unsigned)fileRdevList[i]);
234
235                             if (strlen(fileLinktoList[i]))
236                                 fprintf(stdout, "%s\n", fileLinktoList[i]);
237                             else
238                                 fprintf(stdout, "X\n");
239
240                         } else if (!rpmIsVerbose()) {
241                             fputs(fileList[i], stdout);
242                             fputs("\n", stdout);
243                         } else if (fileOwnerList) 
244                             printFileInfo(fileList[i], fileSizeList[i],
245                                           fileModeList[i], fileMTimeList[i],
246                                           fileRdevList[i], fileOwnerList[i], 
247                                           fileGroupList[i], -1, 
248                                           -1, fileLinktoList[i]);
249                         else if (fileUIDList) {
250                             printFileInfo(fileList[i], fileSizeList[i],
251                                           fileModeList[i], fileMTimeList[i],
252                                           fileRdevList[i], NULL, 
253                                           NULL, fileUIDList[i], 
254                                           fileGIDList[i], fileLinktoList[i]);
255                         } else {
256                             rpmError(RPMERR_INTERNAL, _("package has "
257                                     "neither file owner or id lists"));
258                         }
259                     }
260                 }
261             
262                 free(fileList);
263                 free(fileLinktoList);
264                 free(fileMD5List);
265                 if (fileOwnerList) free(fileOwnerList);
266                 if (fileGroupList) free(fileGroupList);
267             }
268         }
269     }
270 }
271
272 static char * permsString(int mode) {
273     static char perms[11];
274
275     strcpy(perms, "----------");
276    
277     if (mode & S_IRUSR) perms[1] = 'r';
278     if (mode & S_IWUSR) perms[2] = 'w';
279     if (mode & S_IXUSR) perms[3] = 'x';
280  
281     if (mode & S_IRGRP) perms[4] = 'r';
282     if (mode & S_IWGRP) perms[5] = 'w';
283     if (mode & S_IXGRP) perms[6] = 'x';
284
285     if (mode & S_IROTH) perms[7] = 'r';
286     if (mode & S_IWOTH) perms[8] = 'w';
287     if (mode & S_IXOTH) perms[9] = 'x';
288
289
290     if (mode & S_ISVTX)
291         perms[9] = ((mode & S_IXOTH) ? 't' : 'T');
292
293     if (mode & S_ISUID) {
294         if (mode & S_IXUSR) 
295             perms[3] = 's'; 
296         else
297             perms[3] = 'S'; 
298     }
299
300     if (mode & S_ISGID) {
301         if (mode & S_IXGRP) 
302             perms[6] = 's'; 
303         else
304             perms[6] = 'S'; 
305     }
306
307     if (S_ISDIR(mode)) 
308         perms[0] = 'd';
309     else if (S_ISLNK(mode)) {
310         perms[0] = 'l';
311     }
312     else if (S_ISFIFO(mode)) 
313         perms[0] = 'p';
314     else if (S_ISSOCK(mode)) 
315         perms[0] = 's';
316     else if (S_ISCHR(mode)) {
317         perms[0] = 'c';
318     } else if (S_ISBLK(mode)) {
319         perms[0] = 'b';
320     }
321
322     return perms;
323 }
324
325 static void printFileInfo(char * name, unsigned int size, unsigned short mode,
326                           unsigned int mtime, unsigned short rdev,
327                           char * owner, char * group, int uid, int gid,
328                           char * linkto) {
329     char sizefield[15];
330     char ownerfield[9], groupfield[9];
331     char timefield[100] = "";
332     time_t themtime;
333     time_t currenttime;
334     static int thisYear = 0;
335     static int thisMonth = 0;
336     struct tm * tstruct;
337     char * namefield = name;
338     char * perms;
339
340     perms = permsString(mode);
341
342     if (!thisYear) {
343         currenttime = time(NULL);
344         tstruct = localtime(&currenttime);
345         thisYear = tstruct->tm_year;
346         thisMonth = tstruct->tm_mon;
347     }
348
349     ownerfield[8] = groupfield[8] = '\0';
350
351     if (owner) 
352         strncpy(ownerfield, owner, 8);
353     else
354         sprintf(ownerfield, "%-8d", uid);
355
356     if (group) 
357         strncpy(groupfield, group, 8);
358     else 
359         sprintf(groupfield, "%-8d", gid);
360
361     /* this is normally right */
362     sprintf(sizefield, "%10u", size);
363
364     /* this knows too much about dev_t */
365
366     if (S_ISLNK(mode)) {
367         namefield = alloca(strlen(name) + strlen(linkto) + 10);
368         sprintf(namefield, "%s -> %s", name, linkto);
369     } else if (S_ISCHR(mode)) {
370         perms[0] = 'c';
371         sprintf(sizefield, "%3u, %3u", (rdev >> 8) & 0xff, rdev & 0xFF);
372     } else if (S_ISBLK(mode)) {
373         perms[0] = 'b';
374         sprintf(sizefield, "%3u, %3u", (rdev >> 8) & 0xff, rdev & 0xFF);
375     }
376
377     /* this is important if sizeof(int_32) ! sizeof(time_t) */
378     themtime = mtime;
379     tstruct = localtime(&themtime);
380
381     if (tstruct->tm_year == thisYear || 
382       ((tstruct->tm_year + 1) == thisYear && tstruct->tm_mon > thisMonth)) 
383         (void)strftime(timefield, sizeof(timefield) - 1, "%b %d %H:%M",tstruct);
384     else
385         (void)strftime(timefield, sizeof(timefield) - 1, "%b %d  %Y", tstruct);
386
387     fprintf(stdout, "%s %8s %8s %10s %s %s\n", perms, ownerfield, groupfield, 
388                 sizefield, timefield, namefield);
389 }
390
391 static void showMatches(rpmdb db, dbiIndexSet matches, int queryFlags, 
392                         char * queryFormat) {
393     int i;
394     Header h;
395
396     for (i = 0; i < matches.count; i++) {
397         if (matches.recs[i].recOffset) {
398             rpmMessage(RPMMESS_DEBUG, _("querying record number %d\n"),
399                         matches.recs[i].recOffset);
400             
401             h = rpmdbGetRecord(db, matches.recs[i].recOffset);
402             if (h == NULL) {
403                 fprintf(stderr, _("error: could not read database record\n"));
404             } else {
405                 printHeader(h, queryFlags, queryFormat);
406                 headerFree(h);
407             }
408         }
409     }
410 }
411
412 int rpmQuery(char * prefix, enum rpmQuerySources source, int queryFlags, 
413              char * arg, char * queryFormat) {
414     Header h;
415     int offset;
416     int fd;
417     int rc;
418     int isSource;
419     rpmdb db;
420     dbiIndexSet matches;
421     int recNumber;
422     int retcode = 0;
423     char *end = NULL;
424     struct urlContext context;
425     int isUrl = 0;
426     char path[PATH_MAX];
427
428     if (source != QUERY_RPM) {
429         if (rpmdbOpen(prefix, &db, O_RDONLY, 0644)) {
430             exit(1);
431         }
432     }
433
434     switch (source) {
435       case QUERY_RPM:
436         if (urlIsURL(arg)) {
437             isUrl = 1;
438             if ((fd = urlGetFd(arg, &context)) < 0) {
439                 fprintf(stderr, _("open of %s failed: %s\n"), arg, 
440                         ftpStrerror(fd));
441             }
442         } else if (!strcmp(arg, "-")) {
443             fd = 0;
444         } else {
445             if ((fd = open(arg, O_RDONLY)) < 0) {
446                 fprintf(stderr, _("open of %s failed: %s\n"), arg, 
447                         strerror(errno));
448             }
449         }
450
451         if (fd >= 0) {
452             rc = rpmReadPackageHeader(fd, &h, &isSource, NULL, NULL);
453
454             close(fd);
455             if (isUrl) {
456                 urlFinishedFd(&context);
457             }
458
459             switch (rc) {
460                 case 0:
461                     if (h == NULL) {
462                         fprintf(stderr, _("old format source packages cannot "
463                                 "be queried\n"));
464                     } else {
465                         printHeader(h, queryFlags, queryFormat);
466                         headerFree(h);
467                     }
468                     break;
469                 case 1:
470                     fprintf(stderr, 
471                             _("%s does not appear to be a RPM package\n"), 
472                             arg);
473                     /* fallthrough */
474                 case 2:
475                     fprintf(stderr, _("query of %s failed\n"), arg);
476                     retcode = 1;
477             }
478
479         }
480                 
481         break;
482
483       case QUERY_ALL:
484         offset = rpmdbFirstRecNum(db);
485         while (offset) {
486             h = rpmdbGetRecord(db, offset);
487             if (h == NULL) {
488                 fprintf(stderr, _("could not read database record!\n"));
489                 return 1;
490             }
491             printHeader(h, queryFlags, queryFormat);
492             headerFree(h);
493             offset = rpmdbNextRecNum(db, offset);
494         }
495         break;
496
497       case QUERY_GROUP:
498         if (rpmdbFindByGroup(db, arg, &matches)) {
499             fprintf(stderr, _("group %s does not contain any packages\n"), arg);
500             retcode = 1;
501         } else {
502             showMatches(db, matches, queryFlags, queryFormat);
503             dbiFreeIndexRecord(matches);
504         }
505         break;
506
507       case QUERY_WHATPROVIDES:
508         if (rpmdbFindByProvides(db, arg, &matches)) {
509             fprintf(stderr, _("no package provides %s\n"), arg);
510             retcode = 1;
511         } else {
512             showMatches(db, matches, queryFlags, queryFormat);
513             dbiFreeIndexRecord(matches);
514         }
515         break;
516
517       case QUERY_TRIGGEREDBY:
518         if (rpmdbFindByTriggeredBy(db, arg, &matches)) {
519             fprintf(stderr, _("no package triggers %s\n"), arg);
520             retcode = 1;
521         } else {
522             showMatches(db, matches, queryFlags, queryFormat);
523             dbiFreeIndexRecord(matches);
524         }
525         break;
526
527       case QUERY_WHATREQUIRES:
528         if (rpmdbFindByRequiredBy(db, arg, &matches)) {
529             fprintf(stderr, _("no package requires %s\n"), arg);
530             retcode = 1;
531         } else {
532             showMatches(db, matches, queryFlags, queryFormat);
533             dbiFreeIndexRecord(matches);
534         }
535         break;
536
537       case QUERY_PATH:
538         if (*arg != '/') {
539                 /* Using realpath on the arg isn't correct if the arg is a symlink,
540                  * especially if the symlink is a dangling link.  What we should
541                  * instead do is use realpath() on `.' and then append arg to
542                  * it.
543                  */
544             if (realpath(".", path) != NULL) {
545                 if (path[strlen(path)] != '/') {
546                         if (strncat(path, "/", PATH_MAX - strlen(path) - 1) == NULL) {
547                         fprintf(stderr, _("maximum path length exceeded\n"));
548                         return 1;
549                         }
550                 }
551                 /* now append the original file name to the real path */
552                 if (strncat(path, arg, PATH_MAX - strlen(path) - 1) == NULL) {
553                         fprintf(stderr, _("maximum path length exceeded\n"));
554                         return 1;
555                 }
556                 arg = path;
557             }
558         }
559         if (rpmdbFindByFile(db, arg, &matches)) {
560             int myerrno = 0;
561             if (access(arg, F_OK) != 0)
562                 myerrno = errno;
563             switch (myerrno) {
564             default:
565                 fprintf(stderr, _("file %s: %s\n"), arg, strerror(myerrno));
566                 break;
567             case 0:
568                 fprintf(stderr, _("file %s is not owned by any package\n"), arg);
569                 break;
570             }
571             retcode = 1;
572         } else {
573             showMatches(db, matches, queryFlags, queryFormat);
574             dbiFreeIndexRecord(matches);
575         }
576         break;
577
578       case QUERY_DBOFFSET:
579         recNumber = strtoul(arg, &end, 10);
580         if ((*end) || (end == arg) || (recNumber == ULONG_MAX)) {
581             fprintf(stderr, _("invalid package number: %s\n"), arg);
582             return 1;
583         }
584         rpmMessage(RPMMESS_DEBUG, _("showing package: %d\n"), recNumber);
585         h = rpmdbGetRecord(db, recNumber);
586         if (h == NULL)  {
587             fprintf(stderr, _("record %d could not be read\n"), recNumber);
588             retcode = 1;
589         } else {
590             printHeader(h, queryFlags, queryFormat);
591             headerFree(h);
592         }
593         break;
594
595       case QUERY_PACKAGE:
596         rc = rpmdbFindByLabel(db, arg, &matches);
597         if (rc == 1) {
598             retcode = 1;
599             fprintf(stderr, _("package %s is not installed\n"), arg);
600         } else if (rc == 2) {
601             retcode = 1;
602             fprintf(stderr, _("error looking for package %s\n"), arg);
603         } else {
604             showMatches(db, matches, queryFlags, queryFormat);
605             dbiFreeIndexRecord(matches);
606         }
607         break;
608     }
609    
610     if (source != QUERY_RPM) {
611         rpmdbClose(db);
612     }
613
614     return retcode;
615 }
616
617 void rpmDisplayQueryTags(FILE * f) {
618     const struct headerTagTableEntry * t;
619     int i;
620     const struct headerSprintfExtension * ext = rpmHeaderFormats;
621
622     for (i = 0, t = rpmTagTable; i < rpmTagTableSize; i++, t++) {
623         fprintf(f, "%s\n", t->name + 7);
624     }
625
626     while (ext->name) {
627         if (ext->type == HEADER_EXT_TAG)
628             fprintf(f, "%s\n", ext->name + 7), ext++;
629         else if (ext->type == HEADER_EXT_MORE)
630             ext = ext->u.more;
631         else
632             ext++;
633     }
634 }