Annotation of researchv9/X11/src/X.V11R1/clients/xmh/EDiskSrc.c, revision 1.1

1.1     ! root        1: #ifndef lint
        !             2: static char rcs_id[] = "$Header: EDiskSrc.c,v 1.13 87/09/11 08:19:00 toddb Exp $";
        !             3: #endif lint
        !             4: /*
        !             5:  *                       COPYRIGHT 1987
        !             6:  *                DIGITAL EQUIPMENT CORPORATION
        !             7:  *                    MAYNARD, MASSACHUSETTS
        !             8:  *                     ALL RIGHTS RESERVED.
        !             9:  *
        !            10:  * THE INFORMATION IN THIS SOFTWARE IS SUBJECT TO CHANGE WITHOUT NOTICE AND
        !            11:  * SHOULD NOT BE CONSTRUED AS A COMMITMENT BY DIGITAL EQUIPMENT CORPORATION.
        !            12:  * DIGITAL MAKES NO REPRESENTATIONS ABOUT THE SUITABILITY OF THIS SOFTWARE FOR
        !            13:  * ANY PURPOSE.  IT IS SUPPLIED "AS IS" WITHOUT EXPRESS OR IMPLIED WARRANTY.
        !            14:  *
        !            15:  * IF THE SOFTWARE IS MODIFIED IN A MANNER CREATING DERIVATIVE COPYRIGHT RIGHTS,
        !            16:  * APPROPRIATE LEGENDS MAY BE PLACED ON THE DERIVATIVE WORK IN ADDITION TO THAT
        !            17:  * SET FORTH ABOVE.
        !            18:  *
        !            19:  *
        !            20:  * Permission to use, copy, modify, and distribute this software and its
        !            21:  * documentation for any purpose and without fee is hereby granted, provided
        !            22:  * that the above copyright notice appear in all copies and that both that
        !            23:  * copyright notice and this permission notice appear in supporting documentation,
        !            24:  * and that the name of Digital Equipment Corporation not be used in advertising
        !            25:  * or publicity pertaining to distribution of the software without specific,
        !            26:  * written prior permission.
        !            27:  */
        !            28: 
        !            29: /* File: EDiskSource.c */
        !            30: 
        !            31: /* This is a rather simplistic text source.  It allows editing of a disk file.
        !            32:    It divides the file into blocks of BUFSIZE characters.  No block is
        !            33:    actually read in from disk until referenced; each block will be read at
        !            34:    most once.  Thus, the entire file can eventually be read into memory and
        !            35:    stored there.  When the save function is called, everything gets written
        !            36:    back. */
        !            37:    
        !            38: 
        !            39: 
        !            40: #include <sys/types.h>
        !            41: #include <sys/stat.h>
        !            42: #include <sys/file.h>
        !            43: 
        !            44: #include "xmh.h"
        !            45: 
        !            46: 
        !            47: /* Private definitions. */
        !            48: 
        !            49: #define BUFSIZE        512             /* Number of chars to store in each piece. */
        !            50: 
        !            51: extern char *strcpy();
        !            52: extern long lseek();
        !            53: 
        !            54: /* The structure corresponding to each piece of the file in memory. */
        !            55: 
        !            56: typedef struct {
        !            57:     char *buf;                 /* Pointer to this piece's data. */
        !            58:     int length;                        /* Number of chars used in buf. */
        !            59:     int maxlength;             /* Number of chars allocated in buf. */
        !            60:     long start;                        /* Position in file corresponding to piece. */
        !            61: } PieceRec, *PiecePtr;
        !            62: 
        !            63: 
        !            64: 
        !            65: /* The master structure for the source. */
        !            66: 
        !            67: typedef struct {
        !            68:     int file;                  /* File descriptor. */
        !            69:     char *name;                        /* Name of file. */
        !            70:     int length;                        /* Number of characters in file. */
        !            71:     int origlength;            /* Number of characters originally in file. */
        !            72:     int numpieces;             /* How many pieces the file is divided in. */
        !            73:     PiecePtr piece;            /* Pointer to array of pieces. */
        !            74:     short changed;             /* If changes have not been written to disk. */
        !            75:     short everchanged;         /* If changes have ever been made. */
        !            76:     short startsvalid;         /* True iff the start fields in pieces
        !            77:                                   are correct. */
        !            78:     short eversaved;           /* If we've ever saved changes to the file. */
        !            79:     XtEditType editMode;       /* Whether we are editing or read-only. */
        !            80:     void (*func)();            /* Function to call when a change is made. */
        !            81:     caddr_t param;             /* Parameter to pass to above function. */
        !            82:     int checkpointed;          /* Filename to store checkpoints. */
        !            83:     int checkpointchange;      /* TRUE if we've changed since checkpointed. */
        !            84: } EDiskRec, *EDiskPtr;
        !            85: 
        !            86: 
        !            87: 
        !            88: /* Refigure the starting location of each piece, which is the sum of the
        !            89:    lengths of all the preceding pieces. */
        !            90: 
        !            91: static CalculateStarts(data)
        !            92:   EDiskPtr data;
        !            93: {
        !            94:     int     i;
        !            95:     PiecePtr piece;
        !            96:     long    start = 0;
        !            97:     for (i = 0, piece = data->piece; i < data->numpieces; i++, piece++) {
        !            98:        piece->start = start;
        !            99:        start += piece->length;
        !           100:     }
        !           101:     data->startsvalid = TRUE;
        !           102: }
        !           103: 
        !           104: 
        !           105: 
        !           106: /* Read in the text for the ith piece. */
        !           107: 
        !           108: static ReadPiece(data, i)
        !           109:   EDiskPtr data;
        !           110:   int i;
        !           111: {
        !           112:     PiecePtr piece = data->piece + i;
        !           113:     piece->maxlength = piece->length;
        !           114:     piece->buf = XtMalloc((unsigned) piece->maxlength);
        !           115:     (void)lseek(data->file, (long) i * BUFSIZE, 0);
        !           116:     (void)read(data->file, piece->buf, piece->length);
        !           117: }
        !           118: 
        !           119: 
        !           120: 
        !           121: /* Figure out which piece corresponds to the given position.  If this source
        !           122:    has never had any changes, then this is a simple matter of division;
        !           123:    otherwise, we have to search for the specified piece.  (Currently, this is
        !           124:    a simple linear search; a binary one would probably be better.) */
        !           125: 
        !           126: static PiecePtr PieceFromPosition(data, position)
        !           127:   EDiskPtr data;
        !           128:   XtTextPosition position;
        !           129: {
        !           130:     int     i;
        !           131:     PiecePtr piece;
        !           132:     if (!data->everchanged) {
        !           133:        i = position / BUFSIZE;
        !           134:        if (i >= data->numpieces) i = data->numpieces - 1;
        !           135:        piece = data->piece + i;
        !           136:     } else {
        !           137:        if (!data->startsvalid)
        !           138:            CalculateStarts(data);
        !           139:        for (i = 0, piece = data->piece; i < data->numpieces - 1; i++, piece++)
        !           140:            if (position < piece->start + piece->length)
        !           141:                break;
        !           142:     }
        !           143:     if (!piece->buf) ReadPiece(data, i);
        !           144:     return piece;
        !           145: }
        !           146: 
        !           147: 
        !           148: /* Return the given position, coerced to be within the range of legal positions
        !           149:    for the given source. */
        !           150: 
        !           151: static XtTextPosition CoerceToLegalPosition(data, position)
        !           152:   EDiskPtr data;
        !           153:   XtTextPosition position;
        !           154: {
        !           155:     return (position < 0) ? 0 :
        !           156:                 ((position > data->length) ? data->length : position);
        !           157: }
        !           158: 
        !           159: 
        !           160: 
        !           161: /* Semi-public definitions */
        !           162: 
        !           163: /* Reads in the text for the given range.  Will not read past a piece boundary.
        !           164:    Returns the position after the last character that was read. */
        !           165: 
        !           166: static EDiskReadText(src, position, text, maxRead)
        !           167:   XtTextSource *src;
        !           168:   XtTextPosition position;     /* Starting position to read. */
        !           169:   XtTextBlock *text;           /* RETURN - the text read. */
        !           170:   int maxRead;                 /* Number of positions to read. */
        !           171: {
        !           172:     EDiskPtr data = (EDiskPtr) src->data;
        !           173:     PiecePtr piece;
        !           174:     int     count;
        !           175:     text->firstPos = position;
        !           176:     if (position < data->length) {
        !           177:        piece = PieceFromPosition(data, position);
        !           178:        text->ptr = piece->buf + (position - piece->start);
        !           179:        count = piece->length - (position - piece->start);
        !           180:        text->length = (count < maxRead) ? count : maxRead;
        !           181:     }
        !           182:     else {
        !           183:        text->length = 0;
        !           184:        text->ptr = "";
        !           185:     }
        !           186:     return position + text->length;
        !           187: }
        !           188: 
        !           189: 
        !           190: 
        !           191: 
        !           192: /* Replace the text between startPos and endPos with the given text.  Returns
        !           193:    EDITDONE on success, or EDITERROR if this source is read-only.  */
        !           194: 
        !           195: 
        !           196: static int EDiskReplaceText(src, startPos, endPos, text, delta)
        !           197:   XtTextSource *src;
        !           198:   XtTextPosition startPos, endPos;
        !           199:   XtTextBlock *text;
        !           200:   int *delta;                  /* RETURN - change in length of source. */
        !           201: {
        !           202:     EDiskPtr data = (EDiskPtr) src->data;
        !           203:     PiecePtr piece, piece2;
        !           204:     int     oldlength = endPos - startPos;
        !           205:     int     i;
        !           206:     int     del = text->length - oldlength;
        !           207:     if (del == 0 && oldlength == 0) {
        !           208:        *delta = 0;
        !           209:        return EDITDONE;
        !           210:     }
        !           211:     if (data->editMode != XttextEdit)
        !           212:        return EDITERROR;
        !           213:     data->length += del;
        !           214:     piece = PieceFromPosition(data, startPos);
        !           215:     if (oldlength > 0) {
        !           216:        piece2 = PieceFromPosition(data, endPos - 1);
        !           217:        if (piece != piece2) {
        !           218:            oldlength = endPos - piece2->start;
        !           219:            piece2->length -= oldlength;
        !           220:            for (i = 0; i < piece2->length; i++)
        !           221:                piece2->buf[i] = piece2->buf[i + oldlength];
        !           222:            for (piece2--; piece2 > piece; piece2--)
        !           223:                piece2->length = 0;
        !           224:            oldlength = piece->length - (startPos - piece->start);
        !           225:            del = text->length - oldlength;
        !           226:        }
        !           227:     }
        !           228:     data->changed = data->everchanged = data->checkpointchange = TRUE;
        !           229:     data->startsvalid = FALSE;
        !           230:     piece->length += del;
        !           231:     if (piece->length > piece->maxlength) {
        !           232:        do
        !           233:            piece->maxlength *= 2;
        !           234:        while (piece->length > piece->maxlength);
        !           235:        piece->buf = XtRealloc(piece->buf, (unsigned)piece->maxlength);
        !           236:     }
        !           237:     if (del < 0)               /* insert shorter than delete, text getting
        !           238:                                   shorter */
        !           239:        for (i = startPos - piece->start; i < piece->length; i++)
        !           240:            piece->buf[i] = piece->buf[i - del];
        !           241:     else
        !           242:        if (del > 0)            /* insert longer than delete, text getting
        !           243:                                   longer */
        !           244:            for (i = piece->length - del - 1;
        !           245:                    i >= startPos - piece->start;
        !           246:                    i--)
        !           247:                piece->buf[i + del] = piece->buf[i];
        !           248:     if (text->length)  /* do insert */
        !           249:        for (i = 0; i < text->length; ++i)
        !           250:            if ((piece->buf[startPos - piece->start + i] = text->ptr[i]) == CR)
        !           251:                 piece->buf[startPos - piece->start + i] = LF;
        !           252:     *delta = text->length - (endPos - startPos);
        !           253:     if (data->func) (*data->func)(data->param);
        !           254:     return EDITDONE;
        !           255: }
        !           256: 
        !           257: 
        !           258: /* Returns the last legal position in the source. */
        !           259: 
        !           260: static XtTextPosition EDiskGetLastPos(src)
        !           261:   XtTextSource *src;
        !           262: {
        !           263:     return ((EDiskPtr) src->data)->length;
        !           264: }
        !           265: 
        !           266: 
        !           267: /* Sets the last legal position in the source.  This is actually an obsolete
        !           268:    source request, and is therefore a noop. */
        !           269: 
        !           270: /*ARGSUSED*/   /* -- keeps lint from complaining. */
        !           271: static EDiskSetLastPos(src)
        !           272:   XtTextSource *src;
        !           273: {
        !           274: }
        !           275: 
        !           276: 
        !           277: /* Utility macro used in EDiskScan.  Used to determine what the next character
        !           278:    "after" index is, where "after" is either left or right, depending on
        !           279:    what kind of search we're doing. */
        !           280: 
        !           281: #define Look(index, c)\
        !           282: {                                                                      \
        !           283:     if ((dir == XtsdLeft && index <= 0) ||                             \
        !           284:            (dir == XtsdRight && index >= data->length))                \
        !           285:        c = 0;                                                          \
        !           286:     else {                                                             \
        !           287:        if (index + doff < piece->start ||                              \
        !           288:                index + doff >= piece->start + piece->length)           \
        !           289:            piece = PieceFromPosition(data, index + doff);              \
        !           290:        c = piece->buf[index + doff - piece->start];                    \
        !           291:     }                                                                  \
        !           292: }
        !           293: 
        !           294: 
        !           295: 
        !           296: /* Scan the source.  This gets complicated, but should be explained wherever
        !           297:    it is that we documented sources. */
        !           298: 
        !           299: static XtTextPosition EDiskScan(src, position, sType, dir, count, include)
        !           300:   XtTextSource *src;
        !           301:   XtTextPosition position;
        !           302:   ScanType sType;
        !           303:   ScanDirection dir;
        !           304:   int count, include;
        !           305: {
        !           306:     EDiskPtr data = (EDiskPtr) src->data;
        !           307:     XtTextPosition index;
        !           308:     PiecePtr piece;
        !           309:     char    c;
        !           310:     int     ddir, doff, i, whiteSpace;
        !           311:     ddir = (dir == XtsdRight) ? 1 : -1;
        !           312:     doff = (dir == XtsdRight) ? 0 : -1;
        !           313: 
        !           314:     index = position;
        !           315:     piece = data->piece;
        !           316:     if (!piece->buf) ReadPiece(data, 0);
        !           317:     switch (sType) {
        !           318:        case XtstPositions:
        !           319:            if (!include && count > 0)
        !           320:                count--;
        !           321:            index = CoerceToLegalPosition(data, index + count * ddir);
        !           322:            break;
        !           323:        case XtstWhiteSpace:
        !           324:            for (i = 0; i < count; i++) {
        !           325:                whiteSpace = -1;
        !           326:                while (index >= 0 && index <= data->length) {
        !           327:                    Look(index, c);
        !           328:                    if ((c == ' ') || (c == '\t') || (c == '\n')) {
        !           329:                        if (whiteSpace < 0) whiteSpace = index;
        !           330:                    } else if (whiteSpace >= 0)
        !           331:                        break;
        !           332:                    index += ddir;
        !           333:                }
        !           334:            }
        !           335:            if (!include) {
        !           336:                if (whiteSpace < 0 && dir == XtsdRight)
        !           337:                    whiteSpace = data->length;
        !           338:                index = whiteSpace;
        !           339:            }
        !           340:            index = CoerceToLegalPosition(data, index);
        !           341:            break;
        !           342:        case XtstEOL:
        !           343:            for (i = 0; i < count; i++) {
        !           344:                while (index >= 0 && index <= data->length) {
        !           345:                    Look(index, c);
        !           346:                    if (c == '\n')
        !           347:                        break;
        !           348:                    index += ddir;
        !           349:                }
        !           350:                if (i < count - 1)
        !           351:                    index += ddir;
        !           352:            }
        !           353:            if (include)
        !           354:                index += ddir;
        !           355:            index = CoerceToLegalPosition(data, index);
        !           356:            break;
        !           357:        case XtstFile:
        !           358:            if (dir == XtsdLeft)
        !           359:                index = 0;
        !           360:            else
        !           361:                index = data->length;
        !           362:            break;
        !           363:     }
        !           364:     return index;
        !           365: }
        !           366: 
        !           367: 
        !           368: /* Return what the editting mode of this source is. */
        !           369: 
        !           370: static XtEditType EDiskGetEditType(src)
        !           371: XtTextSource *src;
        !           372: {
        !           373:     EDiskPtr data = (EDiskPtr) src->data;
        !           374:     return data->editMode;
        !           375: }
        !           376: 
        !           377: 
        !           378: 
        !           379: /* Public definitions. */
        !           380: 
        !           381: /* Create a new text source from the given file, and the given edit mode. */
        !           382: 
        !           383: XtTextSource *XtCreateEDiskSource(name, mode)
        !           384:   char *name;
        !           385:   XtEditType mode;
        !           386: {
        !           387:     XtTextSource * src;
        !           388:     EDiskPtr data;
        !           389:     struct stat stat;
        !           390:     int     i;
        !           391:     src = (XtTextSource *) XtMalloc(sizeof(XtTextSource));
        !           392:     src->read = EDiskReadText;
        !           393:     src->replace = EDiskReplaceText;
        !           394:     src->getLastPos = EDiskGetLastPos;
        !           395:     src->setLastPos = EDiskSetLastPos;
        !           396:     src->editType = EDiskGetEditType;
        !           397:     src->scan = EDiskScan;
        !           398:     data = (EDiskPtr) XtMalloc(sizeof(EDiskRec));
        !           399:     src->data = (int *) data;
        !           400:     data->editMode = mode;
        !           401:     data->file = myopen(name, O_RDONLY | O_CREAT, 0666);
        !           402:     data->name = strcpy(XtMalloc((unsigned)strlen(name) + 1), name);
        !           403:     (void)fstat(data->file, &stat);
        !           404:     data->length = data->origlength = stat.st_size;
        !           405:     data->numpieces = (data->length + BUFSIZE - 1) / BUFSIZE;
        !           406:     if (data->numpieces < 1)
        !           407:        data->numpieces = 1;
        !           408:     data->piece = (PiecePtr)
        !           409:        XtMalloc((unsigned) data->numpieces * sizeof(PieceRec));
        !           410:     for (i = 0; i < data->numpieces; i++) {
        !           411:        data->piece[i].buf = 0;
        !           412:        data->piece[i].length = BUFSIZE;
        !           413:        data->piece[i].start = i * BUFSIZE;
        !           414:     }
        !           415:     data->piece[data->numpieces - 1].length -=
        !           416:        data->numpieces * BUFSIZE - data->length;
        !           417:     data->startsvalid = TRUE;
        !           418:     data->changed = data->everchanged = data->eversaved = FALSE;
        !           419:     data->checkpointed = data->checkpointchange = FALSE;
        !           420:     data->func = NULL;
        !           421:     if (data->length == 0) {
        !           422:        data->piece[0].buf = XtMalloc(1);
        !           423:        data->piece[0].maxlength = 1;
        !           424:     }
        !           425:     return src;
        !           426: }
        !           427: 
        !           428: 
        !           429: 
        !           430: /* Change the editing mode of a source. */
        !           431: 
        !           432: XtEDiskChangeEditMode(src, editMode)
        !           433: XtTextSource *src;
        !           434: XtEditType editMode;
        !           435: {
        !           436:     EDiskPtr data = (EDiskPtr) src->data;
        !           437:     data->editMode = editMode;
        !           438: }
        !           439: 
        !           440: 
        !           441: 
        !           442: /* Call the given function with the given parameter whenever the source is
        !           443:    changed. */
        !           444: 
        !           445: XtEDiskSetCallbackWhenChanged(src, func, param)
        !           446: XtTextSource *src;
        !           447: void (*func)();
        !           448: caddr_t param;
        !           449: {
        !           450:     EDiskPtr data = (EDiskPtr) src->data;
        !           451:     data->func = func;
        !           452:     data->param = param;
        !           453: }
        !           454: 
        !           455: 
        !           456: /* Return whether any unsaved changes have been made. */
        !           457: 
        !           458: XtEDiskChanged(src)
        !           459:   XtTextSource *src;
        !           460: {
        !           461:     return ((EDiskPtr) src->data)->changed;
        !           462: }
        !           463: 
        !           464: 
        !           465: /* Save any unsaved changes. */
        !           466: 
        !           467: XtEDiskSaveFile(src)
        !           468:   XtTextSource *src;
        !           469: {
        !           470:     EDiskPtr data = (EDiskPtr) src->data;
        !           471:     PiecePtr piece;
        !           472:     int     i, needlseek;
        !           473:     char str[1024];
        !           474:     if (!data->changed)
        !           475:        return;
        !           476:     data->changed = data->checkpointchange = FALSE;
        !           477:     if (!data->eversaved) {
        !           478:        data->eversaved = TRUE; /* Open file in a mode where we can write. */
        !           479:        (void)myclose(data->file);
        !           480:        data->file = myopen(data->name, O_RDWR, 0666);
        !           481:     }
        !           482:     if (!data->startsvalid)
        !           483:        CalculateStarts(data);
        !           484:     for (i = 0, piece = data->piece; i < data->numpieces; i++, piece++) {
        !           485:        if (!(piece->buf) && i * BUFSIZE != piece->start)
        !           486:            ReadPiece(data, i);
        !           487:     }
        !           488:     needlseek = TRUE;
        !           489:     for (i = 0, piece = data->piece; i < data->numpieces; i++, piece++) {
        !           490:        if (piece->buf) {
        !           491:            if (needlseek) {
        !           492:                (void) lseek(data->file, (long) piece->start, 0);
        !           493:                needlseek = FALSE;
        !           494:            }
        !           495:            (void) write(data->file, piece->buf, piece->length);
        !           496:        }
        !           497:        else
        !           498:            needlseek = TRUE;
        !           499:     }
        !           500:     if (data->length < data->origlength)
        !           501:        (void)ftruncate(data->file, data->length);
        !           502:     data->origlength = data->length;
        !           503:     if (data->checkpointed) {
        !           504:        (void) sprintf(str, "%s.CKP", data->name);
        !           505:        (void) unlink(str);
        !           506:        data->checkpointed = FALSE;
        !           507:     }
        !           508: }
        !           509: 
        !           510: 
        !           511: /* Save into a new file. */
        !           512: 
        !           513: XtEDiskSaveAsFile(src, filename)
        !           514:   XtTextSource *src;
        !           515:   char *filename;
        !           516: {
        !           517:     EDiskPtr data = (EDiskPtr) src->data;
        !           518:     int     i;
        !           519:     PiecePtr piece;
        !           520:     if (strcmp(filename, data->name) != 0) {
        !           521:        for (i = 0, piece = data->piece; i < data->numpieces; i++, piece++)
        !           522:            if (!(piece->buf))
        !           523:                ReadPiece(data, i);
        !           524:        data->eversaved = FALSE;
        !           525:        data->origlength = 0;
        !           526:        data->file = creat(filename, 0666);
        !           527:        data->eversaved = TRUE;
        !           528:        data->origlength = 0;
        !           529:        XtFree(data->name);
        !           530:        data->name = strcpy(XtMalloc((unsigned) strlen(filename) + 1),
        !           531:                            filename);
        !           532:        data->changed = TRUE;
        !           533:     }
        !           534:     XtEDiskSaveFile(src);
        !           535: }
        !           536: 
        !           537: 
        !           538: 
        !           539: /* Checkpoint this file. */
        !           540: 
        !           541: XtEDiskMakeCheckpoint(src)
        !           542:   XtTextSource *src;
        !           543: {
        !           544:     EDiskPtr data = (EDiskPtr) src->data;
        !           545:     char name[1024];
        !           546:     int fid, i;
        !           547:     PiecePtr piece;
        !           548:     if (!data->checkpointchange) return; /* No changes to save. */
        !           549:     (void) sprintf(name, "%s.CKP", data->name);
        !           550:     fid = creat(name, 0600);
        !           551:     if (fid < 0) return;
        !           552:     for (i = 0, piece = data->piece; i < data->numpieces; i++, piece++) {
        !           553:        if (!(piece->buf)) ReadPiece(data, i);
        !           554:        (void) write(fid, piece->buf, piece->length);
        !           555:     }
        !           556:     (void)close(fid);
        !           557:     data->checkpointed = TRUE;
        !           558:     data->checkpointchange = FALSE;
        !           559: }
        !           560: 
        !           561: 
        !           562: /* We're done with this source, XtFree its storage. */
        !           563: 
        !           564: XtDestroyEDiskSource(src)
        !           565:   XtTextSource *src;
        !           566: {
        !           567:     EDiskPtr data = (EDiskPtr) src->data;
        !           568:     int i;
        !           569:     char *ptr;
        !           570:     (void)myclose(data->file);
        !           571:     for (i=0 ; i<data->numpieces ; i++)
        !           572:        if (ptr = data->piece[i].buf) XtFree(ptr);
        !           573:     XtFree((char *)data->piece);
        !           574:     XtFree((char *)data->name);
        !           575:     XtFree((char *)data);
        !           576:     XtFree((char *)src);
        !           577: }

unix.superglobalmegacorp.com

This archive runs on limited infrastructure. Preserving old code on modern bandwidth. Automated agents are requested to crawl responsibly.