Login
Check-in [2d90220116]
Login

Many hyperlinks are disabled.
Use anonymous login to enable hyperlinks.

Overview
Comment:Improved the checkin-to-closed-leaf check to allow checkin if the branch would change. Ported in the reserved filename checks, so that 'add' ops will fail if the user adds a filename which is illegal.
Downloads: Tarball | ZIP archive | SQL archive
Timelines: family | ancestors | descendants | both | trunk
Files: files | file ages | folders
SHA1: 2d90220116bb90644f9ac14dd39f078566f5303d
User & Date: stephan 2021-03-01 12:31:47
Context
2021-03-01
13:45
Renamed FCliCommand to fcli_command for consistency's sake. check-in: 92aa86396d user: stephan tags: trunk
12:31
Improved the checkin-to-closed-leaf check to allow checkin if the branch would change. Ported in the reserved filename checks, so that 'add' ops will fail if the user adds a filename which is illegal. check-in: 2d90220116 user: stephan tags: trunk
07:57
Corrected the sameLine() merge-internal algo, so merge collisions are now detected. Minor internal merge/merge-adjacent cleanups. check-in: ecfc36587c user: stephan tags: trunk
Changes
Hide Diffs Unified Diffs Ignore Whitespace Patch

Changes to include/fossil-scm/fossil-checkout.h.

499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
     The user name for the checkin. If NULL or empty, it defaults to
     fsl_cx_user_get(). If that is NULL, a FSL_RC_RANGE error is
     triggered.
  */
  char const * user;

  /**
     Don't use this yet - it is not yet tested all that well.

     If not NULL, makes the checkin the start of a new branch with
     this name.
  */
  char const * branch;

  /**
     If this->branch is not NULL, this is applied as its "bgcolor"







<
<







499
500
501
502
503
504
505


506
507
508
509
510
511
512
     The user name for the checkin. If NULL or empty, it defaults to
     fsl_cx_user_get(). If that is NULL, a FSL_RC_RANGE error is
     triggered.
  */
  char const * user;

  /**


     If not NULL, makes the checkin the start of a new branch with
     this name.
  */
  char const * branch;

  /**
     If this->branch is not NULL, this is applied as its "bgcolor"

Changes to include/fossil-scm/fossil-internal.h.

1474
1475
1476
1477
1478
1479
1480
1481

1482
1483

1484
1485
1486
1487
1488
1489
1490
1491
1492
1493

1494
1495
1496
1497
1498

1499
1500
1501
1502
1503
1504
1505
1506
1507
1508
1509
1510
1511
1512
1513
   eventually be fsl_free()'d by the caller. On error *outRaw is not
   modified.
*/
FSL_EXPORT int fsl_diff_text_raw(fsl_buffer const *p1, fsl_buffer const *p2,
                                 int diffFlags, int ** outRaw);

/**
   Returns true if p contains a fossil-format merge conflict marker,

   else returns false.
   

   @see fsl_buffer_merge3()
*/
FSL_EXPORT bool fsl_buffer_contains_merge_marker(fsl_buffer const *p);

/**
   Performs a three-way merge.
   
   The merge is an edit against pV2. Both pV1 and pV2 have a common
   origin at pPivot. Apply the changes of pPivot ==> pV1 to pV2,
   appending them to pOut. (Pedantic side-note: the input buffers are

   not const because we need to manipulate their cursors.)

   If merge conflicts are encountered, it continues as best as it can
   and injects "indiscrete" markers in the output to denote the nature
   of each conflict. If conflictCount is not NULL then on success the

   number of merge conflicts is written to *conflictCount.

   Returns 0 on success, FSL_RC_OOM on OOM, FSL_RC_TYPE if any input
   appears to be binary. 

   @see fsl_buffer_contains_merge_marker()
*/
FSL_EXPORT int fsl_buffer_merge3(fsl_buffer *pPivot, fsl_buffer *pV1, fsl_buffer *pV2, fsl_buffer *pOut,
                                 unsigned int *conflictCount);

#if defined(__cplusplus)
} /*extern "C"*/
#endif
#endif
/* NET_FOSSIL_SCM_FSL_INTERNAL_H_INCLUDED */







|
>
|
|
>
|

|


<
|
<
|
|
>
|

