332 lines
		
	
	
		
			11 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
			
		
		
	
	
			332 lines
		
	
	
		
			11 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
| /**
 | |
|  * Necronda Web Server
 | |
|  * File cache implementation
 | |
|  * src/cache.c
 | |
|  * Lorenz Stechauner, 2020-12-19
 | |
|  */
 | |
| 
 | |
| #include <zlib.h>
 | |
| #include "cache.h"
 | |
| #include "uri.h"
 | |
| 
 | |
| int magic_init() {
 | |
|     magic = magic_open(MAGIC_MIME);
 | |
|     if (magic == NULL) {
 | |
|         fprintf(stderr, ERR_STR "Unable to open magic cookie: %s" CLR_STR "\n", strerror(errno));
 | |
|         return -1;
 | |
|     }
 | |
|     if (magic_load(magic, MAGIC_FILE) != 0) {
 | |
|         fprintf(stderr, ERR_STR "Unable to load magic cookie: %s" CLR_STR "\n", magic_error(magic));
 | |
|         return -2;
 | |
|     }
 | |
|     return 0;
 | |
| }
 | |
| 
 | |
| void cache_process_term() {
 | |
|     cache_continue = 0;
 | |
| }
 | |
| 
 | |
| int cache_process() {
 | |
|     signal(SIGINT, cache_process_term);
 | |
|     signal(SIGTERM, cache_process_term);
 | |
| 
 | |
|     int shm_id = shmget(SHM_KEY, FILE_CACHE_SIZE * sizeof(cache_entry), 0);
 | |
|     if (shm_id < 0) {
 | |
|         fprintf(stderr, ERR_STR "Unable to create shared memory: %s" CLR_STR "\n", strerror(errno));
 | |
|         return -1;
 | |
|     }
 | |
| 
 | |
|     struct shmid_ds info;
 | |
|     if (shmctl(shm_id, IPC_STAT, &info) < 0) goto shmctl_err;
 | |
|     info.shm_perm.mode = 0600;
 | |
|     if (shmctl(shm_id, IPC_SET, &info) < 0) {
 | |
|         shmctl_err:
 | |
|         fprintf(stderr, ERR_STR "Unable to update permissions for shared memory: %s" CLR_STR "\n", strerror(errno));
 | |
|         return -3;
 | |
|     }
 | |
| 
 | |
|     shmdt(cache);
 | |
|     void *shm_rw = shmat(shm_id, NULL, 0);
 | |
|     if (shm_rw == (void *) -1) {
 | |
|         fprintf(stderr, ERR_STR "Unable to attach shared memory (rw): %s" CLR_STR "\n", strerror(errno));
 | |
|         return -2;
 | |
|     }
 | |
|     cache = shm_rw;
 | |
| 
 | |
|     mkdir("/var/necronda-server", 0755);
 | |
| 
 | |
|     FILE *cache_file = fopen("/var/necronda-server/cache", "rb");
 | |
|     if (cache_file != NULL) {
 | |
|         fread(cache, sizeof(cache_entry), FILE_CACHE_SIZE, cache_file);
 | |
|         fclose(cache_file);
 | |
|     }
 | |
| 
 | |
|     for (int i = 0; i < FILE_CACHE_SIZE; i++) {
 | |
|         cache[i].is_updating = 0;
 | |
|     }
 | |
| 
 | |
|     FILE *file;
 | |
|     char buf[16384];
 | |
|     char comp_buf[16384];
 | |
|     char filename_comp[256];
 | |
|     unsigned long read;
 | |
|     int compress;
 | |
|     SHA_CTX ctx;
 | |
|     unsigned char hash[SHA_DIGEST_LENGTH];
 | |
|     while (cache_continue) {
 | |
|         for (int i = 0; i < FILE_CACHE_SIZE; i++) {
 | |
|             if (cache[i].filename[0] != 0 && cache[i].meta.etag[0] == 0 && !cache[i].is_updating) {
 | |
|                 cache[i].is_updating = 1;
 | |
|                 SHA1_Init(&ctx);
 | |
|                 file = fopen(cache[i].filename, "rb");
 | |
|                 compress = strncmp(cache[i].meta.type, "text/", 5) == 0 ||
 | |
|                         (strncmp(cache[i].meta.type, "application/", 12) == 0 &&
 | |
|                         strstr(cache[i].meta.type, "+xml") != NULL);
 | |
| 
 | |
|                 int level = NECRONDA_ZLIB_LEVEL;
 | |
|                 z_stream strm;
 | |
|                 FILE *comp_file = NULL;
 | |
|                 if (compress) {
 | |
|                     sprintf(buf, "%.*s/.necronda-server", cache[i].webroot_len, cache[i].filename);
 | |
|                     mkdir(buf, 0755);
 | |
|                     sprintf(buf, "%.*s/.necronda-server/cache", cache[i].webroot_len, cache[i].filename);
 | |
|                     mkdir(buf, 0700);
 | |
|                     char *rel_path = cache[i].filename + cache[i].webroot_len + 1;
 | |
|                     for (int j = 0; j < strlen(rel_path); j++) {
 | |
|                         char ch = rel_path[j];
 | |
|                         if (ch == '/') {
 | |
|                             ch = '_';
 | |
|                         }
 | |
|                         buf[j] = ch;
 | |
|                     }
 | |
|                     buf[strlen(rel_path)] = 0;
 | |
|                     sprintf(filename_comp, "%.*s/.necronda-server/cache/%s.z", cache[i].webroot_len, cache[i].filename, buf);
 | |
|                     comp_file = fopen(filename_comp, "wb");
 | |
|                     if (comp_file == NULL) {
 | |
|                         compress = 0;
 | |
|                         fprintf(stderr, ERR_STR "Unable to open cache file: %s" CLR_STR "\n", strerror(errno));
 | |
|                     } else {
 | |
|                         strm.zalloc = Z_NULL;
 | |
|                         strm.zfree = Z_NULL;
 | |
|                         strm.opaque = Z_NULL;
 | |
|                         if (deflateInit(&strm, level) != Z_OK) {
 | |
|                             fprintf(stderr, ERR_STR "Unable to init deflate: %s" CLR_STR "\n", strerror(errno));
 | |
|                             compress = 0;
 | |
|                             fclose(comp_file);
 | |
|                         }
 | |
|                     }
 | |
|                 }
 | |
| 
 | |
|                 while ((read = fread(buf, 1, sizeof(buf), file)) > 0) {
 | |
|                     SHA1_Update(&ctx, buf, read);
 | |
|                     if (compress) {
 | |
|                         strm.avail_in = read;
 | |
|                         strm.next_in = (unsigned char *) buf;
 | |
|                         do {
 | |
|                             strm.avail_out = sizeof(comp_buf);
 | |
|                             strm.next_out = (unsigned char *) comp_buf;
 | |
|                             deflate(&strm, feof(file) ? Z_FINISH : Z_NO_FLUSH);
 | |
|                             fwrite(comp_buf, 1, sizeof(comp_buf) - strm.avail_out, comp_file);
 | |
|                             strm.avail_in = 0;
 | |
|                         } while (strm.avail_out == 0);
 | |
|                     }
 | |
|                 }
 | |
| 
 | |
|                 if (compress) {
 | |
|                     deflateEnd(&strm);
 | |
|                     fclose(comp_file);
 | |
|                     strcpy(cache[i].meta.filename_comp, filename_comp);
 | |
|                 } else {
 | |
|                     memset(cache[i].meta.filename_comp, 0, sizeof(cache[i].meta.filename_comp));
 | |
|                 }
 | |
|                 SHA1_Final(hash, &ctx);
 | |
|                 memset(cache[i].meta.etag, 0, sizeof(cache[i].meta.etag));
 | |
|                 for (int j = 0; j < SHA_DIGEST_LENGTH; j++) {
 | |
|                     sprintf(cache[i].meta.etag + j * 2, "%02x", hash[j]);
 | |
|                 }
 | |
|                 fclose(file);
 | |
|                 cache[i].is_updating = 0;
 | |
|             }
 | |
|         }
 | |
| 
 | |
|         cache_file = fopen("/var/necronda-server/cache", "wb");
 | |
|         fwrite(cache, sizeof(cache_entry), FILE_CACHE_SIZE , cache_file);
 | |
|         fclose(cache_file);
 | |
|         sleep(1);
 | |
|     }
 | |
|     return 0;
 | |
| }
 | |
