/src/netcdf-c/libnczarr/zmap.c
| Line | Count | Source | 
| 1 |  | /* | 
| 2 |  |  *  Copyright 2018, University Corporation for Atmospheric Research | 
| 3 |  |  *      See netcdf/COPYRIGHT file for copying and redistribution conditions. | 
| 4 |  |  */ | 
| 5 |  |  | 
| 6 |  | #include "zincludes.h" | 
| 7 |  | #include <stdarg.h> | 
| 8 |  | #include <stddef.h> | 
| 9 |  | #include "ncpathmgr.h" | 
| 10 |  | #include "ncutil.h" | 
| 11 |  |  | 
| 12 |  | /**************************************************/ | 
| 13 |  | /* Import the current implementations */ | 
| 14 |  |  | 
| 15 |  |  | 
| 16 |  | /**************************************************/ | 
| 17 |  |  | 
| 18 |  | NCZM_FEATURES | 
| 19 |  | nczmap_features(NCZM_IMPL impl) | 
| 20 | 0 | { | 
| 21 | 0 |     switch (impl) { | 
| 22 | 0 |     case NCZM_FILE: return zmap_file.features; | 
| 23 |  | #ifdef NETCDF_ENABLE_NCZARR_ZIP | 
| 24 |  |     case NCZM_ZIP: return zmap_zip.features; | 
| 25 |  | #endif | 
| 26 |  | #ifdef NETCDF_ENABLE_S3 | 
| 27 |  |     case NCZM_S3: return zmap_s3sdk.features; | 
| 28 |  | #endif | 
| 29 | 0 |     default: break; | 
| 30 | 0 |     } | 
| 31 | 0 |     return NCZM_UNIMPLEMENTED; | 
| 32 | 0 | } | 
| 33 |  |  | 
| 34 |  | int | 
| 35 |  | nczmap_create(NCZM_IMPL impl, const char *path, int mode, size64_t flags, void* parameters, NCZMAP** mapp) | 
| 36 | 0 | { | 
| 37 | 0 |     int stat = NC_NOERR; | 
| 38 | 0 |     NCZMAP* map = NULL; | 
| 39 | 0 |     NCURI* uri = NULL; | 
| 40 |  |      | 
| 41 | 0 |     if(path == NULL || strlen(path) == 0) | 
| 42 | 0 |   {stat = NC_EINVAL; goto done;} | 
| 43 |  |  | 
| 44 | 0 |     if(mapp) *mapp = NULL; | 
| 45 |  | 
 | 
| 46 | 0 |     if((mode & NC_NOCLOBBER) == 0) { | 
| 47 |  |         /* Truncate the file */ | 
| 48 | 0 |         if((stat = nczmap_truncate(impl,path))) goto done; | 
| 49 | 0 |     } | 
| 50 |  |  | 
| 51 | 0 |     switch (impl) { | 
| 52 | 0 |     case NCZM_FILE: | 
| 53 | 0 |         stat = zmap_file.create(path, mode, flags, parameters, &map); | 
| 54 | 0 |   if(stat) goto done; | 
| 55 | 0 |   break; | 
| 56 |  | #ifdef NETCDF_ENABLE_NCZARR_ZIP | 
| 57 |  |     case NCZM_ZIP: | 
| 58 |  |         stat = zmap_zip.create(path, mode, flags, parameters, &map); | 
| 59 |  |   if(stat) goto done; | 
| 60 |  |   break; | 
| 61 |  | #endif | 
| 62 |  | #ifdef NETCDF_ENABLE_S3 | 
| 63 |  |     case NCZM_S3: | 
| 64 |  |         stat = zmap_s3sdk.create(path, mode, flags, parameters, &map); | 
| 65 |  |   if(stat) goto done; | 
| 66 |  |   break; | 
| 67 |  | #endif | 
| 68 | 0 |     default: | 
| 69 | 0 |   {stat = REPORT(NC_ENOTBUILT,"nczmap_create"); goto done;} | 
| 70 | 0 |     } | 
| 71 | 0 |     if(mapp) *mapp = map; | 
| 72 | 0 | done: | 
| 73 | 0 |     ncurifree(uri); | 
| 74 | 0 |     return THROW(stat); | 
| 75 | 0 | } | 
| 76 |  |  | 
| 77 |  | int | 
| 78 |  | nczmap_open(NCZM_IMPL impl, const char *path, int mode, size64_t flags, void* parameters, NCZMAP** mapp) | 
| 79 | 0 | { | 
| 80 | 0 |     int stat = NC_NOERR; | 
| 81 | 0 |     NCZMAP* map = NULL; | 
| 82 | 0 |     NCURI* uri = NULL; | 
| 83 |  | 
 | 
| 84 | 0 |     if(path == NULL || strlen(path) == 0) | 
| 85 | 0 |   {stat = NC_EINVAL; goto done;} | 
| 86 |  |  | 
| 87 | 0 |     if(mapp) *mapp = NULL; | 
| 88 |  | 
 | 
| 89 | 0 |     switch (impl) { | 
| 90 | 0 |     case NCZM_FILE: | 
| 91 | 0 |         stat = zmap_file.open(path, mode, flags, parameters, &map); | 
| 92 | 0 |   if(stat) goto done; | 
| 93 | 0 |   break; | 
| 94 |  | #ifdef NETCDF_ENABLE_NCZARR_ZIP | 
| 95 |  |     case NCZM_ZIP: | 
| 96 |  |         stat = zmap_zip.open(path, mode, flags, parameters, &map); | 
| 97 |  |   if(stat) goto done; | 
| 98 |  |   break; | 
| 99 |  | #endif | 
| 100 |  | #ifdef NETCDF_ENABLE_S3 | 
| 101 |  |     case NCZM_S3: | 
| 102 |  |         stat = zmap_s3sdk.open(path, mode, flags, parameters, &map); | 
| 103 |  |   if(stat) goto done; | 
| 104 |  |   break; | 
| 105 |  | #endif | 
| 106 | 0 |     default: | 
| 107 | 0 |   {stat = REPORT(NC_ENOTBUILT,"nczmap_open"); goto done;} | 
| 108 | 0 |     } | 
| 109 |  |  | 
| 110 | 0 | done: | 
| 111 | 0 |     ncurifree(uri); | 
| 112 | 0 |     if(!stat) { | 
| 113 | 0 |         if(mapp) *mapp = map; | 
| 114 | 0 |     } | 
| 115 | 0 |     return THROW(stat); | 
| 116 | 0 | } | 
| 117 |  |  | 
| 118 |  | int | 
| 119 |  | nczmap_truncate(NCZM_IMPL impl, const char *path) | 
| 120 | 0 | { | 
| 121 | 0 |     int stat = NC_NOERR; | 
| 122 | 0 |     switch (impl) { | 
| 123 | 0 |     case NCZM_FILE: | 
| 124 | 0 |         if((stat = zmap_file.truncate(path))) goto done; | 
| 125 | 0 |   break; | 
| 126 |  | #ifdef NETCDF_ENABLE_NCZARR_ZIP | 
| 127 |  |     case NCZM_ZIP: | 
| 128 |  |         if((stat = zmap_zip.truncate(path))) goto done; | 
| 129 |  |   break; | 
| 130 |  | #endif | 
| 131 |  | #ifdef NETCDF_ENABLE_S3 | 
| 132 |  |     case NCZM_S3: | 
| 133 |  |         if((stat = zmap_s3sdk.truncate(path))) goto done; | 
| 134 |  |   break; | 
| 135 |  | #endif | 
| 136 | 0 |     default: | 
| 137 | 0 |   {stat = REPORT(NC_ENOTBUILT,"nczmap_truncate"); goto done;} | 
| 138 | 0 |     } | 
| 139 | 0 | done: | 
| 140 | 0 |     return stat; | 
| 141 | 0 | } | 
| 142 |  |  | 
| 143 |  | /**************************************************/ | 
| 144 |  | /* API Wrapper */ | 
| 145 |  |  | 
| 146 |  | int | 
| 147 |  | nczmap_close(NCZMAP* map, int delete) | 
| 148 | 0 | { | 
| 149 | 0 |     int stat = NC_NOERR; | 
| 150 | 0 |     if(map && map->api) | 
| 151 | 0 |         stat = map->api->close(map,delete); | 
| 152 | 0 |     return THROW(stat); | 
| 153 | 0 | } | 
| 154 |  |  | 
| 155 |  | int | 
| 156 |  | nczmap_exists(NCZMAP* map, const char* key) | 
| 157 | 0 | { | 
| 158 | 0 |     return map->api->exists(map, key); | 
| 159 | 0 | } | 
| 160 |  |  | 
| 161 |  | int | 
| 162 |  | nczmap_len(NCZMAP* map, const char* key, size64_t* lenp) | 
| 163 | 0 | { | 
| 164 | 0 |     return map->api->len(map, key, lenp); | 
| 165 | 0 | } | 
| 166 |  |  | 
| 167 |  | int | 
| 168 |  | nczmap_read(NCZMAP* map, const char* key, size64_t start, size64_t count, void* content) | 
| 169 | 0 | { | 
| 170 | 0 |     return map->api->read(map, key, start, count, content); | 
| 171 | 0 | } | 
| 172 |  |  | 
| 173 |  | int | 
| 174 |  | nczmap_write(NCZMAP* map, const char* key, size64_t count, const void* content) | 
| 175 | 0 | { | 
| 176 | 0 |     return map->api->write(map, key, count, content); | 
| 177 | 0 | } | 
| 178 |  |  | 
| 179 |  | /* Define a static qsort comparator for strings for use with qsort */ | 
| 180 |  | static int | 
| 181 |  | cmp_strings(const void* a1, const void* a2) | 
| 182 | 0 | { | 
| 183 | 0 |     const char** s1 = (const char**)a1; | 
| 184 | 0 |     const char** s2 = (const char**)a2; | 
| 185 | 0 |     return strcmp(*s1,*s2); | 
| 186 | 0 | } | 
| 187 |  |  | 
| 188 |  | int | 
| 189 |  | nczmap_search(NCZMAP* map, const char* prefix, NClist* matches) | 
| 190 | 0 | { | 
| 191 | 0 |     int stat = NC_NOERR; | 
| 192 | 0 |     if((stat = map->api->search(map, prefix, matches)) == NC_NOERR) { | 
| 193 |  |         /* sort the list */ | 
| 194 | 0 |         if(nclistlength(matches) > 1) { | 
| 195 | 0 |       void* base = nclistcontents(matches); | 
| 196 | 0 |             qsort(base, nclistlength(matches), sizeof(char*), cmp_strings); | 
| 197 | 0 |   } | 
| 198 | 0 |     } | 
| 199 | 0 |     return stat; | 
| 200 | 0 | } | 
| 201 |  |  | 
| 202 |  | /**************************************************/ | 
| 203 |  | /* Utilities */ | 
| 204 |  |  | 
| 205 |  | int | 
| 206 |  | nczm_split(const char* path, NClist* segments) | 
| 207 | 0 | { | 
| 208 | 0 |     return NC_split_delim(path,NCZM_SEP[0],segments); | 
| 209 | 0 | } | 
| 210 |  |  | 
| 211 |  | int | 
| 212 |  | nczm_concat(const char* prefix, const char* suffix, char** pathp) | 
| 213 | 0 | { | 
| 214 | 0 |     NCbytes* buf = ncbytesnew(); | 
| 215 |  | 
 | 
| 216 | 0 |     if(prefix == NULL || strlen(prefix)==0) prefix = NCZM_SEP; | 
| 217 | 0 |     if(suffix == NULL) suffix = ""; | 
| 218 | 0 |     ncbytescat(buf,prefix); | 
| 219 | 0 |     if(ncbytesget(buf,ncbyteslength(buf)-1) == NCZM_SEP[0]) | 
| 220 | 0 |   ncbytessetlength(buf,ncbyteslength(buf)-1); | 
| 221 | 0 |     if(strlen(suffix) > 0 && suffix[0] != NCZM_SEP[0]) | 
| 222 | 0 |   ncbytescat(buf,NCZM_SEP); | 
| 223 | 0 |     ncbytescat(buf,suffix); | 
| 224 | 0 |     if(pathp) *pathp = ncbytesextract(buf); | 
| 225 | 0 |     ncbytesfree(buf); | 
| 226 | 0 |     return NC_NOERR; | 
| 227 | 0 | } | 
| 228 |  |  | 
| 229 |  | /* Concat multiple strings, but with no intervening separators */ | 
| 230 |  | int | 
| 231 |  | nczm_appendn(char** resultp, int n, ...) | 
| 232 | 0 | { | 
| 233 | 0 |     va_list args; | 
| 234 | 0 |     NCbytes* buf = ncbytesnew(); | 
| 235 | 0 |     int i; | 
| 236 |  | 
 | 
| 237 | 0 |     va_start(args, n); | 
| 238 | 0 |     for(i=0;i<n;i++) { | 
| 239 | 0 |   char* s = va_arg(args,char*); | 
| 240 | 0 |   if(s != NULL) ncbytescat(buf,s); | 
| 241 | 0 |     } | 
| 242 | 0 |     ncbytesnull(buf); | 
| 243 | 0 |     va_end(args); | 
| 244 | 0 |     if(resultp) {*resultp = ncbytesextract(buf);} | 
| 245 | 0 |     ncbytesfree(buf); | 
| 246 | 0 |     return NC_NOERR; | 
| 247 | 0 | } | 
| 248 |  |  | 
| 249 |  | /* A segment is defined as a '/' plus characters following up | 
| 250 |  |    to the end or upto the next '/' | 
| 251 |  | */ | 
| 252 |  | int | 
| 253 |  | nczm_divide_at(const char* key, int nsegs, char** prefixp, char** suffixp) | 
| 254 | 0 | { | 
| 255 | 0 |     int stat = NC_NOERR; | 
| 256 | 0 |     char* prefix = NULL; | 
| 257 | 0 |     char* suffix = NULL; | 
| 258 | 0 |     size_t len, i; | 
| 259 | 0 |     ptrdiff_t delta; | 
| 260 | 0 |     const char* p; | 
| 261 | 0 |     size_t abssegs = (size_t)(nsegs >= 0 ?nsegs: -nsegs); | 
| 262 | 0 |     size_t presegs = 0; | 
| 263 |  |   | 
| 264 |  |     /* Special case */ | 
| 265 | 0 |     if(key == NULL || strlen(key) == 0) goto done; | 
| 266 |  |  | 
| 267 | 0 |     p = (key[0] == '/' ? key+1 : key); | 
| 268 |  |     /* Count number of segments */ | 
| 269 | 0 |     for(len=0;;) { | 
| 270 | 0 |         const char* q = strchr(p,'/');     | 
| 271 | 0 |   len++; | 
| 272 | 0 |   if(q == NULL) break; | 
| 273 | 0 |   p = q+1; /* start past leading '/' of next segment */ | 
| 274 | 0 |     } | 
| 275 | 0 |     if(abssegs > len) | 
| 276 | 0 |   {stat = NC_EINVAL; goto done;} | 
| 277 |  |     /* find split point */ | 
| 278 | 0 |     if(nsegs >= 0) | 
| 279 | 0 |   {presegs = abssegs;} | 
| 280 | 0 |     else | 
| 281 | 0 |   {presegs = (len - abssegs);} | 
| 282 |  |  | 
| 283 |  |     /* skip past the first presegs segments */ | 
| 284 | 0 |     for(p=key,i=0;i<presegs;i++) { | 
| 285 | 0 |         const char* q = strchr(p+1,'/');  | 
| 286 | 0 |   if(q == NULL) {p = (p + strlen(p)); break;} | 
| 287 | 0 |   else p = q; | 
| 288 | 0 |     } | 
| 289 |  |     /* p should point at the presegs+1 start point */ | 
| 290 | 0 |     delta = (p-key);     | 
| 291 | 0 |     if(prefixp) { | 
| 292 | 0 |         prefix = malloc((size_t)delta+1); | 
| 293 | 0 |         memcpy(prefix,key,(size_t)delta); | 
| 294 | 0 |         prefix[delta] = '\0'; | 
| 295 | 0 |         *prefixp = prefix; | 
| 296 | 0 |     }  | 
| 297 | 0 |     if(suffixp) { | 
| 298 | 0 |         suffix = strdup(p); | 
| 299 | 0 |         *suffixp = suffix; | 
| 300 | 0 |     } | 
| 301 | 0 | done: | 
| 302 | 0 |     return stat; | 
| 303 | 0 | } | 
| 304 |  |  | 
| 305 |  | int | 
| 306 |  | nczm_clear(NCZMAP* map) | 
| 307 | 0 | { | 
| 308 | 0 |     if(map)  | 
| 309 | 0 |   nullfree(map->url); | 
| 310 | 0 |     return NC_NOERR; | 
| 311 | 0 | } | 
| 312 |  |  | 
| 313 |  | int | 
| 314 |  | nczm_isabsolutepath(const char* path) | 
| 315 | 0 | { | 
| 316 | 0 |     if(path == NULL) return 0; | 
| 317 | 0 |     switch (path[0]) { | 
| 318 | 0 |     case '\\': return 1; | 
| 319 | 0 |     case '/': return 1; | 
| 320 | 0 |     case '\0': break; | 
| 321 | 0 |     default: | 
| 322 |  |   /* Check for windows drive letter */ | 
| 323 | 0 |   if(NChasdriveletter(path)) return 1; | 
| 324 | 0 |         break; | 
| 325 | 0 |     } | 
| 326 | 0 |     return 0; | 
| 327 | 0 | } | 
| 328 |  |  | 
| 329 |  | /* Convert forward slash to backslash ( !localize) or vice-versa (localize)*/ | 
| 330 |  | int | 
| 331 |  | nczm_localize(const char* path, char** localpathp, int localize) | 
| 332 | 0 | { | 
| 333 | 0 |     int stat = NC_NOERR; | 
| 334 | 0 |     char* localpath = NULL; | 
| 335 | 0 |     char* p; | 
| 336 | 0 |     int forward = 1; | 
| 337 | 0 |     int offset = 0; | 
| 338 | 0 |     static const char* windrive = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"; | 
| 339 |  | 
 | 
| 340 |  | #ifdef _MSC_VER | 
| 341 |  |     forward = (localize?0:1); | 
| 342 |  | #endif | 
| 343 |  |     /* If path comes from a url, then it may start with: /x:/... | 
| 344 |  |        where x is a drive letter. If so, then remove leading / */ | 
| 345 | 0 |     if(strlen(path) >= 4 | 
| 346 | 0 |        && path[0] == '/' && strchr(windrive,path[1]) != NULL | 
| 347 | 0 |        && path[2] == ':' && path[3] == '/') | 
| 348 | 0 |   offset = 1; | 
| 349 | 0 |     if((localpath = strdup(path+offset))==NULL) return NC_ENOMEM; | 
| 350 |  |  | 
| 351 | 0 |     for(p=localpath;*p;p++) { | 
| 352 | 0 |   if(forward && *p == '\\') *p = '/'; | 
| 353 | 0 |   else if(!forward && *p == '/') *p = '\\'; | 
| 354 | 0 |     } | 
| 355 | 0 |     if(localpathp) {*localpathp = localpath; localpath = NULL;} | 
| 356 | 0 |     nullfree(localpath); | 
| 357 | 0 |     return stat; | 
| 358 | 0 | } | 
| 359 |  |  | 
| 360 |  | /* Convert path0 to be: | 
| 361 |  | 1. absolute -- including drive letters | 
| 362 |  | 2. forward slashed -- we will convert back to back slash in nczm_fixpath | 
| 363 |  | */ | 
| 364 |  |  | 
| 365 |  | int | 
| 366 |  | nczm_canonicalpath(const char* path, char** cpathp) | 
| 367 | 0 | { | 
| 368 | 0 |     int ret = NC_NOERR; | 
| 369 | 0 |     char* cpath = NULL; | 
| 370 | 0 |     char* tmp1 = NULL; | 
| 371 |  | 
 | 
| 372 | 0 |     if(path == NULL)  | 
| 373 | 0 |   {cpath = NULL; goto done;} | 
| 374 |  |  | 
| 375 |  |     /* Process path to make it be absolute*/ | 
| 376 | 0 |     if((tmp1 = NCpathabsolute(path))==NULL) {ret = NC_ENOMEM; goto done;} | 
| 377 |  |  | 
| 378 |  |     /* Fix slashes to be forward for now */ | 
| 379 | 0 |     if((ret = nczm_localize(tmp1,&cpath,!LOCALIZE))) goto done; | 
| 380 |  |  | 
| 381 | 0 |     if(cpathp) {*cpathp = cpath; cpath = NULL;} | 
| 382 | 0 | done: | 
| 383 | 0 |     nullfree(tmp1); | 
| 384 | 0 |     nullfree(cpath); | 
| 385 | 0 |     return THROW(ret);     | 
| 386 | 0 | } | 
| 387 |  |  | 
| 388 |  | /* extract the first segment of a path */ | 
| 389 |  | int | 
| 390 |  | nczm_segment1(const char* path, char** seg1p) | 
| 391 | 0 | { | 
| 392 | 0 |     int ret = NC_NOERR; | 
| 393 | 0 |     char* seg1 = NULL; | 
| 394 | 0 |     const char* p = NULL; | 
| 395 | 0 |     const char* q = NULL; | 
| 396 | 0 |     ptrdiff_t delta; | 
| 397 |  | 
 | 
| 398 | 0 |     if(path == NULL)  | 
| 399 | 0 |   {seg1 = NULL; goto done;} | 
| 400 |  |  | 
| 401 | 0 |     p = path; | 
| 402 | 0 |     if(*p == '/') p++; /* skip any leading '/' */ | 
| 403 | 0 |     q = strchr(p,'/'); | 
| 404 | 0 |     if(q == NULL) q = p+strlen(p); /* point to stop character */ | 
| 405 | 0 |     delta = (q-p); | 
| 406 | 0 |     if((seg1 = (char*)malloc((size_t)delta+1))==NULL) | 
| 407 | 0 |         {ret = NC_ENOMEM; goto done;} | 
| 408 | 0 |     memcpy(seg1,p,(size_t)delta); | 
| 409 | 0 |     seg1[delta] = '\0'; | 
| 410 |  | 
 | 
| 411 | 0 |     if(seg1p) {*seg1p = seg1; seg1 = NULL;} | 
| 412 | 0 | done: | 
| 413 | 0 |     nullfree(seg1); | 
| 414 | 0 |     return THROW(ret);     | 
| 415 | 0 | } | 
| 416 |  |  | 
| 417 |  | /* | 
| 418 |  | Extract the last segment from path. | 
| 419 |  | */ | 
| 420 |  |  | 
| 421 |  | int | 
| 422 |  | nczm_lastsegment(const char* path, char** lastp) | 
| 423 | 0 | { | 
| 424 | 0 |     int ret = NC_NOERR; | 
| 425 | 0 |     const char* last = NULL; | 
| 426 |  | 
 | 
| 427 | 0 |     if(path == NULL) | 
| 428 | 0 |   {if(lastp) *lastp = NULL; goto done;} | 
| 429 |  |  | 
| 430 | 0 |     last = strrchr(path,'/'); | 
| 431 | 0 |     if(last == NULL) last = path; else last++; | 
| 432 |  | 
 | 
| 433 | 0 |     if(lastp) *lastp = strdup(last); | 
| 434 |  | 
 | 
| 435 | 0 | done: | 
| 436 | 0 |     return THROW(ret);     | 
| 437 | 0 | } | 
| 438 |  |  | 
| 439 |  | /* | 
| 440 |  | Extract the basename from a path. | 
| 441 |  | Basename is last segment minus one extension. | 
| 442 |  | */ | 
| 443 |  |  | 
| 444 |  | int | 
| 445 |  | nczm_basename(const char* path, char** basep) | 
| 446 | 0 | { | 
| 447 | 0 |     int stat = NC_NOERR; | 
| 448 | 0 |     char* base = NULL; | 
| 449 | 0 |     char* last = NULL; | 
| 450 | 0 |     const char* p = NULL; | 
| 451 | 0 |     ptrdiff_t delta; | 
| 452 |  | 
 | 
| 453 | 0 |     if((stat=nczm_lastsegment(path,&last))) goto done; | 
| 454 |  |  | 
| 455 | 0 |     if(last == NULL) goto done; | 
| 456 | 0 |     p = strrchr(last,'.'); | 
| 457 | 0 |     if(p == NULL) p = last+strlen(last); | 
| 458 | 0 |     delta = (p - last); | 
| 459 | 0 |     if((base = (char*)malloc((size_t)delta+1))==NULL) | 
| 460 | 0 |         {stat = NC_ENOMEM; goto done;} | 
| 461 | 0 |     memcpy(base,last,(size_t)delta); | 
| 462 | 0 |     base[delta] = '\0'; | 
| 463 | 0 |     if(basep) {*basep = base; base = NULL;} | 
| 464 | 0 | done: | 
| 465 | 0 |     nullfree(last); | 
| 466 | 0 |     nullfree(base); | 
| 467 | 0 |     return THROW(stat);     | 
| 468 | 0 | } | 
| 469 |  |  | 
| 470 |  | static int | 
| 471 |  | nczm_compare(const void* arg1, const void* arg2) | 
| 472 | 0 | { | 
| 473 | 0 |     char* n1 = *((char**)arg1); | 
| 474 | 0 |     char* n2 = *((char**)arg2); | 
| 475 | 0 |     return strcmp(n1,n2); | 
| 476 | 0 | } |