diff --git a/include/connect.h b/include/connect.h index 5d87597..d566594 100644 --- a/include/connect.h +++ b/include/connect.h @@ -257,7 +257,7 @@ extern time_t nw_2_un_time(uint8 *d, uint8 *t); extern void un_time_2_nw(time_t time, uint8 *d, int high_low); -extern void mangle_dos_name(NW_VOL *vol, uint8 *unixname, uint8 *pp); +extern void mangle_dos_name(NW_VOL *vol, uint8 *unixname, uint8 *pp, int len); extern int nw_add_trustee(int dir_handle, uint8 *data, int len, uint32 id, int trustee, int extended); diff --git a/include/namedos.h b/include/namedos.h new file mode 100644 index 0000000..5c68b20 --- /dev/null +++ b/include/namedos.h @@ -0,0 +1,15 @@ +/* + * namedos.h: DOS namespace helpers for mars_nwe + */ +#ifndef _NAMEDOS_H_ +#define _NAMEDOS_H_ +#if WITH_NAME_SPACE_CALLS + +extern int dos_is_83_name(uint8 *name, int options); +extern int build_dos_83_alias(int options, uint8 *parent_unix, + uint8 *real_name, ino_t inode, + uint8 *alias, int alias_len); +extern void mangle_dos_name(NW_VOL *vol, uint8 *unixname, uint8 *pp, int len); + +#endif +#endif diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index a1ce040..5c60a2f 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -57,7 +57,7 @@ ELSE(ENABLE_INTERNAL_RIP_SAP) ENDIF(ENABLE_INTERNAL_RIP_SAP) add_executable(nwserv nwserv.c net1.c tools.c ${EMUTLI} ${EMUTLI1} ${NWROUTE_0} ) -add_executable(nwconn nwconn.c net1.c tools.c connect.c namspace.c nwvolume.c nwfile.c unxfile.c nwqconn.c nameos2.c nwfname.c nwshare.c extpipe.c nwattrib.c trustee.c ${EMUTLI} ) +add_executable(nwconn nwconn.c net1.c tools.c connect.c namspace.c nwvolume.c nwfile.c unxfile.c nwqconn.c nameos2.c namedos.c nwfname.c nwshare.c extpipe.c nwattrib.c trustee.c ${EMUTLI} ) add_executable(ncpserv ncpserv.c net1.c tools.c ${EMUTLI} ) add_executable(nwclient nwclient.c net1.c tools.c ${EMUTLI} ) add_executable(nwbind nwbind.c net1.c tools.c nwdbm.c nwcrypt.c unxlog.c sema.c nwqueue.c unxfile.c ${EMUTLI} ) diff --git a/src/connect.c b/src/connect.c index fd5fb81..9359186 100644 --- a/src/connect.c +++ b/src/connect.c @@ -58,8 +58,17 @@ static int act_umode_file=0; #include "nwfile.h" #include "nwconn.h" #include "namspace.h" +#include "namedos.h" #include "connect.h" +/* connect.h may already be include-guarded through another header before + * NW_VOL is visible, so keep this local forward declaration before the + * first call in build_dir_name(). + */ +void mangle_dos_name(NW_VOL *vol, uint8 *unixname, uint8 *pp, int len); +static void build_dos_attr_name(int volume, uint8 *path, char *unixname, + struct stat *stb, uint8 *out, int out_len); + typedef struct { dev_t dev; /* unix dev */ @@ -107,6 +116,35 @@ static DIR_HANDLE dir_handles[MAX_DIRHANDLES]; static int anz_dirhandles=0; + +/* Last real directory entry found by the old DOS scan code. + * Some callers rebuild unixname later from the synthetic DOS alias; keep the + * real parent/name pair so the final attribute formatter can still generate + * a collision-aware 8.3 alias for display. + */ +static int last_dos_alias_volume = -1; +static ino_t last_dos_alias_inode = 0; +static uint8 last_dos_alias_parent[300]; +static uint8 last_dos_alias_real[256]; +static uint8 last_dos_alias_alias[14]; + +static void remember_dos_alias_source(int volume, uint8 *parent, uint8 *real, + ino_t inode) +{ + last_dos_alias_volume = volume; + last_dos_alias_inode = inode; + strmaxcpy(last_dos_alias_parent, parent ? (char*)parent : "", + sizeof(last_dos_alias_parent)-1); + strmaxcpy(last_dos_alias_real, real ? (char*)real : "", + sizeof(last_dos_alias_real)-1); + last_dos_alias_alias[0] = '\0'; + if (parent && real) { + build_dos_83_alias(get_volume_options(volume), parent, real, inode, + last_dos_alias_alias, sizeof(last_dos_alias_alias)); + } +} + + static char *build_unix_name(NW_PATH *nwpath, int modus) /* * returns complete UNIX path @@ -597,19 +635,25 @@ static int func_search_entry(NW_PATH *nwpath, int attrib, if (dirbuff->d_ino) { uint8 *name=(uint8*)(dirbuff->d_name); uint8 dname[256]; + uint8 dosalias[14]; xstrcpy(dname, name); unix2doscharset(dname); + *kpath = '\0'; + build_dos_83_alias(soptions, (uint8*)xkpath, name, + dirbuff->d_ino, dosalias, sizeof(dosalias)); okflag = (name[0] != '.' && - ( (!strcmp((char*)dname, (char*)entry)) - || fn_dos_match(dname, entry, soptions))); + ( (!strcmp((char*)dosalias, (char*)entry)) + || fn_dos_match(dosalias, entry, soptions))); if (okflag) { - *kpath = '\0'; strmaxcpy(kpath, (char*)name, sizeof(xkpath) - (int)(kpath-xkpath) -1 ); if (!s_stat(xkpath, &(fs->statb), NULL)) { okflag = ( ( ( (fs->statb.st_mode & S_IFMT) == S_IFDIR) && (attrib & 0x10)) || ( ( (fs->statb.st_mode & S_IFMT) != S_IFDIR) && !(attrib & 0x10))); if (okflag){ - xstrcpy(nwpath->fn, (char*)dname); + *kpath = '\0'; + remember_dos_alias_source(volume, (uint8*)xkpath, name, + fs->statb.st_ino); + xstrcpy(nwpath->fn, (char*)dosalias); XDPRINTF((5,0,"FOUND=:%s: attrib=0x%x", nwpath->fn, fs->statb.st_mode)); result = (*fs_func)(nwpath, fs); if (result < 0) break; @@ -678,13 +722,16 @@ static int get_dir_entry(NW_PATH *nwpath, if (dirbuff->d_ino) { uint8 *name=(uint8*)(dirbuff->d_name); uint8 dname[256]; + uint8 dosalias[14]; xstrcpy(dname, name); unix2doscharset(dname); + *kpath = '\0'; + build_dos_83_alias(soptions, (uint8*)xkpath, name, + dirbuff->d_ino, dosalias, sizeof(dosalias)); okflag = ((name[0] != '.' && - ( (!strcmp((char*)dname, (char*)entry)) - || fn_dos_match(dname, entry, soptions)))) ? 0 : -0xff; + ( (!strcmp((char*)dosalias, (char*)entry)) + || fn_dos_match(dosalias, entry, soptions)))) ? 0 : -0xff; if (!okflag) { - *kpath = '\0'; strmaxcpy(kpath, (char*)name, sizeof(xkpath) - (int)(kpath-xkpath) -1); if (!s_stat(xkpath, statb, NULL)) { okflag = (( ( ( (statb->st_mode & S_IFMT) == S_IFDIR) && (attrib & 0x10)) @@ -697,7 +744,11 @@ static int get_dir_entry(NW_PATH *nwpath, if (soptions & VOL_OPTION_IS_PIPE) { statb->st_size = 0x70000000|(statb->st_mtime&0xfffffff); } - xstrcpy(nwpath->fn, (char*)dname); + *kpath = '\0'; + remember_dos_alias_source(volume, (uint8*)xkpath, name, + statb->st_ino); + XDPRINTF((99,0,"DOSGETDIR parent='%s' real='%s' alias='%s' inode=%ld", xkpath, name, dosalias, (long)statb->st_ino)); + xstrcpy(nwpath->fn, (char*)dosalias); XDPRINTF((5,0,"FOUND=:%s: attrib=0x%x", nwpath->fn, statb->st_mode)); break; /* ready */ } else @@ -781,11 +832,14 @@ static int get_dh_entry(DIR_HANDLE *dh, if (dirbuff->d_ino) { uint8 *name=(uint8*)(dirbuff->d_name); uint8 dname[256]; + uint8 dosalias[14]; xstrcpy(dname, name); unix2doscharset(dname); + build_dos_83_alias(dh->vol_options, (uint8*)dh->unixname, name, + dirbuff->d_ino, dosalias, sizeof(dosalias)); okflag = (name[0] != '.' && - ( (!strcmp((char*)dname, (char*)entry)) - || fn_dos_match(dname, entry, dh->vol_options))); + ( (!strcmp((char*)dosalias, (char*)entry)) + || fn_dos_match(dosalias, entry, dh->vol_options))); if (okflag) { strmaxcpy(dh->kpath, (char*)name, @@ -805,7 +859,11 @@ static int get_dh_entry(DIR_HANDLE *dh, if (okflag){ if (unixname) strmaxcpy(unixname, dh->unixname, size_unixname-1); - strmaxcpy((char*)search, (char*)dname, size_search-1); + *(dh->kpath) = '\0'; + remember_dos_alias_source(dh->volume, (uint8*)dh->unixname, + name, statb->st_ino); + XDPRINTF((99,0,"DOSDH parent='%s' real='%s' alias='%s' inode=%ld", dh->unixname, name, dosalias, (long)statb->st_ino)); + strmaxcpy((char*)search, (char*)dosalias, size_search-1); break; /* ready */ } } else okflag = 0; @@ -1052,7 +1110,7 @@ static int build_dir_name(NW_PATH *nwpath, /* gets complete path */ dos2unixcharset(pp); pp += offset; pathlen -= offset; - mangle_dos_name(v, unixname, pp); + mangle_dos_name(v, unixname, pp, sizeof(unixname) - (int)(pp - unixname)); unix2doscharset(pp); XDPRINTF((5, 0, "Mangled DOS/unixname=%s", unixname)); memcpy(ppp, pp, pathlen); @@ -1183,10 +1241,12 @@ time_t nw_2_un_time(uint8 *d, uint8 *t) static int get_file_attrib(NW_FILE_INFO *f, char *unixname, struct stat *stb, NW_PATH *nwpath) { + uint8 spath[14]; uint32 dwattrib; - strncpy((char*)f->name, (char*)nwpath->fn, sizeof(f->name)); - f->attrib[0]=0; /* d->name could be too long */ - up_fn(f->name); + build_dos_attr_name(nwpath->volume, nwpath->fn, unixname, stb, + spath, sizeof(spath)); + memset(f->name, 0, sizeof(f->name)); + strncpy((char*)f->name, (char*)spath, sizeof(f->name)-1); dwattrib = get_nw_attrib_dword(nwpath->volume, unixname, stb); U16_TO_16(dwattrib, f->attrib); @@ -1203,11 +1263,13 @@ static int get_file_attrib(NW_FILE_INFO *f, char *unixname, struct stat *stb, static int get_dir_attrib(NW_DIR_INFO *d, char *unixname, struct stat *stb, NW_PATH *nwpath) { + uint8 spath[14]; uint32 dwattrib; XDPRINTF((5,0, "get_dir_attrib of %s", conn_get_nwpath_name(nwpath))); - strncpy((char*)d->name, (char*)nwpath->fn, sizeof(d->name)); - d->attrib[0]=0; /* d->name could be too long */ - up_fn(d->name); + build_dos_attr_name(nwpath->volume, nwpath->fn, unixname, stb, + spath, sizeof(spath)); + memset(d->name, 0, sizeof(d->name)); + strncpy((char*)d->name, (char*)spath, sizeof(d->name)-1); dwattrib = get_nw_attrib_dword(nwpath->volume, unixname, stb); U16_TO_16(dwattrib, d->attrib); @@ -2385,9 +2447,44 @@ static int s_nw_scan_dir_info(int dir_handle, XDPRINTF((5,0,"SCAN_DIR: von %s, found %s:", dh->unixname, dirname)); if (++aktsequence == dirsequence) { /* actual found */ + uint8 dosalias[14]; + char parent[300]; + char *slash; + uint8 *leaf = dirname; + U16_TO_BE16(aktsequence, subnr); - up_fn(dirname); - strncpy((char*)subname, (char*)dirname, 16); + + /* + * Do not trust dirname here. Some old DOS scan paths carry the + * raw 8 byte directory name only, which collapses colliding long + * directories to the same display name (LONG_DIR/LONG_DIR). + * Rebuild the final directory info name from the real full unixname + * returned by get_dh_entry(), where the long on-disk leaf is still + * available. + */ + if (unixname[0]) { + strmaxcpy(parent, unixname, sizeof(parent)-1); + slash = strrchr(parent, '/'); + if (slash) { + leaf = (uint8*)(slash+1); + *slash = '\0'; + if (!*parent) strcpy(parent, "/"); + build_dos_83_alias(dh->vol_options, (uint8*)parent, + leaf, stbuff.st_ino, + dosalias, sizeof(dosalias)); + } else { + build_dos_83_alias(dh->vol_options, NULL, + leaf, stbuff.st_ino, + dosalias, sizeof(dosalias)); + } + } else { + build_dos_83_alias(dh->vol_options, (uint8*)dh->unixname, + dirname, stbuff.st_ino, + dosalias, sizeof(dosalias)); + } + + up_fn(dosalias); + strncpy((char*)subname, (char*)dosalias, 16); U32_TO_BE32(get_file_owner(&stbuff), owner); un_date_2_nw(stbuff.st_mtime, subdatetime, 1); un_time_2_nw(stbuff.st_mtime, subdatetime+2, 1); @@ -2440,6 +2537,60 @@ int nw_scan_dir_info(int dir_handle, uint8 *data, int len, uint8 *subnr, +static void build_dos_attr_name(int volume, uint8 *path, char *unixname, + struct stat *stb, uint8 *out, int out_len) +{ + char parent[300]; + char *slash; + uint8 *leaf = path; + int options = get_volume_options(volume); + + if (!out || out_len < 2) return; + *out = '\0'; + + if (stb && volume == last_dos_alias_volume + && stb->st_ino == last_dos_alias_inode + && last_dos_alias_parent[0] && last_dos_alias_real[0]) { + if (last_dos_alias_alias[0]) { + strmaxcpy(out, last_dos_alias_alias, out_len-1); + up_fn(out); + } else { + build_dos_83_alias(options, last_dos_alias_parent, + last_dos_alias_real, stb->st_ino, out, out_len); + } + return; + } + + /* + * Prefer the real unixname when it is available. Some namespace/DOS scan + * paths pass an already synthesized 8.3 name in `path`; if we return that + * blindly, colliding long directories can both be displayed as LONG_DIR. + */ + if (unixname && *unixname) { + strmaxcpy(parent, unixname, sizeof(parent)-1); + slash = strrchr(parent, '/'); + if (slash) { + *slash = '\0'; + if (*(slash+1)) leaf = (uint8*)(slash+1); + if (!*parent) strcpy(parent, "/"); + build_dos_83_alias(options, (uint8*)parent, + leaf, stb ? stb->st_ino : 0, out, out_len); + return; + } + } + + /* Fallback: keep a previously synthesized DOS alias only when there is no + * reliable real unixname to rebuild from. */ + if (path && *path && dos_is_83_name(path, options)) { + strmaxcpy(out, path, out_len-1); + up_fn(out); + return; + } + + build_dos_83_alias(options, NULL, + leaf, stb ? stb->st_ino : 0, out, out_len); +} + void get_dos_file_attrib(NW_DOS_FILE_INFO *f, struct stat *stb, int volume, @@ -2448,9 +2599,9 @@ void get_dos_file_attrib(NW_DOS_FILE_INFO *f, { uint8 spath[14]; uint32 nw_owner=get_file_owner(stb); - f->namlen=min(strlen((char*)path), 12); - strmaxcpy(spath, path, 12); - up_fn(spath); + build_dos_attr_name(volume, path, unixname, stb, spath, sizeof(spath)); + XDPRINTF((99,0,"DOSFILEATTR path='%s' unix='%s' alias='%s' inode=%ld", path ? (char*)path : "", unixname ? unixname : "", spath, stb ? (long)stb->st_ino : 0L)); + f->namlen=min(strlen((char*)spath), 12); strncpy((char*)f->name, (char*)spath, f->namlen); U32_TO_32(get_nw_attrib_dword(volume, unixname, stb), f->attributes); U16_TO_16(tru_get_inherited_mask(volume, unixname, stb), @@ -2475,9 +2626,9 @@ void get_dos_dir_attrib(NW_DOS_DIR_INFO *f, char *unixname) { uint8 spath[14]; - f->namlen=min(strlen((char*)path), 12); - strmaxcpy(spath, path, 12); - up_fn(spath); + build_dos_attr_name(volume, path, unixname, stb, spath, sizeof(spath)); + XDPRINTF((99,0,"DOSDIRATTR path='%s' unix='%s' alias='%s' inode=%ld", path ? (char*)path : "", unixname ? unixname : "", spath, stb ? (long)stb->st_ino : 0L)); + f->namlen=min(strlen((char*)spath), 12); strncpy((char*)f->name, (char*)spath, f->namlen); U32_TO_32(get_nw_attrib_dword(volume, unixname, stb), f->attributes); @@ -2679,13 +2830,7 @@ static int get_match(uint8 *unixname, uint8 *p) return(0); } -void mangle_dos_name(NW_VOL *vol, uint8 *unixname, uint8 *pp) -{ - struct stat stb; - if (!s_stat(unixname, &stb, NULL)) /* path is ok I hope */ - return; - get_match(unixname, pp-1); -} +/* DOS name mangling is provided by namedos.c. */ int nw_add_trustee(int dir_handle, uint8 *data, int len, diff --git a/src/namedos.c b/src/namedos.c new file mode 100644 index 0000000..2aa6b03 --- /dev/null +++ b/src/namedos.c @@ -0,0 +1,396 @@ +/* namedos.c : NameSpace DOS Services, mars_nwe + * + * Adds DOS 8.3 alias generation/resolution for long filenames on DOS + * namespace clients. This file was originally derived from nameos2.c, + * but DOS needs synthetic aliases while OS/2 can address long names. + */ + +#include "net.h" +#include +#include +#include +#include +#include +#ifndef LINUX +#include +#endif + +#include "nwfname.h" +#include "nwvolume.h" +#include "connect.h" +#include "nwfile.h" +#include "unxfile.h" +#include "namedos.h" + +#if WITH_NAME_SPACE_CALLS + +#define MAX_NAME_DOS_CACHE 0 + +static int dos_is_alias_char(int c) +{ + if (isalnum((unsigned char)c)) return(1); + switch (c) { + case '$': case '%': case '\'': case '-': case '_': case '@': + case '~': case '`': case '!': case '(': case ')': case '{': + case '}': case '^': case '#': case '&': + return(1); + default: + return(0); + } +} + +static int dos_alias_char(int c) +{ + if (c >= 'a' && c <= 'z') c -= ('a' - 'A'); + if (dos_is_alias_char(c)) return(c); + return('_'); +} + +int dos_is_83_name(uint8 *name, int options) +{ + uint8 *ss=name; + int len=0; + int pf=0; + + if (!name || !*name) return(0); + if (!strcmp((char*)name, ".") || !strcmp((char*)name, "..")) return(1); + + for (; *ss; ss++){ + if (*ss == '.') { + if (pf++) return(0); /* no second dot */ + if (!len) return(0); /* no empty base */ + len=0; + } else { + if (!dos_is_alias_char(*ss)) return(0); + ++len; + if ((pf && len > 3) || len > 8) return(0); + if (!(options & VOL_OPTION_IGNCASE)){ + if (options & VOL_OPTION_DOWNSHIFT){ + if (*ss >= 'A' && *ss <= 'Z') return(0); + } else { + if (*ss >= 'a' && *ss <= 'z') return(0); + } + } + } + } + return(len > 0); +} + +static void build_dos_83_raw(uint8 *real_name, uint8 *alias, int alias_len) +{ + uint8 base[9]; + uint8 ext[4]; + uint8 *dot=NULL; + uint8 *s; + int bl=0, el=0; + + memset(base, 0, sizeof(base)); + memset(ext, 0, sizeof(ext)); + + if (!real_name || !*real_name) { + strmaxcpy(alias, "_", alias_len-1); + return; + } + + if (real_name[0] != '.') + dot=(uint8*)strrchr((char*)real_name, '.'); + + for (s=real_name; *s && s != dot; s++) { + if (bl < 8) base[bl++] = (uint8)dos_alias_char(*s); + } + if (!bl) base[bl++] = '_'; + + if (dot) { + for (s=dot+1; *s; s++) { + if (el < 3) ext[el++] = (uint8)dos_alias_char(*s); + } + } + + if (el) + snprintf((char*)alias, alias_len, "%s.%s", base, ext); + else + snprintf((char*)alias, alias_len, "%s", base); +} + +static int alias_base_equal(uint8 *a, uint8 *b) +{ + uint8 aa[14], bb[14]; + uint8 *pa, *pb; + strmaxcpy(aa, a, sizeof(aa)-1); + strmaxcpy(bb, b, sizeof(bb)-1); + pa=(uint8*)strchr((char*)aa, '.'); + pb=(uint8*)strchr((char*)bb, '.'); + if (pa) *pa='\0'; + if (pb) *pb='\0'; + return(!strcmp((char*)aa, (char*)bb)); +} + +static char dos_digit(int n) +{ + n &= 0x0f; + return((n < 10) ? ('0' + n) : ('A' + n - 10)); +} + +static void apply_collision_number(uint8 *alias, int collision) +{ + uint8 *dot=(uint8*)strchr((char*)alias, '.'); + uint8 ext[5]; + uint8 base[9]; + int bl; + + memset(ext, 0, sizeof(ext)); + memset(base, 0, sizeof(base)); + if (dot) { + strmaxcpy(ext, dot, sizeof(ext)-1); + *dot='\0'; + } + strmaxcpy(base, alias, sizeof(base)-1); + bl=strlen((char*)base); + if (bl >= 8) bl=7; + base[bl++] = dos_digit(collision); + base[bl] = '\0'; + snprintf((char*)alias, 14, "%s%s", base, ext); +} + +int build_dos_83_alias(int options, uint8 *parent_unix, + uint8 *real_name, ino_t inode, + uint8 *alias, int alias_len) +{ + uint8 raw[14]; + int collisions=0; + DIR *d; + + (void)inode; + + if (alias_len < 2) return(0); + *alias='\0'; + + if (dos_is_83_name(real_name, options)) { + strmaxcpy(alias, real_name, alias_len-1); + up_fn(alias); + return(strlen((char*)alias)); + } + + build_dos_83_raw(real_name, raw, sizeof(raw)); + strmaxcpy(alias, raw, alias_len-1); + + if (parent_unix && *parent_unix && NULL != (d=opendir((char*)parent_unix))) { + struct dirent *de; + while ((de=readdir(d)) != NULL) { + uint8 other[14]; + if (!de->d_ino) continue; + if (!strcmp(de->d_name, ".") || !strcmp(de->d_name, "..")) continue; + if (!strcmp(de->d_name, (char*)real_name)) continue; + build_dos_83_raw((uint8*)de->d_name, other, sizeof(other)); + if (!strcmp((char*)other, (char*)raw)) { + if (strcmp(de->d_name, (char*)real_name) < 0) + collisions++; + } + /* A real 8.3 name that equals the raw alias consumes that alias. */ + if (dos_is_83_name((uint8*)de->d_name, options)) { + uint8 native[14]; + strmaxcpy(native, (uint8*)de->d_name, sizeof(native)-1); + up_fn(native); + if (!strcmp((char*)native, (char*)raw)) collisions++; + } + } + closedir(d); + } + + if (collisions > 0) { + strmaxcpy(alias, raw, alias_len-1); + apply_collision_number(alias, collisions-1); + } + + up_fn(alias); + return(strlen((char*)alias)); +} + +static int dos_alias_match(uint8 *a, uint8 *b) +{ + uint8 aa[14], bb[14]; + strmaxcpy(aa, a, sizeof(aa)-1); + strmaxcpy(bb, b, sizeof(bb)-1); + up_fn(aa); + up_fn(bb); + return(!strcmp((char*)aa, (char*)bb)); +} + +void mangle_dos_name(NW_VOL *vol, uint8 *unixname, uint8 *pp, int len) +{ + uint8 wanted[14]; + uint8 rest[512]; + uint8 *slash; + uint8 *parent_slash; + DIR *d; + + if (!vol || !unixname || !pp || !*pp) return; + if (len <= 1) return; + + slash=(uint8*)strchr((char*)pp, '/'); + memset(rest, 0, sizeof(rest)); + if (slash) { + strmaxcpy(rest, slash, sizeof(rest)-1); + *slash='\0'; + } + + strmaxcpy(wanted, pp, sizeof(wanted)-1); + up_fn(wanted); + + parent_slash = (pp > unixname && *(pp-1) == '/') ? pp-1 : NULL; + if (parent_slash) *parent_slash='\0'; + + d=opendir((char*)unixname); + if (d) { + struct dirent *de; + while ((de=readdir(d)) != NULL) { + uint8 alias[14]; + if (!de->d_ino) continue; + if (!strcmp(de->d_name, ".") || !strcmp(de->d_name, "..")) continue; + build_dos_83_alias(vol->options, unixname, (uint8*)de->d_name, + de->d_ino, alias, sizeof(alias)); + if (dos_alias_match(alias, wanted)) { + if (parent_slash) *parent_slash='/'; + snprintf((char*)pp, len, "%s%s", de->d_name, rest); + closedir(d); + if (*rest) { + uint8 *next=(uint8*)strchr((char*)pp, '/'); + if (next && *(next+1)) + mangle_dos_name(vol, unixname, next+1, len - (int)((next+1) - pp)); + } + return; + } + } + closedir(d); + } + + if (parent_slash) *parent_slash='/'; + if (slash) *slash='/'; +} + +static inline int get_n_p(uint8 **p) +{ + int pc=**p; + (*p)++; + if (pc == '\\') { + pc=**p; + (*p)++; + } else if (pc == 255) { + pc=**p; + (*p)++; + switch (pc) { + case 0xaa : + case '*' : return(3000); /* star */ + + case 0xae : + case '.' : return(1000); /* point */ + + case 0xbf : + case '?' : return(2000); /* ? */ + + default : break; + } + } + return(pc); +} + +static int ns_fn_dos_match(uint8 *s, uint8 *p, int soptions) +/* OS/2 name matching routine */ +{ + int pc, sc; + uint state = 0; + int anf, ende; + int not = 0; + uint found = 0; + while ( (pc = get_n_p(&p)) != 0) { + if (!(soptions & VOL_OPTION_IGNCASE)) { + if (soptions & VOL_OPTION_DOWNSHIFT){ /* only downshift chars */ + if (*s >= 'A' && *s <= 'Z') return(0); + } else { + if (*s >= 'a' && *s <= 'z') return(0); + } + } + switch (state){ + case 0 : + switch (pc) { + case '.' : + case 1000: if (*s && ('.' != *s++)) + return(0); + break; + + case '?' : + case 2000: if (!*s) return(0); + ++s; + break; + + case '*' : + case 3000: { + uint8 *pp; + int np; + if (!*p) return(1); /* last star */ + while (*s) { + if (ns_fn_dos_match(s, p, soptions) == 1) return(1); + else if (*s=='.') { + pp=p; + if (!get_n_p(&p) || !get_n_p(&p)) + return(0); + p=pp; + } + ++s; + } + pp=p; + np=get_n_p(&p); + p=pp; + if (np == '.' || np == 1000) + return(ns_fn_dos_match(s, p, soptions)); + } + return(0); + + case '[' : if ( (*p == '!') || (*p == '^') ){ + ++p; + not = 1; + } + state = 1; + continue; + + default : if (soptions & VOL_OPTION_IGNCASE) { + if (!dfn_imatch(*s, pc)) + return(0); + } else if (pc != *s) return(0); + ++s; + break; + + } /* switch */ + break; + + case 1 : /* Bereich von Zeichen */ + sc = *s++; + found = not; + if (!sc) return(0); + do { + if (pc == '\\') pc = *(p++); + if (!pc) return(0); + anf = pc; + if (*p == '-' && *(p+1) != ']'){ + ende = *(++p); + p++; + } + else ende = anf; + if (found == not) { /* only if not found */ + if (anf == sc || (anf <= sc && sc <= ende)) + found = !not; + } + } while ((pc = *(p++)) != ']'); + if (! found ) return(0); + not = 0; + found = 0; + state = 0; + break; + + default : break; + } /* switch */ + } /* while */ + if (*s=='.' && *(s+1)=='\0') return(1); + return ( (*s) ? 0 : 1); +} +#endif diff --git a/src/namspace.c b/src/namspace.c index 8f68b77..855d6cb 100644 --- a/src/namspace.c +++ b/src/namspace.c @@ -45,6 +45,7 @@ #include "unxfile.h" #include "namspace.h" #include "nameos2.h" +#include "namedos.h" #if WITH_NAME_SPACE_CALLS @@ -654,11 +655,12 @@ leave_build_nwpath: else up_fn(pp); - mangle_dos_name(v, unixname, pp); + mangle_dos_name(v, unixname, pp, sizeof(unixname)-v->unixnamlen-npbeg); if (nplen > 0) { unix2doscharset(pp); - memcpy(nwpath->path+npbeg, pp, nplen); + strmaxcpy(nwpath->path+npbeg, pp, + sizeof(nwpath->path)-npbeg-1); } XDPRINTF((5,0, "Mangle DOS unixname='%s'", unixname)); } @@ -908,46 +910,47 @@ static int build_base(int namespace, static int build_dos_name(DIR_BASE_ENTRY *e, uint8 *fname, int size_fname) { - uint8 *ss=e->nwpath.fn; - int len=0; - int pf=0; - int is_ok=1; - int options=get_volume_options(e->nwpath.volume); - for (; *ss; ss++){ - if (*ss == '.') { - if (pf++) { /* no 2. point */ - is_ok=0; - break; - } - len=0; - } else { - ++len; - if ((pf && len > 3) || len > 8) { - is_ok=0; - break; - } - if (!(options & VOL_OPTION_IGNCASE)){ - if (options & VOL_OPTION_DOWNSHIFT){ /* only downshift chars */ - if (*ss >= 'A' && *ss <= 'Z') { - is_ok=0; - break; - } - } else { /* only upshift chars */ - if (*ss >= 'a' && *ss <= 'z') { - is_ok=0; - break; - } - } - } - } - } - if (is_ok) { - strmaxcpy(fname, e->nwpath.fn, size_fname-1); - up_fn(fname); - return(strlen(fname)); + char *parent_unix; + int result; + N_NW_PATH parent_path; + int plen; + + if (!e || !fname || size_fname < 2) return(0); + + /* + * e->nwpath.path contains the full entry path and e->nwpath.fn points to + * the last component inside that buffer. alloc_nwpath2unix(..., 1|2) + * only gives the parent if fn is behind path. For entries directly in the + * volume root fn == path, so the old code accidentally used the entry + * itself as parent. That made collision checks local to each directory and + * both "long directory name one" and "long directory name two" became + * LONG_DIR. + */ + memcpy(&parent_path, &(e->nwpath), sizeof(parent_path)); + if (e->nwpath.fn && e->nwpath.fn > e->nwpath.path) { + plen = (int)(e->nwpath.fn - e->nwpath.path); + if (plen > 0 && e->nwpath.path[plen-1] == '/') --plen; + memcpy(parent_path.path, e->nwpath.path, plen); + parent_path.path[plen] = '\0'; } else { - return(sprintf(fname, "%ld.___", (long)e->nwpath.statb.st_ino)); + parent_path.path[0] = '\0'; } + parent_path.fn = parent_path.path + strlen((char*)parent_path.path); + + parent_unix = alloc_nwpath2unix(&parent_path, 2); + result = build_dos_83_alias(get_volume_options(e->nwpath.volume), + (uint8*)parent_unix, + e->nwpath.fn, + e->nwpath.statb.st_ino, + fname, + size_fname); + XDPRINTF((99,0,"NSDOSNAME parent='%s' real='%s' alias='%s' inode=%ld", + parent_unix ? parent_unix : "", + e->nwpath.fn ? (char*)e->nwpath.fn : "", + fname ? (char*)fname : "", + (long)e->nwpath.statb.st_ino)); + xfree(parent_unix); + return(result); } #if 0 @@ -1517,7 +1520,19 @@ static int search_match(struct dirent *dirbuff, XDPRINTF((8,0,"search_match, Name='%s' dname='%s'", name, dname)); if (!inode_search) { if (namespace == NAME_DOS) { - flag = (*name != '.' && fn_dos_match_old(dname, entry, vol_options)); + if (*name != '.') { + uint8 dosalias[14]; + build_dos_83_alias(vol_options, ds->unixname, name, + dirbuff->d_ino, dosalias, sizeof(dosalias)); + flag = fn_dos_match_old(dosalias, entry, vol_options); + /* + * Keep dname as the real on-disk name. get_add_new_entry() + * appends dname to the parent and stats it; passing the synthetic + * DOS alias here makes long-name matches fail with + * "nw_search_file_dir:Cannot add entry". The alias is only for + * matching and for the returned INFO_MSK_ENTRY_NAME. + */ + } } else if (namespace == NAME_OS2) { flag = (*name != '.' || (*(name+1) != '.' && *(name+1) != '\0' )) && fn_os2_match(dname, entry, vol_options); diff --git a/src/nwvolume.c b/src/nwvolume.c index 57359cc..2b1c62c 100644 --- a/src/nwvolume.c +++ b/src/nwvolume.c @@ -64,6 +64,88 @@ char *path_trustees=NULL; static int max_nw_vols=MAX_NW_VOLS; + +/* + * Modern Linux filesystems can use inode numbers larger than 28 bits. + * The classic MARS_NWE handle format uses the upper 4 bits for the + * dev/namespace map index and the lower 28 bits for the inode. That + * breaks for volumes using multiple namespaces, for example DOS + OS2, + * when VOL_OPTION_ONE_DEV ('o') is not enabled. + * + * Keep the external 32-bit NetWare handle format, but map real Unix inode + * numbers to small pseudo inode numbers per volume/map entry. This allows + * OS2/NFS namespaces on large-inode filesystems without requiring the + * small 'o' ONE_DEV option. + * + * VOL_OPTION_ONE_DEV keeps its old direct 32-bit inode behaviour. + */ +typedef struct inode_handle_map_s { + int volume; + int map_entry; + ino_t inode; + uint32 handle_inode; + struct inode_handle_map_s *next; +} INODE_HANDLE_MAP; + +static INODE_HANDLE_MAP *inode_handle_maps = NULL; +static uint32 next_handle_inode = 1; + +static void free_inode_handle_maps(void) +{ + INODE_HANDLE_MAP *m = inode_handle_maps; + while (m) { + INODE_HANDLE_MAP *n = m->next; + xfree(m); + m = n; + } + inode_handle_maps = NULL; + next_handle_inode = 1; +} + +static uint32 inode_to_handle_inode(int volume, int map_entry, ino_t inode) +{ + INODE_HANDLE_MAP *m; + + for (m = inode_handle_maps; m; m = m->next) { + if (m->volume == volume && m->map_entry == map_entry && m->inode == inode) + return(m->handle_inode); + } + + if (next_handle_inode > 0x0fffffff) { + XDPRINTF((1,0, + "No free pseudo inode handle for vol=%d map=%d inode=%ld", + volume, map_entry, (long)inode)); + return(0); + } + + m = (INODE_HANDLE_MAP*)xcmalloc(sizeof(INODE_HANDLE_MAP)); + m->volume = volume; + m->map_entry = map_entry; + m->inode = inode; + m->handle_inode = next_handle_inode++; + m->next = inode_handle_maps; + inode_handle_maps = m; + + XDPRINTF((99,0, + "Pseudo inode map vol=%d map=%d inode=%ld to pseudo=0x%x", + volume, map_entry, (long)inode, m->handle_inode)); + return(m->handle_inode); +} + +static ino_t handle_inode_to_inode(int volume, int map_entry, uint32 handle_inode) +{ + INODE_HANDLE_MAP *m; + + for (m = inode_handle_maps; m; m = m->next) { + if (m->volume == volume && m->map_entry == map_entry + && m->handle_inode == handle_inode) + return(m->inode); + } + + /* Compatibility fallback for old/direct handles. */ + return((ino_t)handle_inode); +} + static void free_vol_trustee(NW_VOL *vol) { if (vol) { @@ -143,6 +225,7 @@ void nw_init_volumes(FILE *f) } rewind(f); used_nw_volumes = 0; + free_inode_handle_maps(); loaded_namespaces = 0; new_str(path_vol_inodes_cache, "/var/spool/nwserv/.volcache"); new_str(path_attributes, "/var/nwserv/attrib"); @@ -393,8 +476,27 @@ static int look_name_space_map(NW_VOL *v, DEV_NAMESPACE_MAP *dnm, int k=-1; while (++k < v->maps_count) { mp=v->dev_namespace_maps[k]; - if (mp->dev == dnm->dev && mp->namespace == dnm->namespace) - return(k); + if (mp->dev == dnm->dev) { + /* + * VOL_OPTION_ONE_DEV encodes handles as the raw 32-bit inode: + * + * handle = inode + * + * In that mode there is no room in the handle for the dev/namespace + * map index. All namespaces on this one Unix device therefore have + * to share the same map entry. Otherwise a volume with both 'o' and + * 'O' set can map the DOS root entry, but later OS/2 namespace lookups + * fail with: + * + * Cannot map inode=..., dev=..., namespace=4 to vol=... handle + * + * Non-ONE_DEV volumes keep the old behaviour and still distinguish + * mappings by both device and namespace. + */ + if ((v->options & VOL_OPTION_ONE_DEV) + || mp->namespace == dnm->namespace) + return(k); + } } if (do_insert && v->maps_count < v->max_maps_count) { /* now do insert the new map */ @@ -411,20 +513,31 @@ uint32 nw_vol_inode_to_handle(int volume, ino_t inode, { if (volume > -1 && volume < used_nw_volumes) { NW_VOL *v= &(nw_volumes[volume]); - if (inode > 0 && inode <= v->high_inode) { + if (inode > 0) { int result = look_name_space_map(v, dnm, 1); if (result > -1) { - uint32 handle = (v->options & VOL_OPTION_ONE_DEV) - ? (uint32)inode - : (((uint32)result) << 28) | (uint32) inode; - XDPRINTF((3,0, "Handle map inode=%d, dev=%d, namespace=%d to handle 0x%x", - inode, dnm->dev, dnm->namespace, handle)); + uint32 handle; + + if (v->options & VOL_OPTION_ONE_DEV) { + if (inode > v->high_inode) + goto cannot_map; + handle = (uint32)inode; + } else { + uint32 handle_inode = inode_to_handle_inode(volume, result, inode); + if (!handle_inode) + goto cannot_map; + handle = (((uint32)result) << 28) | handle_inode; + } + + XDPRINTF((3,0, "Handle map inode=%ld, dev=%d, namespace=%d to handle 0x%x", + (long)inode, dnm->dev, dnm->namespace, handle)); return(handle); } } } - XDPRINTF((1,0, "Cannot map inode=%d, dev=%d, namespace=%d to vol=%d handle", - inode, dnm->dev, dnm->namespace, volume)); +cannot_map: + XDPRINTF((1,0, "Cannot map inode=%ld, dev=%d, namespace=%d to vol=%d handle", + (long)inode, dnm->dev, dnm->namespace, volume)); return(0L); } @@ -438,13 +551,16 @@ ino_t nw_vol_handle_to_inode(int volume, uint32 handle, ? 0 : (int) ((handle >> 28) & 0xF); if (entry > -1 && entry < v->maps_count) { + ino_t inode = (v->options & VOL_OPTION_ONE_DEV) + ? (ino_t)(handle & v->high_inode) + : handle_inode_to_inode(volume, entry, handle & v->high_inode); if (dnm) memcpy(dnm, v->dev_namespace_maps[entry], sizeof(DEV_NAMESPACE_MAP)); - XDPRINTF((3, 0, "vol=%d, handle=0x%x to ino=%d, dev=%d, namespace=%d", - volume, handle, (int)(handle & v->high_inode), + XDPRINTF((3, 0, "vol=%d, handle=0x%x to ino=%ld, dev=%d, namespace=%d", + volume, handle, (long)inode, v->dev_namespace_maps[entry]->dev, v->dev_namespace_maps[entry]->namespace)); - return((ino_t) (handle & v->high_inode)); + return(inode); } } XDPRINTF((1, 0, "Can't vol=%d, handle=0x%x to inode", volume, handle));