<
<
|
>
|
|
|
<
|
<

|
|






1474
1475
1476
1477
1478
1479
1480
1481
1482
1483
1484
1485
1486
1487
1488
1489
1490

1491

1492
1493
1494
1495
1496


1497
1498
1499
1500
1501

1502

1503
1504
1505
1506
1507
1508
1509
1510
1511
   eventually be fsl_free()'d by the caller. On error *outRaw is not
   modified.
*/
FSL_EXPORT int fsl_diff_text_raw(fsl_buffer const *p1, fsl_buffer const *p2,
                                 int diffFlags, int ** outRaw);

/**
   If the given file name is a reserved filename (case-insensitive) on
   Windows platforms, a pointer to the reserved part of the name, else
   NULL is returned.

   zPath must be a canonical path with forward-slash directory
   separators.
*/
FSL_EXPORT bool fsl_is_reserved_fn_windows(const char *zPath);

/**

   Uses fsl_is_reserved_fn() to determine whether zPath is legal.  If

   it is, 0 is returned, else FSL_RC_MISUSE (or FSL_RC_OOM) is
   returned and f's error state is updated to indicate the nature of
   the problem. nFile is the length of zPath. If negative,
   fsl_strlen() is used to determine its length.



   TODO/FIXME: confirm that zPath does not refer to the current
   repository db. In practice those normally live outside of the
   checkout dir, so cannot be added to a repo, but late-2020 additions
   to fossil(1)'s 'open' support makes it easy to have the repo db in
   the checkout dir. (Some people use them that way even without that

   feature.)

*/
FSL_EXPORT int fsl_reserved_fn_check(fsl_cx *f, const char *zPath,
                                     fsl_int_t nFile);

#if defined(__cplusplus)
} /*extern "C"*/
#endif
#endif
/* NET_FOSSIL_SCM_FSL_INTERNAL_H_INCLUDED */

Changes to include/fossil-scm/fossil-repo.h.

2796
2797
2798
2799
2800
2801
2802
















2803
2804
2805
2806
2807
2808
2809
   If FSL_RC_NOT_FOUND is returned and hashOut is not NULL, *hashOut
   is set to the value of f's preferred hash. *ridOut is only modified
   if 0 is returned, in which case *ridOut will have a positive value.
*/
FSL_EXPORT int fsl_repo_blob_lookup( fsl_cx * f, fsl_buffer const * src, fsl_id_t * ridOut,
                                     fsl_uuid_str * hashOut );


















