intl/icu/source/io/ustdio.c

Wed, 31 Dec 2014 06:09:35 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Wed, 31 Dec 2014 06:09:35 +0100
changeset 0
6474c204b198
permissions
-rw-r--r--

Cloned upstream origin tor-browser at tor-browser-31.3.0esr-4.5-1-build1
revision ID fc1c9ff7c1b2defdbc039f12214767608f46423f for hacking purpose.

     1 /*
     2  ******************************************************************************
     3  *
     4  *   Copyright (C) 1998-2011, International Business Machines
     5  *   Corporation and others.  All Rights Reserved.
     6  *
     7  ******************************************************************************
     8  *
     9  * File ustdio.c
    10  *
    11  * Modification History:
    12  *
    13  *   Date        Name        Description
    14  *   11/18/98    stephen     Creation.
    15  *   03/12/99    stephen     Modified for new C API.
    16  *   07/19/99    stephen     Fixed read() and gets()
    17  ******************************************************************************
    18  */
    20 #include "unicode/ustdio.h"
    21 #include "unicode/putil.h"
    22 #include "cmemory.h"
    23 #include "cstring.h"
    24 #include "ufile.h"
    25 #include "ufmt_cmn.h"
    26 #include "unicode/ucnv.h"
    27 #include "unicode/ustring.h"
    29 #include <string.h>
    31 #define DELIM_LF 0x000A
    32 #define DELIM_VT 0x000B
    33 #define DELIM_FF 0x000C
    34 #define DELIM_CR 0x000D
    35 #define DELIM_NEL 0x0085
    36 #define DELIM_LS 0x2028
    37 #define DELIM_PS 0x2029
    39 /* TODO: is this correct for all codepages? Should we just use \n and let the converter handle it? */
    40 #if U_PLATFORM_USES_ONLY_WIN32_API
    41 static const UChar DELIMITERS [] = { DELIM_CR, DELIM_LF, 0x0000 };
    42 static const uint32_t DELIMITERS_LEN = 2;
    43 /* TODO: Default newline writing should be detected based upon the converter being used. */
    44 #else
    45 static const UChar DELIMITERS [] = { DELIM_LF, 0x0000 };
    46 static const uint32_t DELIMITERS_LEN = 1;
    47 #endif
    49 #define IS_FIRST_STRING_DELIMITER(c1) \
    50  (UBool)((DELIM_LF <= (c1) && (c1) <= DELIM_CR) \
    51         || (c1) == DELIM_NEL \
    52         || (c1) == DELIM_LS \
    53         || (c1) == DELIM_PS)
    54 #define CAN_HAVE_COMBINED_STRING_DELIMITER(c1) (UBool)((c1) == DELIM_CR)
    55 #define IS_COMBINED_STRING_DELIMITER(c1, c2) \
    56  (UBool)((c1) == DELIM_CR && (c2) == DELIM_LF)
    59 #if !UCONFIG_NO_TRANSLITERATION
    61 U_CAPI UTransliterator* U_EXPORT2
    62 u_fsettransliterator(UFILE *file, UFileDirection direction,
    63                      UTransliterator *adopt, UErrorCode *status)
    64 {
    65     UTransliterator *old = NULL;
    67     if(U_FAILURE(*status))
    68     {
    69         return adopt;
    70     }
    72     if(!file)
    73     {
    74         *status = U_ILLEGAL_ARGUMENT_ERROR;
    75         return adopt;
    76     }
    78     if(direction & U_READ)
    79     {
    80         /** TODO: implement */
    81         *status = U_UNSUPPORTED_ERROR;
    82         return adopt;
    83     }
    85     if(adopt == NULL) /* they are clearing it */
    86     {
    87         if(file->fTranslit != NULL)
    88         {
    89             /* TODO: Check side */
    90             old = file->fTranslit->translit;
    91             uprv_free(file->fTranslit->buffer);
    92             file->fTranslit->buffer=NULL;
    93             uprv_free(file->fTranslit);
    94             file->fTranslit=NULL;
    95         }
    96     }
    97     else
    98     {
    99         if(file->fTranslit == NULL)
   100         {
   101             file->fTranslit = (UFILETranslitBuffer*) uprv_malloc(sizeof(UFILETranslitBuffer));
   102             if(!file->fTranslit)
   103             {
   104                 *status = U_MEMORY_ALLOCATION_ERROR;
   105                 return adopt;
   106             }
   107             file->fTranslit->capacity = 0;
   108             file->fTranslit->length = 0;
   109             file->fTranslit->pos = 0;
   110             file->fTranslit->buffer = NULL;
   111         }
   112         else
   113         {
   114             old = file->fTranslit->translit;
   115             ufile_flush_translit(file);
   116         }
   118         file->fTranslit->translit = adopt;
   119     }
   121     return old;
   122 }
   124 static const UChar * u_file_translit(UFILE *f, const UChar *src, int32_t *count, UBool flush)
   125 {
   126     int32_t newlen;
   127     int32_t junkCount = 0;
   128     int32_t textLength;
   129     int32_t textLimit;
   130     UTransPosition pos;
   131     UErrorCode status = U_ZERO_ERROR;
   133     if(count == NULL)
   134     {
   135         count = &junkCount;
   136     }
   138     if ((!f)||(!f->fTranslit)||(!f->fTranslit->translit))
   139     {
   140         /* fast path */
   141         return src;
   142     }
   144     /* First: slide over everything */
   145     if(f->fTranslit->length > f->fTranslit->pos)
   146     {
   147         memmove(f->fTranslit->buffer, f->fTranslit->buffer + f->fTranslit->pos,
   148             (f->fTranslit->length - f->fTranslit->pos)*sizeof(UChar));
   149     }
   150     f->fTranslit->length -= f->fTranslit->pos; /* always */
   151     f->fTranslit->pos = 0;
   153     /* Calculate new buffer size needed */
   154     newlen = (*count + f->fTranslit->length) * 4;
   156     if(newlen > f->fTranslit->capacity)
   157     {
   158         if(f->fTranslit->buffer == NULL)
   159         {
   160             f->fTranslit->buffer = (UChar*)uprv_malloc(newlen * sizeof(UChar));
   161         }
   162         else
   163         {
   164             f->fTranslit->buffer = (UChar*)uprv_realloc(f->fTranslit->buffer, newlen * sizeof(UChar));
   165         }
   166         /* Check for malloc/realloc failure. */
   167         if (f->fTranslit->buffer == NULL) {
   168         	return NULL;
   169         }
   170         f->fTranslit->capacity = newlen;
   171     }
   173     /* Now, copy any data over */
   174     u_strncpy(f->fTranslit->buffer + f->fTranslit->length,
   175         src,
   176         *count);
   177     f->fTranslit->length += *count;
   179     /* Now, translit in place as much as we can  */
   180     if(flush == FALSE)
   181     {
   182         textLength = f->fTranslit->length;
   183         pos.contextStart = 0;
   184         pos.contextLimit = textLength;
   185         pos.start        = 0;
   186         pos.limit        = textLength;
   188         utrans_transIncrementalUChars(f->fTranslit->translit,
   189             f->fTranslit->buffer, /* because we shifted */
   190             &textLength,
   191             f->fTranslit->capacity,
   192             &pos,
   193             &status);
   195         /* now: start/limit point to the transliterated text */
   196         /* Transliterated is [buffer..pos.start) */
   197         *count            = pos.start;
   198         f->fTranslit->pos = pos.start;
   199         f->fTranslit->length = pos.limit;
   201         return f->fTranslit->buffer;
   202     }
   203     else
   204     {
   205         textLength = f->fTranslit->length;
   206         textLimit = f->fTranslit->length;
   208         utrans_transUChars(f->fTranslit->translit,
   209             f->fTranslit->buffer,
   210             &textLength,
   211             f->fTranslit->capacity,
   212             0,
   213             &textLimit,
   214             &status);
   216         /* out: converted len */
   217         *count = textLimit;
   219         /* Set pointers to 0 */
   220         f->fTranslit->pos = 0;
   221         f->fTranslit->length = 0;
   223         return f->fTranslit->buffer;
   224     }
   225 }
   227 #endif
   229 void
   230 ufile_flush_translit(UFILE *f)
   231 {
   232 #if !UCONFIG_NO_TRANSLITERATION
   233     if((!f)||(!f->fTranslit))
   234         return;
   235 #endif
   237     u_file_write_flush(NULL, 0, f, FALSE, TRUE);
   238 }
   241 void
   242 ufile_flush_io(UFILE *f)
   243 {
   244   if((!f) || (!f->fFile)) {
   245     return; /* skip if no file */
   246   }
   248   u_file_write_flush(NULL, 0, f, TRUE, FALSE);
   249 }
   252 void
   253 ufile_close_translit(UFILE *f)
   254 {
   255 #if !UCONFIG_NO_TRANSLITERATION
   256     if((!f)||(!f->fTranslit))
   257         return;
   258 #endif
   260     ufile_flush_translit(f);
   262 #if !UCONFIG_NO_TRANSLITERATION
   263     if(f->fTranslit->translit)
   264         utrans_close(f->fTranslit->translit);
   266     if(f->fTranslit->buffer)
   267     {
   268         uprv_free(f->fTranslit->buffer);
   269     }
   271     uprv_free(f->fTranslit);
   272     f->fTranslit = NULL;
   273 #endif
   274 }
   277 /* Input/output */
   279 U_CAPI int32_t U_EXPORT2 /* U_CAPI ... U_EXPORT2 added by Peter Kirk 17 Nov 2001 */
   280 u_fputs(const UChar    *s,
   281         UFILE        *f)
   282 {
   283     int32_t count = u_file_write(s, u_strlen(s), f);
   284     count += u_file_write(DELIMITERS, DELIMITERS_LEN, f);
   285     return count;
   286 }
   288 U_CAPI UChar32 U_EXPORT2 /* U_CAPI ... U_EXPORT2 added by Peter Kirk 17 Nov 2001 */
   289 u_fputc(UChar32      uc,
   290         UFILE        *f)
   291 {
   292     UChar buf[2];
   293     int32_t idx = 0;
   294     UBool isError = FALSE;
   296     U16_APPEND(buf, idx, sizeof(buf)/sizeof(*buf), uc, isError);
   297     if (isError) {
   298         return U_EOF;
   299     }
   300     return u_file_write(buf, idx, f) == idx ? uc : U_EOF;
   301 }
   304 U_CFUNC int32_t U_EXPORT2
   305 u_file_write_flush(const UChar *chars,
   306                    int32_t     count,
   307                    UFILE       *f,
   308                    UBool       flushIO,
   309                    UBool       flushTranslit)
   310 {
   311     /* Set up conversion parameters */
   312     UErrorCode  status       = U_ZERO_ERROR;
   313     const UChar *mySource    = chars;
   314     const UChar *mySourceBegin; 
   315     const UChar *mySourceEnd;
   316     char        charBuffer[UFILE_CHARBUFFER_SIZE];
   317     char        *myTarget   = charBuffer;
   318     int32_t     written      = 0;
   319     int32_t     numConverted = 0;
   321     if (count < 0) {
   322         count = u_strlen(chars);
   323     }
   325 #if !UCONFIG_NO_TRANSLITERATION
   326     if((f->fTranslit) && (f->fTranslit->translit))
   327     {
   328         /* Do the transliteration */
   329         mySource = u_file_translit(f, chars, &count, flushTranslit);
   330     }
   331 #endif
   333     /* Write to a string. */
   334     if (!f->fFile) {
   335         int32_t charsLeft = (int32_t)(f->str.fLimit - f->str.fPos);
   336         if (flushIO && charsLeft > count) {
   337             count++;
   338         }
   339         written = ufmt_min(count, charsLeft);
   340         u_strncpy(f->str.fPos, mySource, written);
   341         f->str.fPos += written;
   342         return written;
   343     }
   345     mySourceEnd = mySource + count;
   347     /* Perform the conversion in a loop */
   348     do {
   349         mySourceBegin = mySource; /* beginning location for this loop */
   350         status     = U_ZERO_ERROR;
   351         if(f->fConverter != NULL) { /* We have a valid converter */
   352             ucnv_fromUnicode(f->fConverter,
   353                 &myTarget,
   354                 charBuffer + UFILE_CHARBUFFER_SIZE,
   355                 &mySource,
   356                 mySourceEnd,
   357                 NULL,
   358                 flushIO,
   359                 &status);
   360         } else { /*weiv: do the invariant conversion */
   361             int32_t convertChars = (int32_t) (mySourceEnd - mySource); 
   362             if (convertChars > UFILE_CHARBUFFER_SIZE) { 
   363                 convertChars = UFILE_CHARBUFFER_SIZE; 
   364                 status = U_BUFFER_OVERFLOW_ERROR; 
   365             } 
   366             u_UCharsToChars(mySource, myTarget, convertChars); 
   367             mySource += convertChars; 
   368             myTarget += convertChars; 
   369         }
   370         numConverted = (int32_t)(myTarget - charBuffer);
   372         if (numConverted > 0) {
   373             /* write the converted bytes */
   374             fwrite(charBuffer,
   375                 sizeof(char),
   376                 numConverted,
   377                 f->fFile);
   379             written     += (int32_t) (mySource - mySourceBegin);
   380         }
   381         myTarget     = charBuffer;
   382     }
   383     while(status == U_BUFFER_OVERFLOW_ERROR);
   385     /* return # of chars written */
   386     return written;
   387 }
   389 U_CAPI int32_t U_EXPORT2 /* U_CAPI ... U_EXPORT2 added by Peter Kirk 17 Nov 2001 */
   390 u_file_write(    const UChar     *chars,
   391              int32_t        count,
   392              UFILE         *f)
   393 {
   394     return u_file_write_flush(chars,count,f,FALSE,FALSE);
   395 }
   398 /* private function used for buffering input */
   399 void
   400 ufile_fill_uchar_buffer(UFILE *f)
   401 {
   402     UErrorCode  status;
   403     const char  *mySource;
   404     const char  *mySourceEnd;
   405     UChar       *myTarget;
   406     int32_t     bufferSize;
   407     int32_t     maxCPBytes;
   408     int32_t     bytesRead;
   409     int32_t     availLength;
   410     int32_t     dataSize;
   411     char        charBuffer[UFILE_CHARBUFFER_SIZE];
   412     u_localized_string *str;
   414     if (f->fFile == NULL) {
   415         /* There is nothing to do. It's a string. */
   416         return;
   417     }
   419     str = &f->str;
   420     dataSize = (int32_t)(str->fLimit - str->fPos);
   421     if (f->fFileno == 0 && dataSize > 0) {
   422         /* Don't read from stdin too many times. There is still some data. */
   423         return;
   424     }
   426     /* shift the buffer if it isn't empty */
   427     if(dataSize != 0) {
   428         uprv_memmove(f->fUCBuffer, str->fPos, dataSize * sizeof(UChar)); /* not accessing beyond memory */
   429     }
   432     /* record how much buffer space is available */
   433     availLength = UFILE_UCHARBUFFER_SIZE - dataSize;
   435     /* Determine the # of codepage bytes needed to fill our UChar buffer */
   436     /* weiv: if converter is NULL, we use invariant converter with charwidth = 1)*/
   437     maxCPBytes = availLength / (f->fConverter!=NULL?(2*ucnv_getMinCharSize(f->fConverter)):1);
   439     /* Read in the data to convert */
   440     if (f->fFileno == 0) {
   441         /* Special case. Read from stdin one line at a time. */
   442         char *retStr = fgets(charBuffer, ufmt_min(maxCPBytes, UFILE_CHARBUFFER_SIZE), f->fFile);
   443         bytesRead = (int32_t)(retStr ? uprv_strlen(charBuffer) : 0);
   444     }
   445     else {
   446         /* A normal file */
   447         bytesRead = (int32_t)fread(charBuffer,
   448             sizeof(char),
   449             ufmt_min(maxCPBytes, UFILE_CHARBUFFER_SIZE),
   450             f->fFile);
   451     }
   453     /* Set up conversion parameters */
   454     status      = U_ZERO_ERROR;
   455     mySource    = charBuffer;
   456     mySourceEnd = charBuffer + bytesRead;
   457     myTarget    = f->fUCBuffer + dataSize;
   458     bufferSize  = UFILE_UCHARBUFFER_SIZE;
   460     if(f->fConverter != NULL) { /* We have a valid converter */
   461         /* Perform the conversion */
   462         ucnv_toUnicode(f->fConverter,
   463             &myTarget,
   464             f->fUCBuffer + bufferSize,
   465             &mySource,
   466             mySourceEnd,
   467             NULL,
   468             (UBool)(feof(f->fFile) != 0),
   469             &status);
   471     } else { /*weiv: do the invariant conversion */
   472         u_charsToUChars(mySource, myTarget, bytesRead);
   473         myTarget += bytesRead;
   474     }
   476     /* update the pointers into our array */
   477     str->fPos    = str->fBuffer;
   478     str->fLimit  = myTarget;
   479 }
   481 U_CAPI UChar* U_EXPORT2 /* U_CAPI ... U_EXPORT2 added by Peter Kirk 17 Nov 2001 */
   482 u_fgets(UChar        *s,
   483         int32_t       n,
   484         UFILE        *f)
   485 {
   486     int32_t dataSize;
   487     int32_t count;
   488     UChar *alias;
   489     const UChar *limit;
   490     UChar *sItr;
   491     UChar currDelim = 0;
   492     u_localized_string *str;
   494     if (n <= 0) {
   495         /* Caller screwed up. We need to write the null terminatior. */
   496         return NULL;
   497     }
   499     /* fill the buffer if needed */
   500     str = &f->str;
   501     if (str->fPos >= str->fLimit) {
   502         ufile_fill_uchar_buffer(f);
   503     }
   505     /* subtract 1 from n to compensate for the terminator */
   506     --n;
   508     /* determine the amount of data in the buffer */
   509     dataSize = (int32_t)(str->fLimit - str->fPos);
   511     /* if 0 characters were left, return 0 */
   512     if (dataSize == 0)
   513         return NULL;
   515     /* otherwise, iteratively fill the buffer and copy */
   516     count = 0;
   517     sItr = s;
   518     currDelim = 0;
   519     while (dataSize > 0 && count < n) {
   520         alias = str->fPos;
   522         /* Find how much to copy */
   523         if (dataSize < (n - count)) {
   524             limit = str->fLimit;
   525         }
   526         else {
   527             limit = alias + (n - count);
   528         }
   530         if (!currDelim) {
   531             /* Copy UChars until we find the first occurrence of a delimiter character */
   532             while (alias < limit && !IS_FIRST_STRING_DELIMITER(*alias)) {
   533                 count++;
   534                 *(sItr++) = *(alias++);
   535             }
   536             /* Preserve the newline */
   537             if (alias < limit && IS_FIRST_STRING_DELIMITER(*alias)) {
   538                 if (CAN_HAVE_COMBINED_STRING_DELIMITER(*alias)) {
   539                     currDelim = *alias;
   540                 }
   541                 else {
   542                     currDelim = 1;  /* This isn't a newline, but it's used to say
   543                                     that we should break later. We've checked all
   544                                     possible newline combinations even across buffer
   545                                     boundaries. */
   546                 }
   547                 count++;
   548                 *(sItr++) = *(alias++);
   549             }
   550         }
   551         /* If we have a CRLF combination, preserve that too. */
   552         if (alias < limit) {
   553             if (currDelim && IS_COMBINED_STRING_DELIMITER(currDelim, *alias)) {
   554                 count++;
   555                 *(sItr++) = *(alias++);
   556             }
   557             currDelim = 1;  /* This isn't a newline, but it's used to say
   558                             that we should break later. We've checked all
   559                             possible newline combinations even across buffer
   560                             boundaries. */
   561         }
   563         /* update the current buffer position */
   564         str->fPos = alias;
   566         /* if we found a delimiter */
   567         if (currDelim == 1) {
   568             /* break out */
   569             break;
   570         }
   572         /* refill the buffer */
   573         ufile_fill_uchar_buffer(f);
   575         /* determine the amount of data in the buffer */
   576         dataSize = (int32_t)(str->fLimit - str->fPos);
   577     }
   579     /* add the terminator and return s */
   580     *sItr = 0x0000;
   581     return s;
   582 }
   584 U_CFUNC UBool U_EXPORT2
   585 ufile_getch(UFILE *f, UChar *ch)
   586 {
   587     UBool isValidChar = FALSE;
   589     *ch = U_EOF;
   590     /* if we have an available character in the buffer, return it */
   591     if(f->str.fPos < f->str.fLimit){
   592         *ch = *(f->str.fPos)++;
   593         isValidChar = TRUE;
   594     }
   595     else {
   596         /* otherwise, fill the buffer and return the next character */
   597         if(f->str.fPos >= f->str.fLimit) {
   598             ufile_fill_uchar_buffer(f);
   599         }
   600         if(f->str.fPos < f->str.fLimit) {
   601             *ch = *(f->str.fPos)++;
   602             isValidChar = TRUE;
   603         }
   604     }
   605     return isValidChar;
   606 }
   608 U_CAPI UChar U_EXPORT2 /* U_CAPI ... U_EXPORT2 added by Peter Kirk 17 Nov 2001 */
   609 u_fgetc(UFILE        *f)
   610 {
   611     UChar ch;
   612     ufile_getch(f, &ch);
   613     return ch;
   614 }
   616 U_CFUNC UBool U_EXPORT2
   617 ufile_getch32(UFILE *f, UChar32 *c32)
   618 {
   619     UBool isValidChar = FALSE;
   620     u_localized_string *str;
   622     *c32 = U_EOF;
   624     /* Fill the buffer if it is empty */
   625     str = &f->str;
   626     if (f && str->fPos + 1 >= str->fLimit) {
   627         ufile_fill_uchar_buffer(f);
   628     }
   630     /* Get the next character in the buffer */
   631     if (str->fPos < str->fLimit) {
   632         *c32 = *(str->fPos)++;
   633         if (U_IS_LEAD(*c32)) {
   634             if (str->fPos < str->fLimit) {
   635                 UChar c16 = *(str->fPos)++;
   636                 *c32 = U16_GET_SUPPLEMENTARY(*c32, c16);
   637                 isValidChar = TRUE;
   638             }
   639             else {
   640                 *c32 = U_EOF;
   641             }
   642         }
   643         else {
   644             isValidChar = TRUE;
   645         }
   646     }
   648     return isValidChar;
   649 }
   651 U_CAPI UChar32 U_EXPORT2 /* U_CAPI ... U_EXPORT2 added by Peter Kirk 17 Nov 2001 */
   652 u_fgetcx(UFILE        *f)
   653 {
   654     UChar32 ch;
   655     ufile_getch32(f, &ch);
   656     return ch;
   657 }
   659 U_CAPI UChar32 U_EXPORT2 /* U_CAPI ... U_EXPORT2 added by Peter Kirk 17 Nov 2001 */
   660 u_fungetc(UChar32        ch,
   661     UFILE        *f)
   662 {
   663     u_localized_string *str;
   665     str = &f->str;
   667     /* if we're at the beginning of the buffer, sorry! */
   668     if (str->fPos == str->fBuffer
   669         || (U_IS_LEAD(ch) && (str->fPos - 1) == str->fBuffer))
   670     {
   671         ch = U_EOF;
   672     }
   673     else {
   674         /* otherwise, put the character back */
   675         /* Remember, read them back on in the reverse order. */
   676         if (U_IS_LEAD(ch)) {
   677             if (*--(str->fPos) != U16_TRAIL(ch)
   678                 || *--(str->fPos) != U16_LEAD(ch))
   679             {
   680                 ch = U_EOF;
   681             }
   682         }
   683         else if (*--(str->fPos) != ch) {
   684             ch = U_EOF;
   685         }
   686     }
   687     return ch;
   688 }
   690 U_CAPI int32_t U_EXPORT2 /* U_CAPI ... U_EXPORT2 added by Peter Kirk 17 Nov 2001 */
   691 u_file_read(    UChar        *chars,
   692     int32_t        count,
   693     UFILE         *f)
   694 {
   695     int32_t dataSize;
   696     int32_t read = 0;
   697     u_localized_string *str = &f->str;
   699     do {
   701         /* determine the amount of data in the buffer */
   702         dataSize = (int32_t)(str->fLimit - str->fPos);
   703         if (dataSize <= 0) {
   704             /* fill the buffer */
   705             ufile_fill_uchar_buffer(f);
   706             dataSize = (int32_t)(str->fLimit - str->fPos);
   707         }
   709         /* Make sure that we don't read too much */
   710         if (dataSize > (count - read)) {
   711             dataSize = count - read;
   712         }
   714         /* copy the current data in the buffer */
   715         memcpy(chars + read, str->fPos, dataSize * sizeof(UChar));
   717         /* update number of items read */
   718         read += dataSize;
   720         /* update the current buffer position */
   721         str->fPos += dataSize;
   722     }
   723     while (dataSize != 0 && read < count);
   725     return read;
   726 }

mercurial