35 #include <yaz/diagbib1.h>
36 #include <yaz/pquery.h>
37 #include <yaz/sortspec.h>
43 #include <yaz/oid_db.h>
45 #define DEFAULT_APPROX_LIMIT 2000000000
48 #define ASSERTZH assert(zh && zh->service)
49 #define ASSERTZHRES assert(zh && zh->service && zh->res)
50 #define ASSERTZS assert(zs)
65 #define ZEBRA_CHECK_HANDLE(zh) if (zebra_check_handle(zh) != ZEBRA_OK) return ZEBRA_FAIL
76 yaz_log(YLOG_DEBUG,
"chdir %s", dir);
83 yaz_log(YLOG_FATAL|YLOG_ERRNO,
"chdir %s", dir);
101 int rw,
int useshadow,
103 const char *reg_path);
115 const char *default_encoding;
118 log_level = yaz_log_module_level(
"zebraapi");
128 yaz_log(YLOG_DEBUG,
"zebra_open zs=%p returns %p", zs, zh);
163 yaz_iconv_open(
"UTF-8", default_encoding);
165 yaz_log(YLOG_WARN,
"iconv: %s to UTF-8 unsupported",
168 yaz_iconv_open(default_encoding,
"UTF-8");
170 yaz_log(YLOG_WARN,
"iconv: UTF-8 to %s unsupported",
199 char version_str[16];
206 log_level = yaz_log_module_level(
"zebraapi");
214 yaz_log(YLOG_LOG,
"zebra_start %s %s", version_str, system_str);
216 yaz_log(YLOG_LOG,
"config %s", configName);
218 yaz_log_xml_errors(0, YLOG_LOG);
220 if ((res =
res_open(def_res, over_res)))
222 const char *passwd_plain = 0;
223 const char *passwd_encrypt = 0;
224 const char *dbaccess = 0;
237 yaz_log(YLOG_FATAL,
"Configuration error(s) for %s",
247 zh = xmalloc(
sizeof(*zh));
262 if (!passwd_plain && !passwd_encrypt)
268 yaz_log(YLOG_WARN|YLOG_ERRNO,
"passwd_db_open failed");
283 yaz_log(YLOG_FATAL,
"Failed to read %s", dbaccess);
288 zh->
timing = yaz_timing_create();
290 zh->
nmem = nmem_create();
295 const char *module_path =
res_get(res,
"modulePath");
306 void(*cb)(
void *cd,
const char *
name))
317 strcat(path,
"zebrasrv.pid");
321 int compact_flag,
Res res)
323 int page_size = 4096;
324 char resource_str[200];
325 sprintf(resource_str,
"dict.%.100s.pagesize", name);
330 yaz_log(YLOG_LOG,
"Using custom dictionary page size %d for %s",
332 return dict_open(bfs, name, cache, rw, compact_flag, page_size);
337 int rw,
int useshadow,
Res res,
338 const char *reg_path)
342 const char *compression_str = 0;
343 const char *profilePath;
349 reg = xmalloc(
sizeof(*reg));
352 reg->
name = xstrdup(name);
359 yaz_log(YLOG_DEBUG,
"zebra_register_open rw=%d useshadow=%d p=%p n=%s rp=%s",
360 rw, useshadow, reg, name, reg_path ? reg_path :
"(none)");
428 compression_str =
res_get_def(res,
"recordCompression",
"none");
429 if (!strcmp(compression_str,
"none"))
431 else if (!strcmp(compression_str,
"bzip2"))
433 else if (!strcmp(compression_str,
"zlib"))
437 yaz_log(YLOG_FATAL,
"invalid recordCompression: %s", compression_str);
443 yaz_log(YLOG_FATAL,
"unsupported recordCompression: %s",
449 const char *index_fname =
res_get_def(res,
"index",
"default.idx");
450 if (index_fname && *index_fname && strcmp(index_fname,
"none"))
463 yaz_log(YLOG_WARN,
"rec_open failed");
472 yaz_log(YLOG_WARN,
"dict_open failed");
485 yaz_log(YLOG_WARN,
"bad_value for 'sortindex'");
492 yaz_log(YLOG_WARN,
"zebra_sort_open failed");
501 yaz_log(YLOG_WARN,
"isams_open failed");
511 yaz_log(YLOG_WARN,
"isamc_open failed");
522 yaz_log(YLOG_WARN,
"isamb_open failed");
533 yaz_log(YLOG_WARN,
"isamb_open failed");
544 yaz_log(YLOG_WARN,
"isamb_open failed");
555 yaz_log(YLOG_WARN,
"Cannot obtain EXPLAIN information");
565 yaz_log(YLOG_DEBUG,
"zebra_register_open ok p=%p", reg);
572 yaz_log(
log_level,
"zebra_admin_shutdown");
595 yaz_log(YLOG_DEBUG,
"zebra_register_close p=%p", reg);
641 nmem_destroy(zs->
nmem);
644 yaz_timing_stop(zs->
timing);
645 yaz_log(YLOG_LOG,
"zebra_stop: %4.2f %4.2f %4.2f",
646 yaz_timing_get_real(zs->
timing),
647 yaz_timing_get_user(zs->
timing),
648 yaz_timing_get_sys(zs->
timing));
651 yaz_timing_destroy(&zs->
timing);
668 yaz_log(YLOG_DEBUG,
"zebra_close zh=%p", zh);
735 sprintf(fname,
"%.200s/zebra.cfg", zh->
path_reg);
745 yaz_log(YLOG_WARN,
"no register root specified");
766 assert(zh->
reg == 0);
808 const char *lock_area =
res_get(zh->
res,
"lockDir");
812 sprintf(fname,
"norm.%s.LCK", zh->
reg_name);
816 sprintf(fname,
"shadow.%s.LCK", zh->
reg_name);
844 yaz_log(YLOG_LOG,
"static rank set and is %d", zh->
m_staticrank);
851 yaz_log(YLOG_DEBUG,
"segment indexing set and is %d",
861 char fromdb[128], todb[8][128];
868 sscanf(value,
"%127s %127s %127s %127s %127s %127s %127s %127s %127s",
869 fromdb, todb[0], todb[1], todb[2], todb[3], todb[4],
870 todb[5], todb[6], todb[7]);
878 for (i = 0; i < no; i++)
883 nmem_strdup(p->
mem, todb[i]);
898 "database", group,
"Default");
922 info.
mem = stream->mem;
935 yaz_log(YLOG_DEBUG,
"base %s", (*basenames)[i]);
942 yaz_log(
log_level,
"zebra_select_database %s",basename);
958 yaz_log(
log_level,
"zebra_select_databases n=%d [0]=%s",
959 num_bases,basenames[0]);
964 zh->
errCode = YAZ_BIB1_COMBI_OF_SPECIFIED_DATABASES_UNSUPP;
973 const char *db = basenames[i];
977 if ((pp = strchr(p,
'+'))) {
983 if (len == strlen(db) && !strncmp(db, p, len))
987 zh->
errCode = YAZ_BIB1_ACCESS_TO_SPECIFIED_DATABASE_DENIED;
1000 zh->
basenames[i] = xstrdup(basenames[i]);
1002 cp = strrchr(basenames[0],
'/');
1005 len = cp - basenames[0];
1006 new_reg = xmalloc(len + 1);
1007 memcpy(new_reg, basenames[0], len);
1008 new_reg[len] =
'\0';
1011 new_reg = xstrdup(
"");
1016 cp1 = strrchr(basenames[i],
'/');
1021 zh->
errCode = YAZ_BIB1_COMBI_OF_SPECIFIED_DATABASES_UNSUPP;
1024 if (len != cp1 - basenames[i] ||
1025 memcmp(basenames[i], new_reg, len))
1027 zh->
errCode = YAZ_BIB1_COMBI_OF_SPECIFIED_DATABASES_UNSUPP;
1035 zh->
errCode = YAZ_BIB1_COMBI_OF_SPECIFIED_DATABASES_UNSUPP;
1044 zh->
errCode = YAZ_BIB1_DATABASE_UNAVAILABLE;
1049 zh->
errCode = YAZ_BIB1_TEMPORARY_SYSTEM_ERROR;
1057 if (approx_limit == 0)
1070 int (*f)(
void *client_data),
1079 const char *setname,
zint *hits,
1080 int *estimated_hit_count,
1081 int *partial_resultset)
1100 hits, estimated_hit_count);
1108 const char *setname,
zint *hits)
1110 int estimated_hit_count;
1111 int partial_resultset;
1113 &estimated_hit_count,
1114 &partial_resultset);
1118 const char *setname,
1119 Z_RecordComposition *comp,
1120 const Odr_oid *input_format,
int num_recs,
1134 yaz_log(
log_level,
"zebra_records_retrieve n=%d", num_recs);
1146 pos_array = (
zint *) xmalloc(num_recs *
sizeof(*pos_array));
1147 for (i = 0; i<num_recs; i++)
1148 pos_array[i] = recs[i].position;
1152 yaz_log(YLOG_DEBUG,
"zebraPosSetCreate error");
1159 WRBUF addinfo_w = wrbuf_alloc();
1160 for (i = 0; i < num_recs; i++)
1171 recs[i].
format = yaz_oid_recsyn_sutrs;
1172 recs[i].
len = strlen(poset[i].term);
1174 recs[i].
base = poset[i].
db;
1176 else if (poset[i].sysno)
1188 wrbuf_rewind(addinfo_w);
1191 poset[i].sysno, poset[i].score,
1192 stream, input_format, comp,
1193 &recs[i].format, &buf, &len,
1194 &recs[i].base, addinfo_w);
1196 if (wrbuf_len(addinfo_w))
1198 odr_strdup(stream, wrbuf_cstr(addinfo_w));
1202 recs[i].
buf = (
char*) odr_malloc(stream, len);
1203 memcpy(recs[i].buf, buf, len);
1216 YAZ_BIB1_PRESENT_REQUEST_OUT_OF_RANGE,
1224 wrbuf_destroy(addinfo_w);
1235 const char *setname)
1237 YAZ_PQF_Parser pqf_parser = yaz_pqf_create();
1238 Z_AttributesPlusTerm *zapt;
1239 Odr_oid *attributeSet;
1242 if (!(zapt = yaz_pqf_scan(pqf_parser, stream, &attributeSet, query)))
1245 zh->
errCode = YAZ_BIB1_SCAN_MALFORMED_SCAN;
1249 res =
zebra_scan(zh, stream, zapt, yaz_oid_attset_bib_1,
1250 position, num_entries, entries, is_partial,
1253 yaz_pqf_destroy(pqf_parser);
1258 const Odr_oid *attributeset,
1262 const char *setname)
1271 assert(num_entries);
1283 res =
rpn_scan(zh, stream, zapt, attributeset,
1285 num_entries, entries, is_partial, setname);
1291 int num_input_setnames,
const char **input_setnames,
1292 const char *output_setname,
1293 Z_SortKeySpecList *sort_sequence,
1299 assert(num_input_setnames>0);
1300 assert(input_setnames);
1301 assert(sort_sequence);
1302 assert(sort_status);
1307 res =
resultSetSort(zh, stream->mem, num_input_setnames, input_setnames,
1308 output_setname, sort_sequence, sort_status);
1314 int num_setnames,
char **setnames,
1319 yaz_log(
log_level,
"zebra_deleteResultSet n=%d", num_setnames);
1322 return Z_DeleteStatus_systemProblemAtTarget;
1325 case Z_DeleteResultSetRequest_list:
1326 assert(num_setnames>0);
1330 case Z_DeleteResultSetRequest_all:
1335 status = Z_DeleteStatus_success;
1336 for (i = 0; i<num_setnames; i++)
1337 if (statuses[i] == Z_DeleteStatus_resultSetDidNotExist)
1338 status = statuses[i];
1358 yaz_log(
log_level,
"zebra_errString: %s",e);
1367 yaz_log(
log_level,
"zebra_errAdd: %s",a);
1374 const char *astring;
1382 sprintf(u,
"perm.%.30s", user ? user :
"anonymous");
1404 const char *record_type)
1406 yaz_log(
log_level,
"zebra_admin_import_begin db=%s rt=%s",
1407 database, record_type);
1416 yaz_log(
log_level,
"zebra_admin_import_end");
1426 yaz_log(
log_level,
"zebra_admin_import_segment");
1428 for (i = 0; i<segment->num_segmentRecords; i++)
1430 Z_NamePlusRecord *npr = segment->segmentRecords[i];
1432 if (npr->which == Z_NamePlusRecord_intermediateFragment)
1434 Z_FragmentSyntax *fragment = npr->u.intermediateFragment;
1435 if (fragment->which == Z_FragmentSyntax_notExternallyTagged)
1437 Odr_oct *oct = fragment->u.notExternallyTagged;
1447 (
const char *) oct->buf, oct->len) ==
ZEBRA_FAIL)
1460 if (*info ==
sizeof(pos))
1462 memcpy(&pos, info+1,
sizeof(pos));
1473 if (*info ==
sizeof(pos))
1476 memcpy(&pos, info+1,
sizeof(pos));
1495 const char *index_type,
const char *string_index,
1502 yaz_log(YLOG_LOG,
"ord=%d index_type=%s index=%s cat=%d", ord,
1503 index_type, string_index, (
int) cat);
1506 ord_buf[ord_len] =
'\0';
1512 !strcmp(string_index,
"_ALLRECORDS") ?
1521 yaz_log(
log_level,
"zebra_drop_database %s", db);
1548 yaz_log(YLOG_WARN,
"drop database only supported for isam:b");
1550 "drop database only supported for isam:b");
1555 yaz_log(YLOG_WARN,
"zebra_end_trans failed");
1563 yaz_log(
log_level,
"zebra_create_database %s", db);
1578 yaz_log(YLOG_WARN,
"zebra_end_trans failed");
1587 const char *input_str,
int input_len,
1588 char *output_str,
int output_len)
1595 yaz_log(
log_level,
"zebra_string_norm ");
1602 if (wrbuf_len(wrbuf) >= output_len)
1604 if (wrbuf_len(wrbuf))
1605 memcpy(output_str, wrbuf_buf(wrbuf), wrbuf_len(wrbuf));
1606 output_str[wrbuf_len(wrbuf)] =
'\0';
1607 return wrbuf_len(wrbuf);
1622 char state_fname[256];
1627 yaz_log(
log_level,
"zebra_set_state v=%c seq=%d", val, seqno);
1629 sprintf(state_fname,
"state.%s.LCK", zh->
reg_name);
1631 f = fopen(fname,
"w");
1634 yaz_log(YLOG_FATAL|YLOG_ERRNO,
"open %s w", state_fname);
1637 yaz_log(YLOG_DEBUG,
"zebra_set_state: %c %d %ld", val, seqno, p);
1638 fprintf(f,
"%c %d %ld\n", val, seqno, p);
1645 char state_fname[256];
1652 sprintf(state_fname,
"state.%s.LCK", zh->
reg_name);
1654 f = fopen(fname,
"r");
1660 if (fscanf(f,
"%c %d", val, seqno) != 2)
1662 yaz_log(YLOG_ERRNO|YLOG_WARN,
"fscan fail %s",
1682 const char *group =
res_get(zh->
res,
"group");
1716 "zebra_begin_trans: no database selected");
1720 yaz_log(
log_level,
"zebra_begin_trans rw=%d",rw);
1728 YAZ_BIB1_ES_PERMISSION_DENIED_ON_ES_CANNOT_MODIFY_OR_DELETE,
1739 const char *rval = 0;
1750 "zebra_begin_trans: no write trans within read");
1766 #if HAVE_SYS_TIMES_H
1789 yaz_log(YLOG_WARN,
"previous transaction did not finish "
1790 "(shadow disabled)");
1815 1, rval ? 1 : 0, zh->
res,
1826 "zebra_begin_trans: cannot open register");
1827 yaz_log(YLOG_FATAL,
"%s", zh->
errString);
1845 #if HAVE_SYS_TIMES_H
1851 zh->
errCode = YAZ_BIB1_DATABASE_UNAVAILABLE;
1857 zh->
errCode = YAZ_BIB1_TEMPORARY_SYSTEM_ERROR;
1868 yaz_log(YLOG_DEBUG,
"reopen seqno cur/old %d/%d",
1874 yaz_log(YLOG_DEBUG,
"reopen last cur/old %d/%d",
1892 0, val ==
'c' ? 1 : 0,
1899 zh->
errCode = YAZ_BIB1_DATABASE_UNAVAILABLE;
1927 yaz_log(
log_level,
"zebra_end_transaction");
1936 if (!zh->
res || !zh->
reg)
1939 "zebra_end_trans: no open transaction");
1958 yaz_log(YLOG_DEBUG,
"zebra_end_trans");
1994 #if HAVE_SYS_TIMES_H
1996 yaz_log(
log_level,
"user/system: %ld/%ld",
1997 (
long) (zh->tms2.tms_utime - zh->tms1.tms_utime),
1998 (
long) (zh->tms2.tms_stime - zh->tms1.tms_stime));
2000 status->
utime = (long) (zh->tms2.tms_utime - zh->tms1.tms_utime);
2001 status->
stime = (long) (zh->tms2.tms_stime - zh->tms1.tms_stime);
2023 yaz_log(
log_level,
"updating %s", path);
2025 yaz_log(
log_level,
"deleting %s", path);
2027 yaz_log(
log_level,
"attempt deleting %s", path);
2029 yaz_log(
log_level,
"update action=%d", (
int) action);
2041 yaz_log(
log_level,
"zebra_repository_show");
2056 yaz_log(
log_level,
"zebra_commit_ex clean_only=%d", clean_only);
2060 zh->
errCode = YAZ_BIB1_DATABASE_UNAVAILABLE;
2066 yaz_log(YLOG_WARN,
"Cannot perform commit - No shadow area defined");
2085 yaz_log(YLOG_WARN,
"previous transaction didn't reach commit");
2088 else if (val ==
'c')
2124 yaz_log(YLOG_WARN,
"zebra_commit: failed");
2131 yaz_log(
log_level,
"nothing to commit");
2166 "cannot select default database");
2174 zebra_setError(zh, YAZ_BIB1_TEMPORARY_SYSTEM_ERROR,
"bfs_create");
2194 zh->
errCode = YAZ_BIB1_DATABASE_UNAVAILABLE;
2203 #define ZEBRA_CHECK_DICT 1
2204 #define ZEBRA_CHECK_ISAM 2
2207 zint *no_keys,
int message_limit,
2209 zint *no_long_dict_entries,
2210 zint *no_failed_dict_lookups,
2211 zint *no_invalid_keys,
2212 zint *no_invalid_dict_infos,
2213 zint *no_invalid_isam_entries)
2230 NMEM nmem = nmem_create();
2245 (*no_invalid_keys)++;
2246 if (*no_invalid_keys <= message_limit)
2250 ": unexpected key length %d",
2254 if (ord_len + slen >=
sizeof(ord_buf)-1)
2257 (*no_long_dict_entries)++;
2258 if (*no_long_dict_entries <= message_limit)
2263 ": long dictionary entry %d + %d",
2264 rec->
sysno, ord_len, (
int) slen);
2268 memcpy(ord_buf + ord_len, str, slen);
2269 ord_buf[ord_len + slen] =
'\0';
2273 (*no_long_dict_entries)++;
2274 if (*no_long_dict_entries <= message_limit)
2278 ": long dictionary entry %d + %d",
2279 rec->
sysno, (
int) ord_len, (
int) slen);
2288 (*no_failed_dict_lookups)++;
2289 if (*no_failed_dict_lookups <= message_limit)
2293 ": term do not exist in dictionary", rec->
sysno);
2300 if (*info !=
sizeof(pos))
2303 (*no_invalid_dict_infos)++;
2304 if (*no_invalid_dict_infos <= message_limit)
2308 ": long dictionary entry %d + %d",
2309 rec->
sysno, (
int) ord_len, (
int) slen);
2315 memcpy(&pos, info+1,
sizeof(pos));
2323 (*no_invalid_isam_entries)++;
2324 if (*no_invalid_isam_entries <= message_limit)
2344 until_key.
len = key_in.
len - 1;
2345 for (i = 0; i < until_key.
len; i++)
2346 until_key.
mem[i] = key_in.
mem[i+1];
2348 if (until_key.
mem[0] == 0)
2354 (*no_invalid_isam_entries)++;
2355 if (*no_invalid_isam_entries <= message_limit)
2360 " returned no entry",
2370 (*no_invalid_isam_entries)++;
2371 if (*no_invalid_isam_entries
2375 yaz_log(YLOG_WARN,
"Record "
2377 ": isamb_pp_forward "
2379 " returned different entry",
2402 slen, nmem, YLOG_LOG);
2416 int message_limit = 10;
2418 if (!spec || *spec ==
'\0'
2419 || !strcmp(spec,
"dict") || !strcmp(spec,
"default"))
2421 else if (!strcmp(spec,
"isam") || !strcmp(spec,
"full"))
2423 else if (!strcmp(spec,
"quick"))
2428 yaz_log(YLOG_LOG,
"zebra_register_check begin flags=%u message_limit=%d",
2429 flags, message_limit);
2432 zint no_records_total = 0;
2433 zint no_records_fail = 0;
2434 zint total_keys = 0;
2440 zint no_long_dict_entries = 0;
2441 zint no_failed_dict_lookups = 0;
2442 zint no_invalid_keys = 0;
2443 zint no_invalid_dict_infos = 0;
2444 zint no_invalid_isam_entries = 0;
2454 &no_long_dict_entries,
2455 &no_failed_dict_lookups,
2457 &no_invalid_dict_infos,
2458 &no_invalid_isam_entries
2470 total_keys += no_keys;
2478 yaz_log(YLOG_LOG,
"long dict entries: " ZINT_FORMAT,
2479 no_long_dict_entries);
2482 yaz_log(YLOG_LOG,
"failed dict lookups: " ZINT_FORMAT,
2483 no_failed_dict_lookups);
2484 yaz_log(YLOG_LOG,
"invalid dict infos: " ZINT_FORMAT,
2485 no_invalid_dict_infos);
2488 yaz_log(YLOG_LOG,
"invalid isam entries: " ZINT_FORMAT,
2489 no_invalid_isam_entries);
2493 yaz_log(YLOG_LOG,
"zebra_register_check end ret=%d", res);
2507 *code = YAZ_BIB1_TEMPORARY_SYSTEM_ERROR;
2508 *addinfo =
"ZebraHandle is NULL";
2515 yaz_log(
log_level,
"zebra_shadow_enable");
2521 yaz_log(
log_level,
"zebra_octet_term_encoding %s", encoding);
2531 yaz_iconv_open(
"UTF-8", encoding);
2533 yaz_log(YLOG_WARN,
"iconv: %s to UTF-8 unsupported", encoding);
2535 yaz_iconv_open(encoding,
"UTF-8");
2537 yaz_log(YLOG_WARN,
"iconv: UTF-8 to %s unsupported", encoding);
2544 yaz_log(
log_level,
"zebra_record_encoding");
2557 yaz_log(
log_level,
"zebra_set_resource %s:%s", name, value);
2563 const char *name,
const char *defaultvalue)
2569 yaz_log(
log_level,
"zebra_get_resource %s:%s", name, v);
2585 yaz_log(
log_level,
"zebra_get_shadow_enable");
2592 yaz_log(
log_level,
"zebra_set_shadow_enable %d",value);
2598 const char *buf,
int buf_size)
2610 const char *recordType,
2611 zint *sysno,
const char *match,
2613 const char *buf,
int buf_size)
2621 yaz_log(
log_level,
"zebra_update_record");
2626 buf_size = strlen(buf);
2638 yaz_log(YLOG_WARN,
"zebra_end_trans failed");
2649 const char *setname,
zint *hits)
2659 odr = odr_createmem(ODR_ENCODE);
2664 yaz_log(
log_level,
"zebra_search_PQF s=%s q=%s", setname, pqf_query);
2666 query = p_query_rpn(odr, pqf_query);
2670 yaz_log(YLOG_WARN,
"bad query %s\n", pqf_query);
2671 zh->
errCode = YAZ_BIB1_MALFORMED_QUERY;
2691 const char *sort_spec,
2692 const char *output_setname,
2693 const char **input_setnames)
2695 int num_input_setnames = 0;
2696 int sort_status = 0;
2697 Z_SortKeySpecList *sort_sequence;
2702 assert(output_setname);
2703 assert(input_setnames);
2704 sort_sequence = yaz_sort_spec(stream, sort_spec);
2708 yaz_log(YLOG_WARN,
"invalid sort specs '%s'", sort_spec);
2709 zh->
errCode = YAZ_BIB1_CANNOT_SORT_ACCORDING_TO_SEQUENCE;
2715 while (input_setnames[num_input_setnames]) num_input_setnames++;
2720 resultSetSort(zh, stream->mem, num_input_setnames, input_setnames,
2721 output_setname, sort_sequence, &sort_status);
2773 const char *lock_dir =
res_get_def(res,
"lockDir",
"");
2775 strcpy(path, lock_dir);
2776 if (*path && path[strlen(path)-1] !=
'/')