#if 0
/**
   NOT YET IMPLEMENTED - just thinking out loud here.
*/
struct fsl_repo_open_opt {








>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>







2796
2797
2798
2799
2800
2801
2802
2803
2804
2805
2806
2807
2808
2809
2810
2811
2812
2813
2814
2815
2816
2817
2818
2819
2820
2821
2822
2823
2824
2825
   If FSL_RC_NOT_FOUND is returned and hashOut is not NULL, *hashOut
   is set to the value of f's preferred hash. *ridOut is only modified
   if 0 is returned, in which case *ridOut will have a positive value.
*/
FSL_EXPORT int fsl_repo_blob_lookup( fsl_cx * f, fsl_buffer const * src, fsl_id_t * ridOut,
                                     fsl_uuid_str * hashOut );

/**
   Returns true if the specified file name ends with any reserved
   name, e.g.: _FOSSIL_ or .fslckout.

   For the sake of efficiency, zFilename must be a canonical name,
   e.g. an absolute path using only forward slash ('/') as a directory
   separator.

   On Windows builds, this also checks for reserved Windows filenames,
   e.g. "CON" and "PRN".

   nameLen must be the length of zFilename. If it is negative,
   fsl_strlen() is used to calculate it.
*/
FSL_EXPORT bool fsl_is_reserved_fn(const char *zFilename,
                                   fsl_int_t nameLen );

#if 0
/**
   NOT YET IMPLEMENTED - just thinking out loud here.
*/
struct fsl_repo_open_opt {

Changes to include/fossil-scm/fossil-util.h.

4195
4196
4197
4198
4199
4200
4201






























4202
4203
4204
4205
4206

/**

   Frees any memory owned by p, but does not free p.
*/
FSL_EXPORT void fsl_id_bag_clear(fsl_id_bag *p);































#if defined(__cplusplus)
} /*extern "C"*/
#endif
#endif
/* NET_FOSSIL_SCM_FSL_UTIL_H_INCLUDED */







>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>





4195
4196
4197
4198
4199
4200
4201
4202
4203
4204
4205
4206
4207
4208
4209
4210
4211
4212
4213
4214
4215
4216
4217
4218
4219
4220
4221
4222
4223
4224
4225
4226
4227
4228
4229
4230
4231
4232
4233
4234
4235
4236

/**

   Frees any memory owned by p, but does not free p.
*/
FSL_EXPORT void fsl_id_bag_clear(fsl_id_bag *p);


/**
   Returns true if p contains a fossil-format merge conflict marker,
   else returns false.
   
   @see fsl_buffer_merge3()
*/
FSL_EXPORT bool fsl_buffer_contains_merge_marker(fsl_buffer const *p);

/**
   Performs a three-way merge.
   
   The merge is an edit against pV2. Both pV1 and pV2 have a common
   origin at pPivot. Apply the changes of pPivot ==> pV1 to pV2,
   appending them to pOut. (Pedantic side-note: the input buffers are
   not const because we need to manipulate their cursors.)

   If merge conflicts are encountered, it continues as best as it can
   and injects "indiscrete" markers in the output to denote the nature
   of each conflict. If conflictCount is not NULL then on success the
   number of merge conflicts is written to *conflictCount.

   Returns 0 on success, FSL_RC_OOM on OOM, FSL_RC_TYPE if any input
   appears to be binary. 

   @see fsl_buffer_contains_merge_marker()
*/
FSL_EXPORT int fsl_buffer_merge3(fsl_buffer *pPivot, fsl_buffer *pV1, fsl_buffer *pV2, fsl_buffer *pOut,
                                 unsigned int *conflictCount);

#if defined(__cplusplus)
} /*extern "C"*/
#endif
#endif
/* NET_FOSSIL_SCM_FSL_UTIL_H_INCLUDED */

Changes to src/checkin.c.

167
168
169
170
171
172
173















174
175
176
177
178
179
180
181
      fsl_id_t vfid = 0;
      char const * path = fsl_buffer_cstr(&canon);
      char dirCheck;
      fsl_buffer * buf = &f->fsScratch;
      fsl_size_t const oldBagSize = f->ckin.selectedIds.entryCount;
      assert(!buf->used && "Misuse of f->fsScratch");
















      rc = fsl_filename_to_vfile_id(f, path, &vfid);
      if(rc) goto out;
      else if(vfid>0 && fsl_id_bag_contains(&f->ckin.selectedIds, vfid)){
        /* A single file matching a vfile entry... */
        rc = 0;
      }else{
        /* Try interpreting it as a directory... */
        while('/' == canon.mem[canon.used-1]){







>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
|







167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
      fsl_id_t vfid = 0;
      char const * path = fsl_buffer_cstr(&canon);
      char dirCheck;
      fsl_buffer * buf = &f->fsScratch;
      fsl_size_t const oldBagSize = f->ckin.selectedIds.entryCount;
      assert(!buf->used && "Misuse of f->fsScratch");

#if 0
      rc = fsl_reserved_fn_check(f, path, (fsl_int_t)canon.used);
      /*
        Potential problem here: reserved name checking was not added
        to fossil until late 2020, at which point we know for a fact
        that there were older repositories which had managed to add
        _FOSSIL_ db files, and similar, to subdirectories of their
        repos. If we reject such files here, we will prohibit users
        from checking in changes to those files they've already got in
        their repos. Thus we should arguably restrict this check to
        the fsl_checkout_file_add() operation, which would only
        prohibit the addition of new reserved filenames without
        affecting ones which might have slipped in already.
      */
#endif
      if(!rc) rc = fsl_filename_to_vfile_id(f, path, &vfid);
      if(rc) goto out;
      else if(vfid>0 && fsl_id_bag_contains(&f->ckin.selectedIds, vfid)){
        /* A single file matching a vfile entry... */
        rc = 0;
      }else{
        /* Try interpreting it as a directory... */
        while('/' == canon.mem[canon.used-1]){
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
                         ? opt->closeBranch
                         : NULL);
    RC;
  }

  {
    /*
      UNTESTED!

      Close any INTEGRATE merges if !op->integrate, or type-0 and
      integrate merges if opt->integrate.
    */
    rc = fsl_db_prepare(dbC, &q,
                        "SELECT mhash, merge FROM vmerge "
                        " WHERE id %s ORDER BY 1",
                        opt->integrate ? "IN(0,-4)" : "=(-4)");







<
<







957
958
959
960
961
962
963


964
965
966
967
968
969
970
                         ? opt->closeBranch
                         : NULL);
    RC;
  }

  {
    /*


      Close any INTEGRATE merges if !op->integrate, or type-0 and
      integrate merges if opt->integrate.
    */
    rc = fsl_db_prepare(dbC, &q,
                        "SELECT mhash, merge FROM vmerge "
                        " WHERE id %s ORDER BY 1",
                        opt->integrate ? "IN(0,-4)" : "=(-4)");
1014
1015
1016
1017
1018
1019
1020






























1021
1022
1023
1024
1025
1026
1027
1028
1029
1030
1031
1032
1033
1034
1035
1036



1037
1038
1039

1040
1041
1042
1043
1044
1045
1046
1047
                   (fsl_id_t)rid);
  if(rc){
    fsl_cx_uplift_db_error(f, r);
  }
  return rc;
}































