# HG changeset patch # User Olaf Wintermann # Date 1564776240 -7200 # Node ID a7c48e0dca882e77cfc9b5cab5c89211c05a4320 # Parent aa3baf1dd81b3e67bdc6b41c6fa78413a17be086 implement links on Windows (shelllink) diff -r aa3baf1dd81b -r a7c48e0dca88 dav/db.c --- a/dav/db.c Fri Aug 02 21:40:05 2019 +0200 +++ b/dav/db.c Fri Aug 02 22:04:00 2019 +0200 @@ -215,6 +215,8 @@ field = 11; } else if(xstreq(name, "link")) { field = 12; + } else if(xstreq(name, "localpath")) { + field = 13; } else if(xstreq(name, "skipped")) { res->skipped = TRUE; } else if(xstreq(name, "tags-updated")) { @@ -314,6 +316,9 @@ res->link_target = strdup((char*)value); break; } + case 13: { + res->local_path = strdup((char*)value); + } } } else if(XML_READER_TYPE_END_ELEMENT) { if(xstreq(name, "resource")) { @@ -533,6 +538,19 @@ } } + if(res->local_path) { + r = xmlTextWriterWriteElement( + writer, + BAD_CAST "localpath", + BAD_CAST res->local_path); + if(r < 0) { + fprintf(stderr, "Cannot write localpath: %s\n", res->local_path); + xmlFreeTextWriter(writer); + return -1; + } + } + + if(res->xattr_hash) { r = xmlTextWriterWriteElement( writer, diff -r aa3baf1dd81b -r a7c48e0dca88 dav/db.h --- a/dav/db.h Fri Aug 02 21:40:05 2019 +0200 +++ b/dav/db.h Fri Aug 02 22:04:00 2019 +0200 @@ -68,6 +68,8 @@ char *xattr_hash; char *remote_tags_hash; + char *local_path; + char *link_target; FilePart *parts; diff -r aa3baf1dd81b -r a7c48e0dca88 dav/main.c --- a/dav/main.c Fri Aug 02 21:40:05 2019 +0200 +++ b/dav/main.c Fri Aug 02 22:04:00 2019 +0200 @@ -1,7 +1,7 @@ /* * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. * - * Copyright 2018 Olaf Wintermann. All rights reserved. + * Copyright 2019 Olaf Wintermann. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: @@ -89,6 +89,7 @@ return -1; } + sys_init(); xmlGenericErrorFunc fnc = xmlerrorfnc; initGenericErrorDefaultFunc(&fnc); ctx = dav_context_new(); @@ -211,6 +212,7 @@ free_config(); xmlCleanupParser(); curl_global_cleanup(); + sys_uninit(); return ret; } diff -r aa3baf1dd81b -r a7c48e0dca88 dav/main.h --- a/dav/main.h Fri Aug 02 21:40:05 2019 +0200 +++ b/dav/main.h Fri Aug 02 22:04:00 2019 +0200 @@ -1,7 +1,7 @@ /* * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. * - * Copyright 2018 Olaf Wintermann. All rights reserved. + * Copyright 2019 Olaf Wintermann. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: diff -r aa3baf1dd81b -r a7c48e0dca88 dav/sync.c --- a/dav/sync.c Fri Aug 02 21:40:05 2019 +0200 +++ b/dav/sync.c Fri Aug 02 22:04:00 2019 +0200 @@ -136,6 +136,7 @@ xmlGenericErrorFunc fnc = xmlerrorfnc; initGenericErrorDefaultFunc(&fnc); + sys_init(); ctx = dav_context_new(); int cfgret = load_config(ctx) || load_sync_config(); @@ -212,6 +213,8 @@ curl_global_cleanup(); xmlCleanupParser(); + sys_uninit(); + return ret; } @@ -716,13 +719,16 @@ // the first thing we need are all directories to put the files in UCX_FOREACH(elm, res_mkdir) { DavResource *res = elm->data; + char *res_path = resource_local_path(res); char *local_path = create_local_path(dir, res->path); + free(res_path); if(sys_mkdir(local_path) && errno != EEXIST) { fprintf(stderr, "Cannot create directory %s: %s", local_path, strerror(errno)); } free(local_path); + // TODO: create localres for dir } // we need a map for all conflicts for fast lookups @@ -757,7 +763,7 @@ continue; } - char *local_path = util_concat_path(dir->path, local->path); + char *local_path = util_concat_path(dir->path, local_resource_path(local)); int staterr = sys_stat(local_path, &s); free(local_path); if(staterr) { @@ -840,7 +846,9 @@ LocalResource *local = ucx_map_cstr_get(db->resources, res->path); if(local) { printf("update: %s\n", res->path); + char *res_path = resource_local_path(res); char *local_path = create_local_path(dir, res->path); + free(res_path); if(sync_store_metadata(dir, local_path, local, res)) { fprintf(stderr, "Metadata update failed: %s\n", res->path); sync_error++; @@ -1223,7 +1231,7 @@ renamefunc fn = copy ? copy_file : sys_rename; char *new_path = util_concat_path(dir->path, res->path); - char *old_path = util_concat_path(dir->path, content->path); + char *old_path = util_concat_path(dir->path, local_resource_path(content)); printf("%s: %s -> %s\n", copy?"copy":"move", content->path, res->path); if(fn(old_path, new_path)) { @@ -1287,12 +1295,20 @@ SyncDatabase *db, int *counter) { - LocalResource *local = ucx_map_cstr_get(db->resources, path); - char *local_path = create_local_path(dir, path); - char *link = SYNC_SYMLINK(dir) ? dav_get_string_property_ns(res, DAV_PROPS_NS, "link") : NULL; + LocalResource *local = ucx_map_cstr_get(db->resources, path); + + char *local_path; + if(link) { + char *res_path = resource_local_path(res); + local_path = create_local_path(dir, res_path); + free(res_path); + } else { + local_path = create_local_path(dir, path); + } + char *etag = dav_get_string_property(res, "D:getetag"); SYS_STAT s; memset(&s, 0, sizeof(SYS_STAT)); @@ -1821,7 +1837,7 @@ continue; } - char *local_path = util_concat_path(dir->path, local->path); + char *local_path = util_concat_path(dir->path, local_resource_path(local)); char *hash = util_file_hash(local_path); local->hash = hash; // check if a file with this hash already exists @@ -2504,29 +2520,38 @@ res->isdirectory = 1; } - if(S_ISLNK(s.st_mode)) { - off_t l_sz = s.st_size + 16; - size_t lnksize = l_sz > 256 ? l_sz : 256; - char *lnkbuf = malloc(lnksize); + if(SYS_ISLINK(file_path, s)) { + char *lnkbuf = sys_readlink(file_path, &s); +#ifdef SYS_LINK_EXT + // on Windows, we interpret .lnk files as links + // we dont want resource names with this extension + // and possibly sync this to other operating systems + // therefore we remove the .lnk extension from the file name + // change res->path + // we only do this, if there isn't any other file with this name + sstr_t fpath = sstr(file_path); + sstr_t rpath = sstr(path); + // remove last 4 chars (.lnk) + sstr_t new_file_path = sstrdup(sstrsubsl(fpath, 0 , fpath.length-4)); + // check if a file with this name exists + SYS_STAT nfp_s; + if(!sys_stat(new_file_path.ptr, &nfp_s)) { + // we can't upload the link without the file extension, because + // there is another file with this name + free(lnkbuf); + lnkbuf = NULL; + } else { + sstr_t new_path = sstrdup(sstrsubsl(rpath, 0, rpath.length-4)); + res->local_path = res->path; + res->path = new_path.ptr; // remove .lnk ext from resource path + } + free(new_file_path.ptr); +#endif - ssize_t len = 0; - for(int i=0;i<4;i++) { - // we try to read the link at most 4 times - // only repeat if the buffer is too small - len = sys_readlink(file_path, lnkbuf, lnksize); - if(len != lnksize) { - break; - } - lnksize *= 2; - lnkbuf = realloc(lnkbuf, lnksize); - } - - if(len > 0) { - // readlink successful - lnkbuf[len] = 0; - + if(lnkbuf) { + // readlink successful char *normalized = NULL; - if(lnkbuf[0] != '/') { + if(!util_path_isabsolut(lnkbuf)) { char *link_parent = util_parent_path(res->path); char *abs_link_parent = util_concat_path(dir->path, link_parent); char *link = util_concat_path(abs_link_parent, lnkbuf); @@ -2538,15 +2563,16 @@ normalized = util_path_normalize(lnkbuf); } - if(util_path_isrelated(dir->path, normalized)) { + char *dirpath = util_path_normalize(dir->path); + if(util_path_isrelated(dirpath, normalized)) { // the link points to a file inside the syncdir char *rel = util_create_relative_path(normalized, file_path); res->link_target = rel; } + free(dirpath); free(normalized); + free(lnkbuf); } - - free(lnkbuf); } free(file_path); @@ -2554,6 +2580,10 @@ return res; } +char* local_resource_path(LocalResource *res) { + return res->local_path ? res->local_path : res->path; +} + int local_resource_is_changed( SyncDirectory *dir, SyncDatabase *db, @@ -2712,6 +2742,20 @@ return ret; } +char* resource_local_path(DavResource *res) { +#ifdef SYS_LINK_EXT + // on Windows, add .lnk extension to links + if(dav_get_property_ns(res, DAV_PROPS_NS, "link")) { + return ucx_sprintf("%s%s", res->path, SYS_LINK_EXT).ptr; + } else { + // not a link + return strdup(res->path); + } +#else + return strdup(res->path); +#endif +} + size_t resource_get_blocksize(SyncDirectory *dir, LocalResource *local, DavResource *res, off_t filesize) { size_t local_blocksize = 0; if(local->blocksize < 0) { @@ -3066,7 +3110,7 @@ UcxBuffer *buf = NULL; if(dir->tagconfig->store == TAG_STORE_XATTR) { ssize_t tag_length = 0; - char *local_path = create_local_path(dir, res->path); + char *local_path = create_local_path(dir, local_resource_path(res)); char* tag_data = xattr_get( local_path, dir->tagconfig->xattr_name, @@ -3560,7 +3604,7 @@ LocalResource *local, int *counter) { - char *local_path = create_local_path(dir, res->path); + char *local_path = create_local_path(dir, local_resource_path(local)); SYS_STAT s; if(sys_stat(local_path, &s)) { diff -r aa3baf1dd81b -r a7c48e0dca88 dav/sync.h --- a/dav/sync.h Fri Aug 02 21:40:05 2019 +0200 +++ b/dav/sync.h Fri Aug 02 22:04:00 2019 +0200 @@ -132,6 +132,7 @@ UcxList* local_scan(SyncDirectory *dir, SyncDatabase *db); UcxList* read_changes(SyncDirectory *dir, SyncDatabase *db); LocalResource* local_resource_new(SyncDirectory *dir, SyncDatabase *db, char *path); +char* local_resource_path(LocalResource *res); int local_resource_is_changed( SyncDirectory *dir, SyncDatabase *db, @@ -145,6 +146,7 @@ SyncDatabase *db, DavResource *remote, LocalResource *res); +char* resource_local_path(DavResource *res); size_t resource_get_blocksize(SyncDirectory *dir, LocalResource *local, DavResource *res, off_t filesize); diff -r aa3baf1dd81b -r a7c48e0dca88 dav/system.c --- a/dav/system.c Fri Aug 02 21:40:05 2019 +0200 +++ b/dav/system.c Fri Aug 02 22:04:00 2019 +0200 @@ -26,6 +26,8 @@ * POSSIBILITY OF SUCH DAMAGE. */ +#include + #include #include #include @@ -34,6 +36,8 @@ #include #include +#include + #ifndef _WIN32 #include #endif @@ -48,6 +52,13 @@ #ifndef _WIN32 /* ---------- POSIX implementation ---------- */ +void sys_init(void) { + +} +void sys_uninit(void) { + +} + SYS_DIR sys_opendir(const char *path) { DIR *dir = opendir(path); if(!dir) { @@ -96,6 +107,13 @@ return lstat(path, s); } +int sys_islink(const char *path) { + struct stat s; + if(!lstat(path, &s)) { + return S_ISLNK(s.st_mode); + } +} return 0; + int sys_rename(const char *oldpath, const char *newpath) { return rename(oldpath, newpath); } @@ -108,8 +126,31 @@ return mkdir(path, S_IRWXU | S_IRGRP | S_IXGRP | S_IROTH | S_IXOTH); } -ssize_t sys_readlink(const char *path, char *buffer, size_t size) { - return readlink(path, buffer, size); +char* sys_readlink(const char *path, SYS_STAT *s) { + char *ret = NULL; + + off_t l_sz = s->st_size + 16; + size_t lnksize = l_sz > 256 ? l_sz : 256; + char *lnkbuf = malloc(lnksize); + + ssize_t len = 0; + for(int i=0;i<4;i++) { + // we try to read the link at most 4 times + // only repeat if the buffer is too small + len = readlink(path, lnkbuf, lnksize); + if(len < lnksize) { + ret = lnkbuf; // success + lnkbuf[len] = 0; // terminate buffer + break; + } + lnksize *= 2; // retry with bigger buffer + lnkbuf = realloc(lnkbuf, lnksize); + } + + if(!ret) { + free(lnkbuf); + } + return ret; } int sys_symlink(const char *target, const char *linkpath) { @@ -126,6 +167,26 @@ #else /* ---------- Windows implementation ---------- */ +#include +#include +#include +#include +#include + +#include +#include + +void sys_init(void) { + HRESULT res = CoInitialize(NULL); + if(res != S_OK) { + fprintf(stderr, "Error: CoInitialize failed\n"); + } +} + +void sys_uninit(void) { + CoUninitialize(); +} + static wchar_t* path2winpath(const char *path, int dir, int *newlen) { size_t len = strlen(path); size_t lenadd = dir ? 2 : 0; @@ -158,7 +219,25 @@ wpath[wlen] = 0; return wpath; -} +} + +static char* winpath2multibyte(const wchar_t *wpath, size_t wlen) { + size_t maxlen = wlen * 4; + char *ret = malloc(maxlen + 1); + int ret_len = WideCharToMultiByte( + CP_UTF8, + 0, + wpath, + wlen, + ret, + maxlen, + NULL, + NULL); + ret[ret_len] = 0; + return ret; +} + + SYS_DIR sys_opendir(const char *path) { struct WinDir *dir = malloc(sizeof(struct WinDir)); @@ -247,7 +326,43 @@ } int sys_lstat(const char *path, SYS_STAT *s) { - return sys_stat(path, s); // unsupported on windows + return sys_stat(path, s); // unsupported on windows +} + +int sys_islink(const char *path) { + // don't use symlinks on windows, because it is not really useful + // however, we interpret .lnk files as symlinks + int ret = 0; + + scstr_t path_s = scstr(path); + if(scstrsuffix(path_s, SC(".lnk"))) { + // looks like a .lnk file + // check content + IShellLink *sl; + HRESULT hres; + hres = CoCreateInstance(&CLSID_ShellLink, NULL, CLSCTX_INPROC_SERVER, &IID_IShellLinkW, (LPVOID*)&sl); + if(SUCCEEDED(hres)) { + IPersistFile *file; + hres = sl->lpVtbl->QueryInterface(sl, &IID_IPersistFile, (void**)&file); + if(!SUCCEEDED(hres)) { + sl->lpVtbl->Release(sl); + return ret; + } + + int newlen = 0; + wchar_t *wpath = path2winpath(path, 0, &newlen); + + hres = file->lpVtbl->Load(file, wpath, STGM_READ); + if(SUCCEEDED(hres)) { + ret = 1; + file->lpVtbl->Release(file); + } + free(wpath); + + sl->lpVtbl->Release(sl); + } + } + return ret; } int sys_rename(const char *oldpath, const char *newpath) { @@ -292,16 +407,103 @@ return ret; } -ssize_t sys_readlink(const char *path, char *buffer, size_t size) { - // TODO - fprintf(stderr, "sys_readlink: implement me\n"); - return 1; +char* sys_readlink(const char *path, SYS_STAT *s) { + char *ret_link = NULL; + + // create COM object for using the ShellLink interface + IShellLinkW *sl; + HRESULT hres = CoCreateInstance(&CLSID_ShellLink, NULL, CLSCTX_INPROC_SERVER, &IID_IShellLinkW, (LPVOID*)&sl); + if(!SUCCEEDED(hres)) { + return NULL; + } + + IPersistFile *file; + hres = sl->lpVtbl->QueryInterface(sl, &IID_IPersistFile, (void**)&file); + if(!SUCCEEDED(hres)) { + sl->lpVtbl->Release(sl); + return NULL; + } + + // load .lnk file + int newlen = 0; + wchar_t *wpath = path2winpath(path, 0, &newlen); + hres = file->lpVtbl->Load(file, wpath, STGM_READ); + if(SUCCEEDED(hres)) { + WCHAR link_path[MAX_PATH]; + memset(link_path, 0, MAX_PATH); + + hres = sl->lpVtbl->Resolve(sl, 0, SLR_NO_UI); + if(SUCCEEDED(hres)) { + hres = sl->lpVtbl->GetPath(sl, link_path, MAX_PATH, NULL, SLGP_SHORTPATH); + if(SUCCEEDED(hres)) { + ret_link = winpath2multibyte(link_path, wcslen(link_path)); + } + } + } + // cleanup + free(wpath); + file->lpVtbl->Release(file); + sl->lpVtbl->Release(sl); + + return ret_link; } int sys_symlink(const char *target, const char *linkpath) { - // TODO - fprintf(stderr, "sys_symlink: implement me\n"); - return 1; + // convert relative target to absolut path + char *link_parent = util_parent_path(linkpath); + char *target_unnormalized = util_concat_path(link_parent, target); + char *target_normalized = util_path_normalize(target_unnormalized); + + free(link_parent); + free(target_unnormalized); + + // convert to wchar_t* + int wtargetlen = 0; + wchar_t *wtarget = path2winpath(target_normalized, FALSE, &wtargetlen); + free(target_normalized); + if(!wtarget) { + return 1; + } + + int wlinkpathlen = 0; + wchar_t *wlinkpath = path2winpath(linkpath, FALSE, &wlinkpathlen); + if(!wlinkpath) { + free(wtarget); + return 1; + } + + int ret = 1; + + // create COM object for using the ShellLink interface + IShellLinkW *sl; + HRESULT hres = CoCreateInstance(&CLSID_ShellLink, NULL, CLSCTX_INPROC_SERVER, &IID_IShellLinkW, (LPVOID*)&sl); + if(SUCCEEDED(hres)) { + IPersistFile *file; + hres = sl->lpVtbl->QueryInterface(sl, &IID_IPersistFile, (void**)&file); + if(SUCCEEDED(hres)) { + // try to load the shortcut + file->lpVtbl->Load(file, wlinkpath, STGM_READ); // ignore error + + // set path + hres = sl->lpVtbl->SetPath(sl, wtarget); + if(SUCCEEDED(hres)) { + hres = file->lpVtbl->Save(file, wlinkpath, TRUE); + if(SUCCEEDED(hres)) { + // successfully created/modified shortcut + ret = 0; // ok + } + } + + file->lpVtbl->Release(file); + } + + sl->lpVtbl->Release(sl); + } + + free(wtarget); + free(wlinkpath); + + return ret; } #endif diff -r aa3baf1dd81b -r a7c48e0dca88 dav/system.h --- a/dav/system.h Fri Aug 02 21:40:05 2019 +0200 +++ b/dav/system.h Fri Aug 02 22:04:00 2019 +0200 @@ -57,11 +57,12 @@ #define SYS_DIR struct WinDir* #define SYS_STAT struct __stat64 -#define S_ISLNK(s) 0 - typedef int uid_t; typedef int gid_t; +#define SYS_ISLINK(path, mode) sys_islink(path) +#define SYS_LINK_EXT ".lnk" + #else typedef struct SysDir { @@ -71,10 +72,16 @@ #define SYS_DIR SysDir* #define SYS_STAT struct stat + +#define SYS_ISLINK(p, s) S_ISLNK(s.st_mode) + #endif typedef int(*stat_func)(const char*, SYS_STAT *); +void sys_init(void); +void sys_uninit(void); + void sys_freedirent(SysDirEnt *ent); SYS_DIR sys_opendir(const char *path); SysDirEnt* sys_readdir(SYS_DIR dir); @@ -85,11 +92,13 @@ int sys_stat(const char *path, SYS_STAT *s); int sys_lstat(const char *path, SYS_STAT *s); +int sys_islink(const char *path); + int sys_rename(const char *oldpath, const char *newpath); int sys_unlink(const char *path); int sys_mkdir(const char *path); -ssize_t sys_readlink(const char *path, char *buffer, size_t size); +char* sys_readlink(const char *path, SYS_STAT *s); int sys_symlink(const char *target, const char *linkpath); diff -r aa3baf1dd81b -r a7c48e0dca88 libidav/utils.c --- a/libidav/utils.c Fri Aug 02 21:40:05 2019 +0200 +++ b/libidav/utils.c Fri Aug 02 22:04:00 2019 +0200 @@ -41,9 +41,13 @@ #ifdef _WIN32 #include #define getpasswordchar() getch() +#define IS_PATH_SEPARATOR(c) (c == '/' || c == '\\') +#define PATH_SEPARATOR '\\' #else #include #define getpasswordchar() getchar() +#define IS_PATH_SEPARATOR(c) (c == '/') +#define PATH_SEPARATOR '/' #endif #include "webdav.h" @@ -377,10 +381,10 @@ scstr_t p1 = scstr(path1); scstr_t p2 = scstr(path2); - if(p1.ptr[p1.length-1] == '/') { + if(IS_PATH_SEPARATOR(p1.ptr[p1.length-1])) { p1.length--; } - if(p2.ptr[p2.length-1] == '/') { + if(IS_PATH_SEPARATOR(p2.ptr[p2.length-1])) { p2.length--; } @@ -393,7 +397,7 @@ } if(sstrprefix(p2, p1)) { - if(p2.ptr[p1.length] == '/') { + if(IS_PATH_SEPARATOR(p2.ptr[p1.length])) { return 1; } } @@ -401,6 +405,29 @@ return 0; } +#ifdef _WIN32 +int util_path_isabsolut(const char *path) { + if(strlen(path) < 3) { + return 0; + } + + // check if first char is A-Z or a-z + char c = path[0]; + if(!((c >= 65 && c <= 90) || (c >= 97 && c <= 122))) { + return 0; + } + + if(path[1] == ':' && path[2] == '\\') { + return 1; + } + return 0; +} +#else +int util_path_isabsolut(const char *path) { + return path[0] == '/'; +} +#endif + char* util_path_normalize(const char *path) { size_t len = strlen(path); UcxBuffer *buf = ucx_buffer_new(NULL, len+1, UCX_BUFFER_AUTOEXTEND); @@ -413,10 +440,10 @@ int seg_start = 0; for(int i=0;i<=len;i++) { char c = path[i]; - if(c == '/' || c == '\0') { + if(IS_PATH_SEPARATOR(c) || c == '\0') { const char *seg_ptr = path+seg_start; int seg_len = i - seg_start; - if(seg_ptr[0] == '/') { + if(IS_PATH_SEPARATOR(seg_ptr[0])) { seg_ptr++; seg_len--; } @@ -426,11 +453,11 @@ if(!sstrcmp(seg, SC(".."))) { for(int j=buf->pos;j>=0;j--) { char t = buf->space[j]; - if(t == '/' || j == 0) { + if(IS_PATH_SEPARATOR(t) || j == 0) { buf->pos = j; buf->size = j; buf->space[j] = 0; - add_separator = t == '/' ? 1 : 0; + add_separator = IS_PATH_SEPARATOR(t) ? 1 : 0; break; } } @@ -438,7 +465,7 @@ // ignore } else { if(add_separator) { - ucx_buffer_putc(buf, '/'); + ucx_buffer_putc(buf, PATH_SEPARATOR); } ucx_buffer_write(seg_ptr, 1, seg_len, buf); add_separator = 1; @@ -458,19 +485,19 @@ return space; } -char* util_create_relative_path(const char *abspath, const char *base) { +static char* create_relative_path(const char *abspath, const char *base) { size_t path_len = strlen(abspath); size_t base_len = strlen(base); - if(abspath[path_len-1] == '/') { + if(IS_PATH_SEPARATOR(abspath[path_len-1])) { path_len--; } - if(base[base_len-1] == '/') { + if(IS_PATH_SEPARATOR(base[base_len-1])) { base_len--; } // get base parent for(int i=base_len-1;i>=0;i--) { - if(base[i] == '/') { + if(IS_PATH_SEPARATOR(base[i])) { base_len = i+1; break; } @@ -486,7 +513,7 @@ char c = abspath[i]; if(c != base[i]) { break; - } else if(c == '/') { + } else if(IS_PATH_SEPARATOR(c)) { last_dir = i; } } @@ -497,7 +524,7 @@ // base is deeper than the link root, we have to go backwards int dircount = 0; for(int i=last_dir+1;i