Main Page | Data Structures | File List | Data Fields | Globals | Related Pages

cache.c

Go to the documentation of this file.
00001 /*
00002  *  cache.c
00003  *  mod_musicindex
00004  *
00005  *  $Id: cache.c,v 1.36 2003/10/28 20:50:11 boudinr Exp $
00006  *
00007  *  Created by Thibaut VARENE on Fri Jul 04 2003.
00008  *  Copyright (c) 2003 Thibaut VARENE
00009  *   
00010  *  This program is free software; you can redistribute it and/or modify
00011  *  it under the terms of the GNU Lesser General Public License as published by
00012  *  the Free Software Foundation; either version 2.1, or (at your option)
00013  *  any later version.
00014  *
00015  */
00016 
00043 #include "cache.h"
00044 #include "playlist.h"
00045 
00046 #ifdef CACHE_SQL
00047 /* A coder */
00048 short cache_check_dir(request_rec * r, mu_config *conf, mu_cache_data *cachedata);
00049 short cache_check_file(request_rec * r, mu_config *conf, mu_cache_data *cachedata);
00050 mu_ent *cache_read_file(request_rec * r, mu_ent *head, mu_config *conf, mu_cache_data *cachedata);
00051 short cache_write_file(request_rec * r, mu_ent *p, mu_config *conf, mu_cache_data *cachedata);
00052 /* http://www.mysql.com/documentation/mysql/bychapter/manual_Clients.html#C */
00053 /* one db, two tables
00054  * table files: |id|timestamp|full path|
00055  * table fdata: |id|rid| -fields- |
00056  * pid being the 'relative id', aka the id of the corresponding file entry.
00057  * fdata would contain data related to actual music files only.
00058  * it would probably be more interesting to store only full paths for directories,
00059  * and to store files separately... Who knows, who cares indeed?
00060  */
00061  /*
00062  MYSQL *mysql_real_connect(MYSQL *mysql, const char *host, const char *user, const char *passwd, const char *db, unsigned int port, const char *unix_socket, unsigned long client_flag) 
00063  int mysql_query(MYSQL *mysql, const char *query)
00064  unsigned int mysql_field_count(MYSQL *mysql)
00065  unsigned long mysql_real_escape_string(MYSQL *mysql, char *to, const char *from, unsigned long length) 
00066  int mysql_select_db(MYSQL *mysql, const char *db) 
00067  void mysql_close(MYSQL *mysql) 
00068  char *mysql_error(MYSQL *mysql) 
00069  MYSQL_FIELD *mysql_fetch_field(MYSQL_RES *result) 
00070  unsigned long *mysql_fetch_lengths(MYSQL_RES *result) 
00071  MYSQL_RES *mysql_store_result(MYSQL *mysql) 
00072  void mysql_free_result(MYSQL_RES *result) 
00073  */
00074 
00075 #else   /* Default cache system: filesystem hierarchy with regular text files */
00076 #ifndef NO_CACHE
00077 
00087 static void error_handler(request_rec *r, const char *caller)
00088 {
00089         switch (errno) {
00090                 case EPERM:
00091                         /* The filesystem containing pathname does not support the creation of directories. */
00092                         ap_log_printf(r->server, "[musicindex] (%s) Can't create directory.", caller);
00093                         break;
00094 #if 0
00095                 case EISDIR:
00096                         /* pathname refers to a directory.  (This is  the  non-POSIX  value returned by Linux since 2.1.132.) */
00097                 case EINVAL:
00098                         /* mode  requested  creation of something other than a normal file, device special file or FIFO */
00099                 case EEXIST:
00100                         /* pathname already exists (not necessarily as a directory). */
00101                 case EFAULT:
00102                         /* pathname points outside your accessible address space. */
00103 #endif
00104                 case EACCES:
00105                         /* The parent directory does not allow write permission to the  process,  or  one  of  the
00106                         directories in pathname did not allow search (execute) permission. */
00107                         ap_log_printf(r->server, "[musicindex] (%s) Permission denied.", caller);
00108                         break;
00109                 case EMFILE:
00110                         /* Too many file descriptors in use by process. */
00111                 case ENFILE:
00112                         /* Too many files are currently open in the system. */
00113                         ap_log_printf(r->server, "[musicindex] (%s) Too many open files!", caller);
00114                         break;
00115                 case ENAMETOOLONG:
00116                         /* pathname was too long. */
00117                         ap_log_printf(r->server, "[musicindex] (%s) Pathname was too long.", caller);
00118                         break;
00119 #if 0
00120                 case ENOENT:
00121                         /* A directory component in pathname does not exist or is a dangling symbolic link. */
00122                 case ENOTDIR:
00123                         /* A component used as a directory in pathname is not, in fact, a directory. */
00124                 case ENOTEMPTY:
00125                         /* pathname contains entries other than . and .. . */
00126 #endif
00127                 case ENOMEM:
00128                         /* Insufficient kernel memory was available. */
00129                         ap_log_printf(r->server, "[musicindex] (%s) Out Of Memory!", caller);
00130                         break;
00131                 case EROFS:
00132                         /* pathname refers to a file on a read-only filesystem. */
00133                         ap_log_printf(r->server, "[musicindex] (%s) Read-Only filesystem!", caller);
00134                         break;
00135                 case ELOOP:
00136                         /* Too many symbolic links were encountered in resolving pathname. */
00137                         ap_log_printf(r->server, "[musicindex] (%s) Too many symbolic links.", caller);
00138                         break;
00139                 case EIO:
00140                         /* An I/O error occured. */
00141                         ap_log_printf(r->server, "[musicindex] (%s) I/O error.", caller);
00142                         break;
00143                 case ENOSPC:
00144                         /* The device containing pathname has no room for the new directory.
00145                         The new directory cannot be created because the user's disk quota is exhausted. */
00146                         ap_log_printf(r->server, "[musicindex] (%s) No space left on device!", caller);
00147                         break;
00148                 default:
00149                         ap_log_printf(r->server, "[musicindex] (%s) - error_handler! errno=%i", caller, errno);
00150                         break;
00151         }
00152         return;
00153 }
00154 
00168 static short cache_make_dir(request_rec *r, char *dirpath)
00169 {
00170         short l = 0, m = 0;
00171         char *tempdir = NULL;
00172         
00173         do {    /* We build the path subdirs by subdirs, in a "mkdir -p" fashion */
00174                 tempdir = ap_pstrndup(r->pool, dirpath, (m + (l = strcspn(dirpath + m, "/"))));
00175                 m += l;
00176                 
00177                 if (!l)
00178                         break;
00179 
00180                 /* skipping (potentially multiple) slashes */
00181                 while (dirpath[m] == '/')
00182                         m++;
00183                 
00184                 if (mkdir(tempdir, S_IRWXU)) {
00185                         if (errno == EEXIST);
00186                         else
00187                                 goto error_out;
00188                 }
00189         } while (1);
00190 
00191         return 0;
00192 
00193 error_out:
00194         error_handler(r, __FUNCTION__);
00195         return CA_FATAL;
00196 }
00197 
00209 static void cache_remove_dir(request_rec *r, DIR *cachedir, char *curdir)
00210 {
00211         DIR             *subdir = NULL;
00212         struct dirent   *cachedirent = NULL;
00213         struct stat     origdirstat;
00214         char            *origdir = NULL;
00215         
00216         fchdir(dirfd(cachedir));                        /* on se place dans le repertoire de cache. XXX Pas de test ici, tout est verifie avant (en principe). */
00217         while ((cachedirent = readdir(cachedir))) {     /* on parcours le repertoire */
00218                 if (!(strcmp(cachedirent->d_name, ".")) || !(strcmp(cachedirent->d_name, "..")))        /* We'd rather avoid trying to remove the whole filesystem... */
00219                         continue;
00220                 
00221                 if (unlink(cachedirent->d_name)) {      /* We try to remove any entry (actually we will only remove regular files) */
00222                         if (errno == EISDIR) {          /* Unfortunately errno isn't seen as volatile, so we have to separate the if() */
00223                                 /* If it's a directory, we check that the "original" still exists.
00224                                  * If not, we remove it recursively */
00225                                 origdir = ap_pstrcat(r->pool, curdir, "/", cachedirent->d_name, NULL);
00226                                 if (stat(origdir, &origdirstat)) {
00227                                         if (rmdir(cachedirent->d_name)) {       /* stat() sets errno. We have to split */
00228                                                 if (errno == ENOTEMPTY) {                       /* il est pas vide, bigre! */
00229                                                         subdir = opendir(cachedirent->d_name);  /* on ouvre le vilain repertoire pour en supprimer le contenu */
00230                                                         cache_remove_dir(r, subdir, origdir);   /* en rappelant recursivement la fonction sur son contenu. */
00231                                                         closedir(subdir);                       /* a noter que dans ce cas la il y a un test inutile, celui qui verifie si l'original existe tjrs. Mais bon. */
00232                                                         fchdir(dirfd(cachedir));                /* on retourne au repertoire precedent */
00233                                                         rmdir(cachedirent->d_name);             /* maintenant il est vide, et on peut pas avoir d'erreur vu les tests precedants */
00234                                                 }
00235                                                 else
00236                                                         error_handler(r, __FUNCTION__);                 /* Oops, on est tombe sur une merde */
00237                                         }
00238                                 }
00239                         }
00240                         else
00241                                 error_handler(r, __FUNCTION__);         /* Oops, on est tombe sur une merde, mais plus tot */
00242                 }
00243         }
00244         
00245         return;
00246 }
00247 
00258 static short cache_init(request_rec * r, mu_config *conf)
00259 {
00260         chdir("/");     /* let's pray this will never fail */
00261         if (cache_make_dir(r, conf->cache_path + 1))    /* since we've chdir'd, we send the path without the leading '/' */
00262                 goto error_out;
00263         
00264         return 0;
00265         
00266 error_out:
00267         error_handler(r, __FUNCTION__);
00268         return CA_FATAL;
00269 }
00270 
00285 short cache_check_dir(request_rec * r, mu_config *conf, mu_cache_data *cachedata)
00286 {
00287         DIR             *cachedir = NULL;
00288         struct stat     cachedirstat, dirstat;
00289         
00290         if (!cachedata->name)
00291                 return CA_MISSARG;
00292         
00293         /* Making sure the cache has been initialized, initialize it otherwise.
00294          * Bear in mind we're chdir'd from now on. */
00295         if (chdir(conf->cache_path)) {                  /* on va dans le rep de cache */
00296                 if (errno == ENOENT) {                  /* il n'existe pas, il est temps d'initialiser le cache */
00297                         if (cache_init(r, conf))
00298                                 return CA_FATAL;        /* l'init du cache a chie, c'est mauvais */
00299                         chdir(conf->cache_path);        /* maintenant qu'on l'a cree on peut enfin y aller. Considere sans echec */
00300                 }
00301                 else
00302                         goto error_out;                 /* un autre probleme, on degage */
00303         }
00304         
00305         /* Actually check for the directory in the cache, create it if needed.
00306          * "+ 1" offset to suppress leading '/'. */
00307         if (!(cachedir = opendir(cachedata->name + 1))) {               /* on essaye d'ouvrir le repertoire concerne dans le cache (on supprime le leading "/" */
00308                 if (errno == ENOENT) {                                  /* il n'existe pas mais on peut le creer (ca correspond a ENOENT, a verifier) */
00309                         if (cache_make_dir(r, cachedata->name + 1))     /* on envoie le chemin prive du leading '/' */
00310                                 goto error_out;
00311                 }
00312                 else
00313                         goto error_out;                                 /* un autre probleme, on degage */
00314         }
00315         else {  /* Checking for cache sanity. Has it expired for that folder ? If so, delete its content. */
00316                 fstat(dirfd(cachedir), &cachedirstat);                  /* recuperons les stats du repertoire. XXX On considere cet appel sans echec vu les tests qu'on a fait avant. */        
00317                 stat(cachedata->name, &dirstat);                        /* recuperons les stats du rep d'origine. XXX pas de test ici, a priori ya pas de raison qu'on puisse pas les recuperer */
00318                 if (cachedirstat.st_mtime < dirstat.st_mtime)           /* si la date de modif du rep de cache est plus vieille que celle du rep original, alors qqc a ete ajoute ou retire ou ecrit */
00319                         cache_remove_dir(r, cachedir, cachedata->name); /* alors on le vide proprement */
00320                 closedir(cachedir);                                     /* On en a fini avec le repertoire, on le referme */
00321         }
00322 
00323         return 0;
00324 
00325 error_out:
00326         error_handler(r, __FUNCTION__);
00327         return CA_FATAL;
00328 }
00329 
00346 short cache_check_file(request_rec * r, mu_config *conf, mu_cache_data *cachedata)
00347 {
00348         if (!cachedata->name)
00349                 return CA_MISSARG;
00350         
00351         /* Making sure the cache has been initialized, initialize it otherwise.
00352          * Bear in mind we're chdir'd from now on. */
00353         if (chdir(conf->cache_path)) {                  /* on va dans le rep de cache */
00354                 if (errno == ENOENT) {                  /* il n'existe pas, il est temps d'initialiser le cache */
00355                         if (cache_init(r, conf))
00356                                 return CA_FATAL;        /* l'init du cache a chie, c'est mauvais */
00357                         chdir(conf->cache_path);        /* maintenant qu'on l'a cree on peut enfin y aller. Considere sans echec */
00358                 }
00359                 else
00360                         goto error_out;                 /* un autre probleme, on degage */
00361         }
00362 
00363         /* Actually check for the file in the cache, open it if possible.
00364          * "+ 1" offset to suppress leading '/'. */
00365         if (!(cachedata->stream = fopen(cachedata->name + 1, "r"))) {   /* on essaye d'ouvrir le fichier concerne en ro */
00366                 if (errno == ENOENT)                                    /* il n'existe pas mais on peut a priori le creer (ca correspond a ENOENT, a verifier) */
00367                         return CA_CREATE;
00368                 else
00369                         goto error_out;                                 /* un autre probleme, on degage */
00370         }
00371         
00372         /* The way we handle cache sanity in cache_check_dir() ensures that data is always up-to-date in cache files. */
00373         return 0;                       /* le fichier existe et est ouvert, on peu sortir. */
00374         
00375 error_out:
00376         error_handler(r, __FUNCTION__);
00377         return CA_FATAL;
00378 }
00379 
00396 mu_ent *cache_read_file(request_rec * r, mu_ent *head, mu_config *conf, mu_cache_data *cachedata)
00397 {
00398         mu_ent          *p = head;
00399         short           result = 0;
00400 
00401         /* We acquire a shared advisory lock on the file to be (almost) certain of its integrity.
00402          * This will prevent reading from incomplete cache files */
00403         if (flock(fileno((FILE *)cachedata->stream), LOCK_SH|LOCK_NB))
00404                 return p;
00405 
00406         p = new_ent(r->pool, head);
00407         p->title = ap_pcalloc(r->pool, MAX_STRING);     /* ugh..ly! */
00408         p->album = ap_pcalloc(r->pool, MAX_STRING);
00409         p->artist = ap_pcalloc(r->pool, MAX_STRING);
00410         p->genre = ap_pcalloc(r->pool, 64);     /* no very_long_and_explicit_genre I hope */
00411         
00412         result = fscanf(cachedata->stream, "album: %[^\n]\nartist: %[^\n]\n"
00413                 "title: %[^\n]\ndate: %hu\ntrack: %hu\nposn: %hu\n"
00414                 "length: %lu\nbitrate: %lu\nsize: %lu\nfiletype: %c\n"
00415                 "genre: %[^\n]\n",
00416                 p->album, p->artist, p->title, &p->date, &p->track, &p->posn, &p->length,
00417                 &p->bitrate, &p->size, &p->filetype, p->genre);
00418 
00419         /* Explicitely releasing the lock, and closing the file */
00420         flock(fileno((FILE *)cachedata->stream), LOCK_UN);
00421         fclose(cachedata->stream);
00422         
00423         /* XXX ameliorer le test ici. if (result != NB_FIELDS) ? */
00424         if (!result)    /* fscanf() returns the number of input items assigned */
00425                 return head;
00426         
00427         if (!strcmp(p->album, "(null)"))
00428                 p->album[0] = '\0';
00429         if (!strcmp(p->artist, "(null)"))
00430                 p->artist[0] = '\0';
00431         if (!strcmp(p->genre, "(null)"))
00432                 p->genre[0] = '\0';
00433         
00434         return p;
00435 }
00436 
00450 short cache_write_file(request_rec * r, mu_ent *p, mu_config *conf, mu_cache_data *cachedata)
00451 {
00452         chdir(conf->cache_path);        /* Par securite on se re-chdir(). XXX Considere sans echec */
00453         
00454         if (!(cachedata->stream = fopen(cachedata->name + 1, "w+")))    /* on cree le fichier en le tronquant des fois qu'il existe deja (fichier pourri). */
00455                 goto error_out;                 /* decidement ca chie, on degage */
00456         
00457         /* We acquire an exclusive advisory lock on the file to avoid corruption by another process.
00458          * This will also prevent reading from incomplete cache */
00459         if (flock(fileno((FILE *)cachedata->stream), LOCK_EX|LOCK_NB)) {
00460                 if (errno == EWOULDBLOCK)
00461                         return CA_LOCKED;
00462                 else
00463                         goto error_out;
00464         }
00465         
00466         fprintf(cachedata->stream, "album: %s\nartist: %s\ntitle: %s\ndate: %hu\n"
00467                 "track: %hu\nposn: %hu\nlength: %lu\nbitrate: %lu\n"
00468                 "size: %lu\nfiletype: %s\ngenre: %s\n",
00469                 p->album, p->artist, p->title, p->date, p->track, p->posn, p->length,
00470                 p->bitrate, p->size, &p->filetype, p->genre);
00471 
00472         /* Explicitely releasing the lock, and closing the file */
00473         flock(fileno((FILE *)cachedata->stream), LOCK_UN);
00474         fclose(cachedata->stream);
00475         
00476         return 0;
00477 
00478 error_out:
00479         error_handler(r, __FUNCTION__);
00480         return CA_FATAL;
00481 }
00482 #endif  /* NO_CACHE */
00483 #endif  /* Cache type */

Generated on Thu Oct 30 13:50:28 2003 for mod_musicindex by doxygen 1.3.4