int fsl_checkin_commit(fsl_cx * f, fsl_checkin_opt const * opt,
                       fsl_id_t * newRid, fsl_uuid_str * newUuid ){
  int rc;
  fsl_deck deck = fsl_deck_empty;
  fsl_deck *d = &deck;
  fsl_db * dbC;
  fsl_db * dbR;
  char inTrans = 0;
  char oldPrivate;
  int const oldFlags = f ? f->flags : 0;
  fsl_id_t const vid = f ? f->ckout.rid : 0;
  if(!f || !opt) return FSL_RC_MISUSE;
  else if(!(dbC = fsl_needs_checkout(f))) return FSL_RC_NOT_A_CHECKOUT;
  else if(!(dbR = fsl_needs_repo(f))) return FSL_RC_NOT_A_REPO;
  assert(vid>=0);




  if( fsl_db_exists(dbR, "SELECT 1 FROM tagxref"
                    " WHERE tagid=%d "
                    " AND rid=%"FSL_ID_T_PFMT" AND tagtype>0",

                    FSL_TAGID_CLOSED, (fsl_id_t)vid) ){
    return fsl_cx_err_set(f, FSL_RC_MISUSE,
                          "Cannot commit against a closed leaf.");
  }

  fsl_cx_err_reset(f) /* avoid propagating an older error by accident.
                         Did that in test code. */;








>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>















|
>
>
>
|
<
<
>
|







1027
1028
1029
1030
1031
1032
1033
1034
1035
1036
1037
1038
1039
1040
1041
1042
1043
1044
1045
1046
1047
1048
1049
1050
1051
1052
1053
1054
1055
1056
1057
1058
1059
1060
1061
1062
1063
1064
1065
1066
1067
1068
1069
1070
1071
1072
1073
1074
1075
1076
1077
1078
1079
1080
1081
1082
1083


1084
1085
1086
1087
1088
1089
1090
1091
1092
                   (fsl_id_t)rid);
  if(rc){
    fsl_cx_uplift_db_error(f, r);
  }
  return rc;
}


/**
   Returns true if the given blob RID is has a "closed" tag. This is
   generally intended only to be passed the RID of the current
   checkout, before attempting to perform a commit against it.
*/
static bool fsl_leaf_is_closed(fsl_cx * f, fsl_id_t rid){
  fsl_db * const dbR = fsl_needs_repo(f);
  return dbR
    ? fsl_db_exists(dbR, "SELECT 1 FROM tagxref"
                    " WHERE tagid=%d "
                    " AND rid=%"FSL_ID_T_PFMT" AND tagtype>0",
                    FSL_TAGID_CLOSED, rid)
    : false;
}

/**
   Returns true if the given name is the current branch
   for the given checkin version.
 */
static bool fsl_is_current_branch(fsl_db * dbR, fsl_id_t vid,
                                  char const * name){
  return fsl_db_exists(dbR,
                       "SELECT 1 FROM tagxref"
                       " WHERE tagid=%d AND rid=%"FSL_ID_T_PFMT
                       " AND tagtype>0"
                       " AND value=%Q",
                       FSL_TAGID_BRANCH, vid, name);
}

