/*
** Mfm.c
** Multimedia File Management
*/
#include <stdio.h>
#include <string.h>
/*#include <unistd.h>*/
#include <stdlib.h>
#include <fcntl.h>
#include <errno.h>
#include <sys/stat.h>
/*#include <dirent.h>*/
#include "Mfm.h"
/*
** When any of the MFM functions is used the first time within
** program, MFM attempts to load the global rules file:
**
** 1) If environment variable defined by MFM_GLOBAL_RULES exists,
** then the value of that variable is used as the filename of
** the global rules. Note that existence of this environment
** variable completely overrides the 'global_files'.
**
** 2) If environment variable is not defined, then the first file
** in global_files that exists, will be used.
**
** 3) If no global rules is found or some error occurs when reading
** the selected file, then no global rules are used.
*/
#define MFM_GLOBAL_RULES "MFM_RULES"
static char *global_files[] =
{
"/etc/mfm",
"/usr/local/etc/mfm",
NULL,
};
/*
** MFM keeps the .MFM of the last accessed directory in memory.
** This buffer is allocated as large as required, but to prevent
** "startup reallocation burst", a mimimun size is defined.
*/
#define MFM_MIN_RULE_BUFFER_SIZE 2000
/*
** MFM needs to construct filename occasionally. This value gives
** the maximum length of such path.
*/
#define MFM_MAX_FILEPATH_LENGTH 500
/*
** When MFM needs to create the local .MFM files, the following
** is the default file mode that is used
*/
#define MFM_DEFAULT_MODE 0644
/*
** matches
** Compare if a given string (name) matches the given
** mask. The mask can contain wild cards: '*' - match any
** number of chars, '?' - match any single character. Mask
** is terminated either with NUL byte or ':' character.
**
** return 0, if match found, msk points past the terminating
** character.
** 1, if not matched, msk points to the next character
** *after* the one that caused mismatch.
**
** *NOTE* This function is *NOT* fast if the mask has
** '*'s in it, the more, the slower it will become.
**
** 'nam' must not contain ':'!
*/
static int matches(msk, nam)
char **msk, *nam;
{
register char c, m;
for (;;)
{
m = *(*msk)++;
c = *nam++;
if (c == '\0')
break;
if ((m != c && m != '?') || c == '*')
break;
}
if (m != '*')
return c != 0 || (m != ':' && m != 0);
/*
** Any *'s following one in mask can be ignored. If mask ends
** after a '*', there is a match.
*/
for (;;)
if ((m = *(*msk)++) == '\0' || m == ':')
return 0; /* matched everything, end of mask */
else if (m != '*')
break; /* more work to do... */
/*
** m = next character after '*' in the mask. This must
** match the character in 'c'.
*/
if (m == '?')
{
/*
** '?' matches with any following single character.
** Eat nam away one by one and try to match the
** remainder with current 'msk'. Strings don't match
** if we run out of nam while doing this.
*/
for ( ; *nam; nam++)
{
char *tmp = *msk;
if (matches(&tmp, nam) == 0)
return *msk = tmp, 0;
}
return 1;
}
/*
** m contains a non-wild card character which must
** exist somewhere in nam for match to be successful.
** Look forward for this character and when found,
** eat it away and check if the remainder matches with
** the mask. If it doesn't, repeat the search and
** matches for the remaining nam.
*/
for ( ;; )
{
char *tmp = *msk;
while (c != m)
if ((c = *nam++) == '\0')
return 1;
if (matches(&tmp, nam) == 0)
return *msk = tmp, 0;
if ((c = *nam++) == '\0')
return 1;
}
}
/*
** equals
** Look for exact match from rules without wild card processing.
** Mask is terminated either with NUL byte or ':' character.
**
** return 0, if match found, msk points past the terminating
** character.
** 1, if not matched, msk points to the next character
** *after* the one that caused mismatch.
*/
static int equals(msk, nam)
char **msk, *nam;
{
register char c, m;
do
{
m = *(*msk)++;
c = *nam++;
if (c == 0)
return m != ':' && m != 0;
}
while (c == m);
return 1;
}
/*
** ******************************
** File Type management utilities
** ******************************
*/
static char *global_rules = NULL;
static int mfm_errno = MfmError_ERROR;
/*
** LoadRulesFile
** Read the content of the specified rules file into a buffer
** and zap LF's from it.
*/
static void LoadRulesFile(char *path, char *buf, int len)
{
FILE *fp = fopen(path, "rb");
if (fp != NULL && len > 2)
{
if ((len = fread(buf, 1, len-2, fp)) < 0)
len = 0;
fclose(fp);
buf[len] = 0;
buf[len+1] = 0;
while (buf = strchr(buf, '\n'))
*buf++ = 0;
}
else
{
*buf++ = 0;
*buf++ = 0;
}
}
/*
** SaveRulesFile
** Write a new rule (if specified) and append old rules after
** it.
*/
static int SaveRulesFile(char *path, char *rules, char *name, char *type)
{
FILE *fp = fopen(path, "wb");
if (fp == NULL)
return MfmError_ERROR;
if (name)
fprintf(fp, "%s:%s\n", name, type ? type : "");
while (*rules)
{
fprintf(fp, "%s\n", rules);
while (*rules++);
}
fclose(fp);
return MfmError_SUCCESS;
}
/*
** ReadConfiguration
** Read the predefined global configuration file into memory
*/
static void ReadConfiguration()
{
char *name;
struct stat info;
int i, r;
if ((name = getenv(MFM_GLOBAL_RULES)) != NULL)
r = stat(name, &info);
else for (i = 0, r = -1; name = global_files[i]; ++i)
if ((r = stat(name, &info)) == 0)
break;
if (r == 0 &&
(global_rules = (char *)malloc(info.st_size+2)) != NULL)
LoadRulesFile(name, global_rules, info.st_size+2);
else
global_rules = "\0";
}
/*
** LocalMfmName
** Build the name of the local MFM which is applicaple to the
** given name. The returned value is guaranteed to be valid
** only until next call to this function.
*/
static char *LocalMfmName(char *name)
{
static char mfm[] = {'.', 'M', 'F', 'M', 0};
static char buf[MFM_MAX_FILEPATH_LENGTH+1];
char *path = strrchr(name, '/');
if (path == NULL)
return mfm;
else if ((path-name)+1+sizeof(mfm) < sizeof(buf))
{
memcpy(buf,name,(path-name)+1);
memcpy(buf+(path-name)+1, mfm, sizeof(mfm));
return buf;
}
mfm_errno = MfmError_LONGNAME;
return NULL; /* Can't handle the file path, too long! */
}
/*
** ReadLocalRules
** Read the local rules file matching the specified file path.
** Return a pointer to the rules if found and NULL otherwise.
**
** Any previously existing locks on local file will be removed.
**
** 'lock' is non-zero when an update access to the local rules
** is required. The local rules file (.MFM) will be created if
** it doesn't exist and locked exclusively.
*/
#define MFM_NO_LOCK 0
#define MFM_LOCK 1
static char *ReadLocalRules(char *name, int lock)
{
static struct stat current;
static char *local_rules = NULL;
static int local_size = 0;
static int locked_fd = -1;
struct stat new;
char *path = LocalMfmName(name);
if (locked_fd >= 0)
{
close(locked_fd);
locked_fd = -1;
current.st_mtime = 0; /* Force reload! */
}
if (path == NULL)
return NULL;
if ((lock &&
((locked_fd = open(path, O_RDWR | O_CREAT, MFM_DEFAULT_MODE))<0 ||
lockf(locked_fd, F_LOCK, 0) ||
fstat(locked_fd, &new))
) || stat(path, &new))
{
mfm_errno = errno;
return NULL;
}
if (new.st_dev == current.st_dev && new.st_ino == current.st_ino &&
new.st_mtime == current.st_mtime)
return local_rules;
current = new;
if (local_size < current.st_size+2)
{
if (local_rules)
free(local_rules);
local_size = current.st_size + 2;
if (local_size < MFM_MIN_RULE_BUFFER_SIZE)
local_size = MFM_MIN_RULE_BUFFER_SIZE;
if ((local_rules = (char *)malloc(local_size)) == NULL)
{
local_size = 0;
mfm_errno = MfmError_MEMORY;
return NULL;
}
}
LoadRulesFile(path, local_rules, local_size);
return local_rules;
}
/*
** LookupRules
*/
static char *LookupRules(char *rules, char *name)
{
if (rules != NULL)
while (*rules)
if (matches(&rules, name) == 0)
return rules[-1] ? rules : rules-1;
else while (*rules++);
return NULL;
}
/*
** MfmGetContentType
*/
char *MfmGetContentType(char *name)
{
char *m = LookupRules(ReadLocalRules(name, MFM_NO_LOCK), name);
if (m != NULL)
return m;
if (global_rules == NULL)
ReadConfiguration();
return LookupRules(global_rules, name);
}
/*
** MfmPutContentType
*/
int MfmPutContentType(char *name, char *type)
{
char *rules = ReadLocalRules(name, MFM_LOCK);
char *m, *s;
if (rules == NULL)
return mfm_errno;
m = rules;
while (*m)
if (equals(&m, name) == 0)
{
/*
** Remove the matched definition from the rules
*/
for (s = --m; s > rules && s[-1]; --s);
while (*m++);
while (*m)
while (*s++ = *m++);
*s++ = 0;
break;
}
else while (*m++);
s = LocalMfmName(name);
SaveRulesFile(s, rules, type ? name : (char *)NULL, type);
rules = ReadLocalRules(name, MFM_NO_LOCK);
return MfmError_SUCCESS;
}
/*
** MfmSetContentType
**
** *NOTE*
** This function works properly ONLY IF wildcards are not used
** in local rules!!!
*/
int MfmSetContentType(char *name, char *type)
{
char *t;
if (global_rules == NULL)
ReadConfiguration();
t = LookupRules(global_rules, name);
if (t == type || (t && type && strcmp(t, type) == 0))
{
/*
** Global rules give out the same type. Now have to
** check if local rules would override this.
*/
if (LookupRules(ReadLocalRules(name,MFM_NO_LOCK),name)==NULL)
return MfmError_SUCCESS;
type = NULL;
}
return MfmPutContentType(name, type);
}
/*
** ****************************
** Directory Browsing utilities
** ****************************
*/
typedef struct MfmDirectoryRec
{
DIR *dirp;
MfmFilter filter;
void *data;
} MfmDirectoryRec;
MfmDirectory MfmOpenDirectory(char *name, MfmFilter filter, void *data)
{
MfmDirectory dir;
DIR *dirp;
dirp = opendir(name);
if (dirp == NULL)
return NULL;
dir = (MfmDirectory)malloc(sizeof(*dir));
if (dir == NULL)
closedir(dirp);
else
{
dir->filter = filter;
dir->data = data;
dir->dirp = dirp;
}
return dir;
}
char *MfmNextFile(MfmDirectory dir)
{
struct dirent *dp;
if (dir)
while ((dp = readdir(dir->dirp)) != NULL)
if (dir->filter == NULL ||
(*dir->filter)(dp->d_name, dir->data))
return dp->d_name;
return NULL;
}
void MfmCloseDirectory(MfmDirectory dir)
{
if (dir)
{
closedir(dir->dirp);
free(dir);
}
}
syntax highlighted by Code2HTML, v. 0.9.1