| 
 | |
| int cache_init() {
 | |
|     if (magic_init() != 0) {
 | |
|         return -1;
 | |
|     }
 | |
| 
 | |
|     int shm_id = shmget(SHM_KEY, FILE_CACHE_SIZE * sizeof(cache_entry), IPC_CREAT | IPC_EXCL);
 | |
|     if (shm_id < 0) {
 | |
|         fprintf(stderr, ERR_STR "Unable to create shared memory: %s" CLR_STR "\n", strerror(errno));
 | |
|         return -2;
 | |
|     }
 | |
| 
 | |
|     void *shm = shmat(shm_id, NULL, SHM_RDONLY);
 | |
|     if (shm == (void *) -1) {
 | |
|         fprintf(stderr, ERR_STR "Unable to attach shared memory (ro): %s" CLR_STR "\n", strerror(errno));
 | |
|         return -3;
 | |
|     }
 | |
|     cache = shm;
 | |
| 
 | |
|     void *shm_rw = shmat(shm_id, NULL, 0);
 | |
|     if (shm_rw == (void *) -1) {
 | |
|         fprintf(stderr, ERR_STR "Unable to attach shared memory (rw): %s" CLR_STR "\n", strerror(errno));
 | |
|         return -4;
 | |
|     }
 | |
|     cache = shm_rw;
 | |
|     memset(cache, 0, FILE_CACHE_SIZE * sizeof(cache_entry));
 | |
|     shmdt(shm_rw);
 | |
|     cache = shm;
 | |
| 
 | |
|     pid_t pid = fork();
 | |
|     if (pid == 0) {
 | |
|         // child
 | |
|         if (cache_process() == 0) {
 | |
|             return 1;
 | |
|         } else {
 | |
|             return -6;
 | |
|         }
 | |
|     } else if (pid > 0) {
 | |
|         // parent
 | |
|         fprintf(stderr, "Started child process with PID %i as cache-updater\n", pid);
 | |
|         children[0] = pid;
 | |
|     } else {
 | |
|         fprintf(stderr, ERR_STR "Unable to create child process: %s" CLR_STR "\n", strerror(errno));
 | |
|         return -5;
 | |
|     }
 | |
| 
 | |
|     return 0;
 | |
| }
 | |