int fsl_checkin_commit(fsl_cx * f, fsl_checkin_opt const * opt,
                       fsl_id_t * newRid, fsl_uuid_str * newUuid ){
  int rc;
  fsl_deck deck = fsl_deck_empty;
  fsl_deck *d = &deck;
  fsl_db * dbC;
  fsl_db * dbR;
  char inTrans = 0;
  char oldPrivate;
  int const oldFlags = f ? f->flags : 0;
  fsl_id_t const vid = f ? f->ckout.rid : 0;
  if(!f || !opt) return FSL_RC_MISUSE;
  else if(!(dbC = fsl_needs_checkout(f))) return FSL_RC_NOT_A_CHECKOUT;
  else if(!(dbR = fsl_needs_repo(f))) return FSL_RC_NOT_A_REPO;
  assert(vid>=0);
  /**
     Do not permit a checkin to a closed leaf unless opt->branch would
     switch us to a new branch.
  */
  if( fsl_leaf_is_closed(f, vid)


      && (!opt->branch || !*opt->branch
          || fsl_is_current_branch(dbR, vid, opt->branch))){
    return fsl_cx_err_set(f, FSL_RC_MISUSE,
                          "Cannot commit against a closed leaf.");
  }

  fsl_cx_err_reset(f) /* avoid propagating an older error by accident.
                         Did that in test code. */;

Changes to src/checkout.c.

283
284
285
286
287
288
289



290
291
292
293
294
295
296
  if(!f) return FSL_RC_MISUSE;
  else if(!db) return FSL_RC_NOT_A_CHECKOUT;
  assert(vid>=0);

  rc = fsl_cx_stat2(f, relativeToCwd, zFilename, &fst, &fname, 0);
  if(rc) goto end;
  zNorm = fsl_buffer_cstr(&fname);



  if( fsl_db_exists(db, "SELECT 1 FROM vfile"
                    " WHERE vid=%"FSL_ID_T_PFMT
                    " AND pathname=%Q %s",
                    (fsl_id_t)vid, zNorm,
                    fsl_cx_filename_collation(f)) ){
    rc = fsl_db_exec(db, "UPDATE vfile SET deleted=0,"
                     " mtime=%"PRIi64







>
>
>







283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
  if(!f) return FSL_RC_MISUSE;
  else if(!db) return FSL_RC_NOT_A_CHECKOUT;
  assert(vid>=0);

  rc = fsl_cx_stat2(f, relativeToCwd, zFilename, &fst, &fname, 0);
  if(rc) goto end;
  zNorm = fsl_buffer_cstr(&fname);
  rc = fsl_reserved_fn_check(f, zNorm, (fsl_int_t)fname.used);
  if(rc) goto end;

  if( fsl_db_exists(db, "SELECT 1 FROM vfile"
                    " WHERE vid=%"FSL_ID_T_PFMT
                    " AND pathname=%Q %s",
                    (fsl_id_t)vid, zNorm,
                    fsl_cx_filename_collation(f)) ){
    rc = fsl_db_exec(db, "UPDATE vfile SET deleted=0,"
                     " mtime=%"PRIi64
511
512
513
514
515
516
517












518
    *rv = fnid;
  }else if(db->error.code){
    fsl_cx_uplift_db_error(f, db);
  }
  return rc;
}













#undef MARKER







>
>
>
>
>
>
>
>
>
>
>
>

514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
    *rv = fnid;
  }else if(db->error.code){
    fsl_cx_uplift_db_error(f, db);
  }
  return rc;
}

FSL_EXPORT int fsl_reserved_fn_check(fsl_cx *f, const char *zPath,
                                     fsl_int_t nPath){
  int rc = 0;
  if(nPath<0) nPath = (fsl_int_t)fsl_strlen(zPath);
  if(fsl_is_reserved_fn(zPath, nPath)){
    rc = fsl_cx_err_set(f, FSL_RC_MISUSE,
                        "Filename is reserved, not legal "
                        "for adding to a repository: %.*s",
                        (int)nPath, zPath);
  }
  return rc;
}
#undef MARKER

Changes to src/fsl.c.

983
984
985
986
987
988
989













990































































991
992
993
994
}


