Files
ncpfs/dir.c
ncpfs archive import 7591e85f7b Import ncpfs 0.1
2026-04-28 20:39:57 +02:00

918 lines
23 KiB
C

/*
* dir.c
*
* Copyright (C) 1995 by Volker Lendecke
*
*/
#include <linux/config.h>
#ifdef MODULE
#include <linux/module.h>
#include <linux/version.h>
#endif
#include <linux/sched.h>
#include <linux/errno.h>
#include <linux/stat.h>
#include <linux/kernel.h>
#include <linux/malloc.h>
#include <linux/mm.h>
#include <linux/ncp_fs.h>
#include <asm/segment.h>
#include <linux/errno.h>
#include "ncplib.h"
#define NAME_OFFSET(de) ((int) ((de)->d_name - (char *) (de)))
#define ROUND_UP(x) (((x)+3) & ~3)
static int
ncp_dir_read(struct inode *inode, struct file *filp, char *buf, int count);
static int
ncp_readdir(struct inode *inode, struct file *filp,
void *dirent, filldir_t filldir);
static int
ncp_read_volume_list(struct ncp_server *server, int start_with,
int cache_size);
static int
ncp_do_readdir(struct ncp_server *server, struct inode *dir, int fpos,
int cache_size, struct ncp_dirent *entry);
static int
get_pname(struct inode *dir, const char *name, int len,
char **res_path, int *res_len);
static int
get_pname_static(struct inode *dir, const char *name, int len,
char *path, int *res_len);
static struct inode *
ncp_iget(struct inode *dir, char *path, struct ncp_dirent *finfo);
static void
put_pname(char *path);
static struct ncp_inode_info *
ncp_find_inode(struct ncp_server *server, const char *path);
static int
ncp_lookup(struct inode *dir, const char *__name,
int len, struct inode **result);
static int
date_dos2unix(unsigned short time,unsigned short date);
/*
static void
date_unix2dos(int unix_date,unsigned short *time, unsigned short *date);
*/
static inline void
str_upper(char *name)
{
while (*name) {
if (*name >= 'a' && *name <= 'z')
*name -= ('a' - 'A');
name++;
}
}
static inline void
str_lower(char *name)
{
while (*name) {
if (*name >= 'A' && *name <= 'Z')
*name += ('a' - 'A');
name ++;
}
}
static struct file_operations ncp_dir_operations = {
NULL, /* lseek - default */
ncp_dir_read, /* read - bad */
NULL, /* write - bad */
ncp_readdir, /* readdir */
NULL, /* select - default */
ncp_ioctl, /* ioctl - default */
NULL, /* mmap */
NULL, /* no special open code */
NULL, /* no special release code */
NULL /* fsync */
};
struct inode_operations ncp_dir_inode_operations = {
&ncp_dir_operations, /* default directory file ops */
NULL, /* create */
ncp_lookup, /* lookup */
NULL, /* link */
NULL, /* unlink */
NULL, /* symlink */
NULL, /* mkdir */
NULL, /* rmdir */
NULL, /* mknod */
NULL, /* rename */
NULL, /* readlink */
NULL, /* follow_link */
NULL, /* bmap */
NULL, /* truncate */
NULL, /* permission */
NULL /* smap */
};
static int
ncp_dir_read(struct inode *inode, struct file *filp, char *buf, int count)
{
return -EISDIR;
}
/* In ncpfs, we have unique inodes across all mounted filesystems, for
all inodes that are in memory. That's why it's enough to index the
directory cache by the inode number. */
static unsigned long c_ino = 0;
static int c_size;
static int c_seen_eof;
static int c_last_returned_index;
static struct ncp_dirent* c_entry = NULL;
static int
ncp_readdir(struct inode *inode, struct file *filp,
void *dirent, filldir_t filldir)
{
int result, i = 0;
int index = 0;
struct ncp_dirent *entry = NULL;
struct ncp_server *server = NCP_SERVER(inode);
DDPRINTK("ncp_readdir: filp->f_pos = %d\n", (int)filp->f_pos);
DDPRINTK("ncp_readdir: inode->i_ino = %ld, c_ino = %ld\n",
inode->i_ino, c_ino);
if (!inode || !S_ISDIR(inode->i_mode)) {
printk("ncp_readdir: inode is NULL or not a directory\n");
return -EBADF;
}
if (c_entry == NULL)
{
i = sizeof (struct ncp_dirent) * NCP_READDIR_CACHE_SIZE;
c_entry = (struct ncp_dirent *) ncp_kmalloc(i, GFP_KERNEL);
if (c_entry == NULL) {
printk("ncp_readdir: no MEMORY for cache\n");
return -ENOMEM;
}
for (i = 0; i < NCP_READDIR_CACHE_SIZE; i++) {
c_entry[i].path =
(char *) ncp_kmalloc(NCP_MAXNAMELEN + 1,
GFP_KERNEL);
if (c_entry[i].path == NULL) {
DPRINTK("ncp_readdir: could not alloc path\n");
while (--i>=0)
kfree(c_entry[i].path);
kfree(c_entry);
c_entry = NULL;
return -ENOMEM;
}
}
}
if (filp->f_pos == 0) {
ncp_invalid_dir_cache(inode->i_ino);
}
if (inode->i_ino == c_ino) {
for (i = 0; i < c_size; i++) {
if (filp->f_pos == c_entry[i].f_pos) {
entry = &c_entry[i];
c_last_returned_index = i;
index = i;
break;
}
}
if ((entry == NULL) && c_seen_eof)
return 0;
}
if (entry == NULL) {
DPRINTK("ncp_readdir: Not found in cache.\n");
if (inode->i_ino == (int)&(server->root)) {
result = ncp_read_volume_list(server, filp->f_pos,
NCP_READDIR_CACHE_SIZE);
DPRINTK("ncp_read_volume_list returned %d\n", result);
} else {
result = ncp_do_readdir(server, inode, filp->f_pos,
NCP_READDIR_CACHE_SIZE,
c_entry);
DPRINTK("ncp_readdir returned %d\n", result);
}
if (result < 0) {
c_ino = 0;
return result;
}
if (result > 0) {
c_seen_eof = (result < NCP_READDIR_CACHE_SIZE);
c_ino = inode->i_ino;
c_size = result;
entry = c_entry;
c_last_returned_index = 0;
index = 0;
for (i = 0; i < c_size; i++) {
str_lower(c_entry[i].path);
}
}
}
if (entry == NULL) {
/* Nothing found, even from a ncp call */
return 0;
}
while (index < c_size) {
/* We found it. For getwd(), we have to return the
correct inode in d_ino if the inode is currently in
use. Otherwise the inode number does not
matter. (You can argue a lot about this..) */
int path_len;
int len;
struct ncp_inode_info *ino_info;
char complete_path[NCP_MAXPATHLEN];
len = strlen(entry->path);
if ((result = get_pname_static(inode, entry->path, len,
complete_path,
&path_len)) < 0)
return result;
ino_info = ncp_find_inode(server, complete_path);
/* Some programs seem to be confused about a zero
inode number, so we set it to one. Thanks to
Gordon Chaffee for this one. */
if (ino_info == NULL) {
ino_info = (struct ncp_inode_info *) 1;
}
DDPRINTK("ncp_readdir: entry->path = %s\n", entry->path);
DDPRINTK("ncp_readdir: entry->f_pos = %ld\n", entry->f_pos);
if (filldir(dirent, entry->path, len,
entry->f_pos, (ino_t)ino_info) < 0) {
break;
}
if ( (inode->i_ino != c_ino)
|| (entry->f_pos != filp->f_pos)) {
/* Someone has destroyed the cache while we slept
in filldir */
break;
}
filp->f_pos += 1;
index += 1;
entry += 1;
}
return 0;
}
static int
ncp_read_volume_list(struct ncp_server *server, int start_with, int cache_size)
{
struct ncp_dirent *entry = c_entry;
int total_count = 0;
int i;
for (i=0; i<NCP_NUMBER_OF_VOLUMES; i++) {
struct ncp_volume_info info;
if (ncp_get_volume_info_with_number(server, i, &info) != 0) {
return total_count;
}
if (strlen(info.volume_name) > 0) {
if (total_count < start_with) {
DPRINTK("ncp_read_volumes: skipped vol: %s\n",
info.volume_name);
} else if (total_count >= start_with + cache_size) {
return (total_count - start_with);
} else {
DPRINTK("ncp_read_volumes: found vol %s\n",
info.volume_name);
entry->attr = aDIR;
entry->mtime = 0;
entry->ctime = 0;
entry->atime = 0;
entry->size = 1024;
entry->f_pos = total_count;
strcpy(entry->path, info.volume_name);
entry += 1;
}
total_count += 1;
}
}
return (total_count - start_with);
}
static int
ncp_do_readdir(struct ncp_server *server, struct inode *dir, int fpos,
int cache_size, struct ncp_dirent *entry)
{
struct ncp_filesearch_info fsinfo;
struct ncp_file_info finfo;
int total_count = 0;
void doit(int attr) {
if (ncp_file_search_init(server, 0, NCP_FINFO(dir)->path,
&fsinfo) != 0) {
DPRINTK("could not fs init\n");
return;
}
while (ncp_file_search_continue(server, &fsinfo, attr, "*",
&finfo) == 0) {
if (total_count < fpos) {
DPRINTK("ncp_do_readdir: skipped file: %s\n",
finfo.file_name);
} else if (total_count >= fpos + cache_size) {
return;
} else {
DPRINTK("ncp_do_readdir: found file: %s\n",
finfo.file_name);
entry->attr = attr;
entry->mtime =
date_dos2unix(finfo.update_time,
finfo.update_date);
entry->ctime =
date_dos2unix(0, finfo.creation_date);
entry->atime =
date_dos2unix(0, finfo.access_date);
;
entry->size = (attr & aDIR) != 0 ?
1024 : finfo.file_length;
entry->f_pos = total_count;
strcpy(entry->path, finfo.file_name);
entry += 1;
}
total_count += 1;
}
return;
}
doit(0);
doit(aDIR);
return (total_count - fpos);
}
void
ncp_init_dir_cache(void)
{
c_ino = 0;
c_entry = NULL;
}
void
ncp_invalid_dir_cache(unsigned long ino)
{
if (ino == c_ino) {
c_ino = 0;
c_seen_eof = 0;
}
}
void
ncp_free_dir_cache(void)
{
int i;
DPRINTK("ncp_free_dir_cache: enter\n");
if (c_entry == NULL)
return;
for (i = 0; i < NCP_READDIR_CACHE_SIZE; i++) {
ncp_kfree_s(c_entry[i].path, NAME_MAX + 1);
}
ncp_kfree_s(c_entry,
sizeof(struct ncp_dirent) * NCP_READDIR_CACHE_SIZE);
c_entry = NULL;
DPRINTK("ncp_free_dir_cache: exit\n");
}
/* get_pname_static: it expects the res_path to be a preallocated
string of len NCP_MAXPATHLEN. */
static int
get_pname_static(struct inode *dir, const char *name, int len,
char *path, int *res_len)
{
char *parentname = NCP_INOP(dir)->finfo.path;
int parentlen = NCP_INOP(dir)->finfo.len;
#if 1
if (parentlen != strlen(parentname)) {
printk("get_pname: parent->finfo.len = %d instead of %d\n",
parentlen, strlen(parentname));
parentlen = strlen(parentname);
}
#endif
DPRINTK("get_pname_static: parentname = %s, len = %d\n",
parentname, parentlen);
if (len > NCP_MAXNAMELEN) {
return -ENAMETOOLONG;
}
/* Fast cheat for . */
if (len == 0 || (len == 1 && name[0] == '.')) {
memcpy(path, parentname, parentlen + 1);
*res_len = parentlen;
return 0;
}
/* Hmm, what about .. ? */
if (len == 2 && name[0] == '.' && name[1] == '.') {
char *pos = strrchr(parentname, '\\');
if ( (pos == NULL)
&& (parentlen == 0)) {
/* We're at the top */
path[0] = '\\';
path[1] = '\0';
*res_len = 2;
return 0;
}
if (pos == NULL) {
printk("ncp_make_name: Bad parent NCP-name: %s",
parentname);
return -ENODATA;
}
len = pos - parentname;
memcpy(path, parentname, len);
path[len] = '\0';
}
else if (parentlen == 0) {
memcpy(path, name, len);
path[len] = ':';
path[len+1] = '\0';
len = len+1;
}
else
{
if (len + parentlen + 2 > NCP_MAXPATHLEN)
return -ENAMETOOLONG;
memcpy(path, parentname, parentlen);
path[parentlen] = '\\';
memcpy(path + parentlen + 1, name, len);
path[parentlen + 1 + len] = '\0';
len = parentlen + len + 1;
}
*res_len = len;
DPRINTK("get_pname: path = %s, *pathlen = %d\n",
path, *res_len);
return 0;
}
static int
get_pname(struct inode *dir, const char *name, int len,
char **res_path, int *res_len)
{
char result[NCP_MAXPATHLEN];
int result_len;
int res;
if ((res = get_pname_static(dir,name,len,result,&result_len) != 0)) {
return res;
}
if ((*res_path = ncp_kmalloc(result_len+1, GFP_KERNEL)) == NULL) {
printk("get_pname: Out of memory while allocating name.");
return -ENOMEM;
}
strcpy(*res_path, result);
*res_len = result_len;
return 0;
}
static void
put_pname(char *path)
{
ncp_kfree_s(path, 0);
}
/* Insert a NEW ncp_inode_info into the inode tree of our filesystem,
under dir. The caller must assure that it's not already there. We
assume that path is allocated for us. */
static struct inode *
ncp_iget(struct inode *dir, char *path, struct ncp_dirent *finfo)
{
struct inode *inode;
struct ncp_inode_info *new_inode_info;
struct ncp_inode_info *root;
if (!dir) {
printk("ncp_iget: dir is NULL\n");
return NULL;
}
if (!path) {
printk("ncp_iget: path is NULL\n");
return NULL;
}
if (!finfo) {
printk("ncp_iget: finfo is NULL\n");
return NULL;
}
new_inode_info = ncp_kmalloc(sizeof(struct ncp_inode_info),
GFP_KERNEL);
if (new_inode_info == NULL) {
printk("ncp_iget: could not alloc mem for %s\n", path);
return NULL;
}
new_inode_info->state = INODE_LOOKED_UP;
new_inode_info->nused = 0;
new_inode_info->dir = NCP_INOP(dir);
new_inode_info->finfo = *finfo;
new_inode_info->finfo.opened = 0;
new_inode_info->finfo.path = path;
new_inode_info->finfo.len = strlen(path);
NCP_INOP(dir)->nused += 1;
/* We have to link the new inode_info into the doubly linked
list of inode_infos to make a complete linear search
possible. */
root = &(NCP_SERVER(dir)->root);
new_inode_info->prev = root;
new_inode_info->next = root->next;
root->next->prev = new_inode_info;
root->next = new_inode_info;
if (!(inode = iget(dir->i_sb, (int)new_inode_info))) {
printk("ncp_iget: iget failed!");
return NULL;
}
return inode;
}
void
ncp_free_inode_info(struct ncp_inode_info *i)
{
if (i == NULL) {
printk("ncp_free_inode: i == NULL\n");
return;
}
i->state = INODE_CACHED;
while ((i->nused == 0) && (i->state == INODE_CACHED)) {
struct ncp_inode_info *dir = i->dir;
i->next->prev = i->prev;
i->prev->next = i->next;
ncp_kfree_s(i->finfo.path, i->finfo.len+1);
ncp_kfree_s(i, sizeof(struct ncp_inode_info));
if (dir == NULL) return;
(dir->nused)--;
i = dir;
}
}
void
ncp_init_root(struct ncp_server *server)
{
struct ncp_inode_info *root = &(server->root);
DPRINTK("ncp_init_root: server %s\n", server->m.server_name);
root->finfo.opened = 0;
root->finfo.attr = aDIR;
root->finfo.size = 1024;
root->finfo.mtime = 0;
root->finfo.ctime = 0;
root->finfo.atime = 0;
server->root_path = '\0';
root->finfo.path = &(server->root_path);
root->finfo.len = 0;
root->state = INODE_LOOKED_UP;
root->nused = 1;
root->dir = NULL;
root->next = root->prev = root;
return;
}
void
ncp_free_all_inodes(struct ncp_server *server)
{
/* Here nothing should be to do. I do not know whether it's
better to leave some memory allocated or be stuck in an
endless loop */
#if 1
struct ncp_inode_info *root = &(server->root);
if (root->next != root) {
printk("ncp_free_all_inodes: INODES LEFT!!!\n");
}
while (root->next != root) {
printk("ncp_free_all_inodes: freeing inode\n");
ncp_free_inode_info(root->next);
/* In case we have an endless loop.. */
schedule();
}
#endif
return;
}
/* We will search the inode that belongs to this name, currently by a
complete linear search through the inodes belonging to this
filesystem. This has to be fixed. */
static struct ncp_inode_info *
ncp_find_inode(struct ncp_server *server, const char *path)
{
struct ncp_inode_info *result = &(server->root);
if (path == NULL)
return NULL;
do {
if (strcmp(result->finfo.path, path) == 0)
return result;
result = result->next;
} while (result != &(server->root));
return NULL;
}
static int
ncp_lookup(struct inode *dir, const char *__name, int len,
struct inode **result)
{
char *name = NULL;
struct ncp_dirent finfo;
struct ncp_server *server;
struct ncp_inode_info *result_info;
int error;
int found_in_cache;
int path_len;
*result = NULL;
if (!dir || !S_ISDIR(dir->i_mode)) {
printk("ncp_lookup: inode is NULL or not a directory.\n");
iput(dir);
return -ENOENT;
}
DPRINTK("ncp_lookup: %s\n", __name);
server = NCP_SERVER(dir);
/* Fast cheat for . */
if (len == 0 || (len == 1 && __name[0] == '.')) {
*result = dir;
return 0;
}
/* Now we will have to build up an NCP filename. */
if ((error = get_pname(dir, __name, len, &name, &path_len)) < 0) {
iput(dir);
return error;
}
result_info = ncp_find_inode(NCP_SERVER(dir), name);
if (result_info != 0) {
if (result_info->state == INODE_CACHED)
result_info->state = INODE_LOOKED_UP;
put_pname(name);
/* Here we convert the inode_info address into an
inode number */
*result = iget(dir->i_sb, (int)result_info);
iput(dir);
if (*result == NULL) {
return -EACCES;
}
return 0;
}
/* Ok, now we have made our name. We have to build a new
ncp_inode_info struct and insert it into the tree, if it is
a name that exists on the server */
/* If the file is in the dir cache, we do not have to ask the
server. */
found_in_cache = 0;
if (dir->i_ino == c_ino) {
int first = c_last_returned_index;
int i;
i = first;
do {
DDPRINTK("ncp_lookup: trying index: %d, name: %s\n",
i, c_entry[i].path);
if (strcmp(c_entry[i].path, __name) == 0) {
DPRINTK("ncp_lookup: found in cache!\n");
finfo = c_entry[i];
finfo.path = NULL; /* It's not ours! */
found_in_cache = 1;
break;
}
i = (i + 1) % c_size;
DDPRINTK("ncp_lookup: index %d, name %s failed\n",
i, c_entry[i].path);
} while (i != first);
}
if (found_in_cache == 0) {
char this_name[len+1];
memcpy(this_name, __name, len);
this_name[len] = 0;
if (dir->i_ino == (int)&(server->root)) {
/* We want to look up a volume. We only want
to know whether it exists, nothing more. */
int vol_no;
DPRINTK("ncp_lookup: looking up volume %s\n",
this_name);
if (ncp_get_volume_number(server,this_name,
&vol_no)!=0) {
put_pname(name);
iput(dir);
return -ENOENT;
}
finfo.attr = aDIR;
finfo.mtime = 0;
finfo.ctime = 0;
finfo.atime = 0;
finfo.size = 1024;
} else {
struct ncp_file_info ninfo;
if (ncp_get_finfo(server, 0, NCP_FINFO(dir)->path,
this_name, &ninfo) != 0) {
error = -ENOENT;
}
finfo.attr = ninfo.file_attributes;
finfo.mtime = date_dos2unix(ninfo.update_time,
ninfo.update_date);
finfo.ctime = date_dos2unix(0, ninfo.creation_date);
finfo.atime = date_dos2unix(0, ninfo.access_date);
finfo.size = (ninfo.file_attributes & aDIR) != 0
? 1024 : ninfo.file_length;
}
if (error < 0) {
put_pname(name);
iput(dir);
return error;
}
}
if (!(*result = ncp_iget(dir, name, &finfo))) {
put_pname(name);
iput(dir);
return -EACCES;
}
DDPRINTK("ncp_lookup: %s => %lu\n", name, (unsigned long)result_info);
iput(dir);
return 0;
}
/* The following are taken directly from msdos-fs */
/* Linear day numbers of the respective 1sts in non-leap years. */
static int day_n[] = { 0,31,59,90,120,151,181,212,243,273,304,334,0,0,0,0 };
/* JanFebMarApr May Jun Jul Aug Sep Oct Nov Dec */
extern struct timezone sys_tz;
/*
static int
utc2local(int time)
{
return time - sys_tz.tz_minuteswest*60;
}
*/
static int
local2utc(int time)
{
return time + sys_tz.tz_minuteswest*60;
}
/* Convert a MS-DOS time/date pair to a UNIX date (seconds since 1 1 70). */
static int
date_dos2unix(unsigned short time,unsigned short date)
{
int month,year,secs;
month = ((date >> 5) & 15)-1;
year = date >> 9;
secs = (time & 31)*2+60*((time >> 5) & 63)+(time >> 11)*3600+86400*
((date & 31)-1+day_n[month]+(year/4)+year*365-((year & 3) == 0 &&
month < 2 ? 1 : 0)+3653);
/* days since 1.1.70 plus 80's leap day */
return local2utc(secs);
}
/* Convert linear UNIX date to a MS-DOS time/date pair. */
#if 0
static void
date_unix2dos(int unix_date,unsigned short *time, unsigned short *date)
{
int day,year,nl_day,month;
unix_date = utc2local(unix_date);
*time = (unix_date % 60)/2+(((unix_date/60) % 60) << 5)+
(((unix_date/3600) % 24) << 11);
day = unix_date/86400-3652;
year = day/365;
if ((year+3)/4+365*year > day) year--;
day -= (year+3)/4+365*year;
if (day == 59 && !(year & 3)) {
nl_day = day;
month = 2;
}
else {
nl_day = (year & 3) || day <= 59 ? day : day-1;
for (month = 0; month < 12; month++)
if (day_n[month] > nl_day) break;
}
*date = nl_day-day_n[month-1]+1+(month << 5)+(year << 9);
}
#endif