| 
 | |
| int cache_unload() {
 | |
|     int shm_id = shmget(SHM_KEY, 0, 0);
 | |
|     if (shm_id < 0) {
 | |
|         fprintf(stderr, ERR_STR "Unable to create shared memory: %s" CLR_STR "\n", strerror(errno));
 | |
|     } else if (shmctl(shm_id, IPC_RMID, NULL) < 0) {
 | |
|         fprintf(stderr, ERR_STR "Unable to configure shared memory: %s" CLR_STR "\n", strerror(errno));
 | |
|     }
 | |
|     shmdt(cache);
 | |
|     return 0;
 | |
| }
 | |
| 
 | |
| int cache_update_entry(int entry_num, const char *filename, const char *webroot) {
 | |
|     void *cache_ro = cache;
 | |
|     int shm_id = shmget(SHM_KEY, 0, 0);
 | |
|     void *shm_rw = shmat(shm_id, NULL, 0);
 | |
|     if (shm_rw == (void *) -1) {
 | |
|         print(ERR_STR "Unable to attach shared memory (rw): %s" CLR_STR, strerror(errno));
 | |
|         return -1;
 | |
|     }
 | |
|     cache = shm_rw;
 | |
| 
 | |
|     struct stat statbuf;
 | |
|     stat(filename, &statbuf);
 | |
|     memcpy(&cache[entry_num].meta.stat, &statbuf, sizeof(statbuf));
 | |
| 
 | |
|     cache[entry_num].webroot_len = (unsigned char) strlen(webroot);
 | |
|     strcpy(cache[entry_num].filename, filename);
 | |
| 
 | |
|     magic_setflags(magic, MAGIC_MIME_TYPE);
 | |
|     const char *type = magic_file(magic, filename);
 | |
|     char type_new[24];
 | |
|     sprintf(type_new, "%s", type);
 | |
|     if (strcmp(type, "text/plain") == 0) {
 | |
|         if (strncmp(filename + strlen(filename) - 4, ".css", 4) == 0) {
 | |
|             sprintf(type_new, "text/css");
 | |
|         } else if (strcmp(filename + strlen(filename) - 3, ".js") == 0) {
 | |
|             sprintf(type_new, "text/javascript");
 | |
|         }
 | |
|     }
 | |
|     strcpy(cache[entry_num].meta.type, type_new);
 | |
| 
 | |
|     magic_setflags(magic, MAGIC_MIME_ENCODING);
 | |
|     strcpy(cache[entry_num].meta.charset, magic_file(magic, filename));
 | |
| 
 | |
|     memset(cache[entry_num].meta.etag, 0, sizeof(cache[entry_num].meta.etag));
 | |
|     memset(cache[entry_num].meta.filename_comp, 0, sizeof(cache[entry_num].meta.filename_comp));
 | |
|     cache[entry_num].is_updating = 0;
 | |
| 
 | |
|     shmdt(shm_rw);
 | |
|     cache = cache_ro;
 | |
|     return 0;
 | |
| }
 | |