char fsl_isatty(int fd){
  return isatty(fd) ? 1 : 0;
}














































































#undef MARKER
#if defined(_WIN32) || defined(WIN32)
#undef isatty
#endif







>
>
>
>
>
>
>
>
>
>
>
>
>
|
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>




983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
1001
1002
1003
1004
1005
1006
1007
1008
1009
1010
1011
1012
1013
1014
1015
1016
1017
1018
1019
1020
1021
1022
1023
1024
1025
1026
1027
1028
1029
1030
1031
1032
1033
1034
1035
1036
1037
1038
1039
1040
1041
1042
1043
1044
1045
1046
1047
1048
1049
1050
1051
1052
1053
1054
1055
1056
1057
1058
1059
1060
1061
1062
1063
1064
1065
1066
1067
1068
1069
1070
}


char fsl_isatty(int fd){
  return isatty(fd) ? 1 : 0;
}

bool fsl_is_reserved_fn_windows(const char *zPath){
  static const char *const azRes[] = {
    "CON", "PRN", "AUX", "NUL", "COM", "LPT"
  };
  unsigned int i;
  while( zPath[0] ){
    for(i=0; i<sizeof(azRes)/sizeof(azRes[0]); ++i){
      if( fsl_strnicmp(zPath, azRes[i], 3)==0
       && ((i>=4 && fsl_isdigit(zPath[3])
                 && (zPath[4]=='/' || zPath[4]=='.' || zPath[4]==0))
          || (i<4 && (zPath[3]=='/' || zPath[3]=='.' || zPath[3]==0)))
      ){
        return true;
      }
    }
    while( zPath[0] && zPath[0]!='/' ) ++zPath;
    while( zPath[0]=='/' ) ++zPath;
  }
  return false;
}

bool fsl_is_reserved_fn(const char *zFilename, fsl_int_t nameLen){
  fsl_size_t nFilename = nameLen>=0
    ? (fsl_size_t)nameLen : fsl_strlen(zFilename);
  char const * zEnd;
  int gotSuffix = 0;
  assert( zFilename && "API misuse" );
#if FSL_PLATFORM_IS_WINDOWS
  if(nFilename>2 && fsl_is_reserved_fn_windows(zFilename)){
    return true;
  }
#endif
  if( nFilename<8 ) return false; /* strlen("_FOSSIL_") */
  zEnd = zFilename + nFilename;
  if( nFilename>=12 ){ /* strlen("_FOSSIL_-(shm|wal)") */
    /* Check for (-wal, -shm, -journal) suffixes, with an eye towards
    ** runtime speed. */
    if( zEnd[-4]=='-' ){
      if( fsl_strnicmp("wal", &zEnd[-3], 3)
       && fsl_strnicmp("shm", &zEnd[-3], 3) ){
        return false;
      }
      gotSuffix = 4;
    }else if( nFilename>=16 && zEnd[-8]=='-' ){ /*strlen(_FOSSIL_-journal) */
      if( fsl_strnicmp("journal", &zEnd[-7], 7) ) return false;
      gotSuffix = 8;
    }
    if( gotSuffix ){
      assert( 4==gotSuffix || 8==gotSuffix );
      zEnd -= gotSuffix;
      nFilename -= gotSuffix;
      gotSuffix = 1;
    }
    assert( nFilename>=8 && "strlen(_FOSSIL_)" );
    assert( gotSuffix==0 || gotSuffix==1 );
  }
  switch( zEnd[-1] ){
    case '_':{
      if( fsl_strnicmp("_FOSSIL_", &zEnd[-8], 8) ) return false;
      if( 8==nFilename ) return true;
      return zEnd[-9]=='/' ? true : !!gotSuffix;
    }
    case 'T':
    case 't':{
      if( nFilename<9 || zEnd[-9]!='.'
       || fsl_strnicmp(".fslckout", &zEnd[-9], 9) ){
        return false;
      }
      if( 9==nFilename ) return 1;
      return zEnd[-10]=='/' ? true : !!gotSuffix;
    }
    default:{
      return false;
    }
  }
}

#undef MARKER
#if defined(_WIN32) || defined(WIN32)
#undef isatty
#endif