| 
 | |
| int cache_filename_comp_invalid(const char *filename) {
 | |
|     void *cache_ro = cache;
 | |
|     int shm_id = shmget(SHM_KEY, 0, 0);
 | |
|     void *shm_rw = shmat(shm_id, NULL, 0);
 | |
|     if (shm_rw == (void *) -1) {
 | |
|         print(ERR_STR "Unable to attach shared memory (rw): %s" CLR_STR, strerror(errno));
 | |
|         return -1;
 | |
|     }
 | |
|     cache = shm_rw;
 | |
| 
 | |
|     int i;
 | |
|     for (i = 0; i < FILE_CACHE_SIZE; i++) {
 | |
|         if (cache[i].filename[0] != 0 && strlen(cache[i].filename) == strlen(filename) &&
 | |
|             strcmp(cache[i].filename, filename) == 0) {
 | |
|             if (cache[i].is_updating) {
 | |
|                 return 0;
 | |
|             } else {
 | |
|                 break;
 | |
|             }
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     memset(cache[i].meta.etag, 0, sizeof(cache[i].meta.etag));
 | |
|     memset(cache[i].meta.filename_comp, 0, sizeof(cache[i].meta.filename_comp));
 | |
|     cache[i].is_updating = 0;
 | |
| 
 | |
|     shmdt(shm_rw);
 | |
|     cache = cache_ro;
 | |
|     return 0;
 | |
| }
 | |
| 
 | |
| int uri_cache_init(http_uri *uri) {
 | |
|     if (uri->filename == NULL) {
 | |
|         return 0;
 | |
|     }
 | |
| 
 | |
|     int i;
 | |
|     for (i = 0; i < FILE_CACHE_SIZE; i++) {
 | |
|         if (cache[i].filename[0] != 0 && strlen(cache[i].filename) == strlen(uri->filename) &&
 | |
|             strcmp(cache[i].filename, uri->filename) == 0) {
 | |
|             uri->meta = &cache[i].meta;
 | |
|             if (cache[i].is_updating) {
 | |
|                 return 0;
 | |
|             } else {
 | |
|                 break;
 | |
|             }
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     if (uri->meta == NULL) {
 | |
|         for (i = 0; i < FILE_CACHE_SIZE; i++) {
 | |
|             if (cache[i].filename[0] == 0) {
 | |
|                 if (cache_update_entry(i, uri->filename, uri->webroot) != 0) {
 | |
|                     return -1;
 | |
|                 }
 | |
|                 uri->meta = &cache[i].meta;
 | |
|                 break;
 | |
|             }
 | |
|         }
 | |
|     } else {
 | |
|         struct stat statbuf;
 | |
|         stat(uri->filename, &statbuf);
 | |
|         if (memcmp(&uri->meta->stat.st_mtime, &statbuf.st_mtime, sizeof(statbuf.st_mtime)) != 0) {
 | |
|             if (cache_update_entry(i, uri->filename, uri->webroot) != 0) {
 | |
|                 return -1;
 | |
|             }
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     return 0;
 | |
| }
 |