/*************************************************** * * Copyright 2007 by Sean Conner. All Rights Reserved. * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 2 * of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * * Comments, questions and criticisms can be sent to: sean@conman.org * ****************************************************/ #include #include #include #include #include #include #include #include #include #include #include #include "blog.h" #include "wbtum.h" /***********************************************************************/ static inline size_t max(size_t a,size_t b) { if (a > b) return a; else return b; } /***********************************************************************/ static bool set_date(char const *file,struct btm *when,struct btm *now) { assert(file != NULL); assert(when != NULL); assert(now != NULL); FILE *fp = fopen(file,"r"); if (fp == NULL) { fp = fopen(file,"w"); if (fp != NULL) { *when = *now; fprintf( fp, "%4d/%02d/%02d.%d\n", now->year, now->month, now->day, now->part ); fclose(fp); } else { syslog(LOG_ERR,"%s: %s",file,strerror(errno)); return false; } } else { char buffer[128]; char *p; fgets(buffer,sizeof(buffer),fp); when->year = strtoul(buffer,&p,10); p++; when->month = strtoul(p,&p,10); p++; when->day = strtoul(p,&p,10); p++; when->part = strtoul(p,&p,10); fclose(fp); } return true; } /***********************************************************************/ static int blog_lock(char const *lockfile) { if (lockfile) { int lock = open(lockfile,O_CREAT | O_RDWR,0x666); if (lock == -1) { syslog(LOG_ERR,"%s %s",lockfile,strerror(errno)); return lock; } int rc = fcntl( lock, F_SETLKW, &(struct flock){ .l_type = F_WRLCK, .l_start = 0, .l_whence = SEEK_SET, .l_len = 0, } ); if (rc < 0) { syslog(LOG_ERR,"fcntl('%s',F_SETLKW) = %s",lockfile,strerror(errno)); close(lock); return -1; } return lock; } return -1; } /***********************************************************************/ static void blog_unlock(char const *lockfile,int lock) { assert(lockfile != NULL); if (lock >= 0) { int rc = fcntl( lock, F_SETLK, &(struct flock){ .l_type = F_UNLCK, .l_start = 0, .l_whence = SEEK_SET, .l_len = 0, } ); if (rc == 0) { close(lock); remove(lockfile); } else syslog(LOG_ERR,"fcntl('%s',F_UNLCK) = %s",lockfile,strerror(errno)); } } /***********************************************************************/ static void date_to_dir(char *tname,struct btm const *date) { assert(tname != NULL); assert(date != NULL); snprintf(tname,FILENAME_MAX,"%04d/%02d/%02d",date->year,date->month,date->day); } /***********************************************************************/ static void date_to_filename(char *tname,struct btm const *date,char const *file) { assert(tname != NULL); assert(date != NULL); assert(file != NULL); snprintf(tname,FILENAME_MAX,"%04d/%02d/%02d/%s",date->year,date->month,date->day,file); } /**********************************************************************/ static void date_to_part(char *tname,struct btm const *date,int p) { assert(tname != NULL); assert(date != NULL); assert(p > 0); snprintf(tname,FILENAME_MAX,"%04d/%02d/%02d/%d",date->year,date->month,date->day,p); } /*********************************************************************/ static FILE *open_file_r(char const *name,struct btm const *date) { FILE *in; char buffer[FILENAME_MAX]; assert(name != NULL); assert(date != NULL); date_to_filename(buffer,date,name); in = fopen(buffer,"r"); if (in == NULL) in = fopen("/dev/null","r"); /*---------------------------------------------------------------------- ; because the code was written using a different IO model (that is, if ; there's no data in the file to begin with, we automatically end up in ; EOF mode, let's trigger EOF. Sigh. ;-----------------------------------------------------------------------*/ ungetc(fgetc(in),in); return in; } /**********************************************************************/ static FILE *open_file_w(char const *name,struct btm const *date) { FILE *out; char buffer[FILENAME_MAX]; assert(name != NULL); assert(date != NULL); date_to_filename(buffer,date,name); out = fopen(buffer,"w"); return out; } /*********************************************************************/ static bool date_check(struct btm const *date) { char tname[FILENAME_MAX]; struct stat status; assert(date != NULL); date_to_dir(tname,date); return stat(tname,&status) == 0; } /************************************************************************/ static int date_checkcreate(struct btm const *date) { int rc; char tname[FILENAME_MAX]; struct stat status; assert(date != NULL); snprintf(tname,sizeof(tname),"%04d",date->year); rc = stat(tname,&status); if (rc != 0) { if (errno != ENOENT) return errno; rc = mkdir(tname,0777); if (rc != 0) return errno; } snprintf(tname,sizeof(tname),"%04d/%02d",date->year,date->month); rc = stat(tname,&status); if (rc != 0) { if (errno != ENOENT) return errno; rc = mkdir(tname,0777); if (rc != 0) return errno; } snprintf(tname,sizeof(tname),"%04d/%02d/%02d",date->year,date->month,date->day); rc = stat(tname,&status); if (rc != 0) { if (errno != ENOENT) return errno; rc = mkdir(tname,0777); if (rc != 0) return errno; } return 0; } /********************************************************************/ static char *blog_meta_entry(char const *name,struct btm const *date) { assert(name != NULL); assert(date != NULL); char *text = NULL; size_t size = 0; unsigned int num = date->part; FILE *fp = open_file_r(name,date); while(!feof(fp) && (num--)) { ssize_t bytes = getline(&text,&size,fp); if (bytes == -1) { free(text); text = strdup(""); break; } } fclose(fp); if (text != NULL) { char *nl = strchr(text,'\n'); if (nl) *nl = '\0'; } else text = strdup(""); return text; } /********************************************************************/ static size_t blog_meta_read( char **plines[], char const *name, struct btm const *date ) { char **lines; size_t i; FILE *fp; assert(plines != NULL); assert(name != NULL); assert(date != NULL); lines = calloc(ENTRY_MAX,sizeof(char *)); if (lines == NULL) { *plines = NULL; return 0; } fp = open_file_r(name,date); if (feof(fp)) { fclose(fp); *plines = lines; return 0; } for(i = 0 ; i < ENTRY_MAX ; i++) { char *nl; ssize_t bytes; size_t size; lines[i] = NULL; size = 0; bytes = getline(&lines[i],&size,fp); if (bytes == -1) { free(lines[i]); break; } nl = strchr(lines[i],'\n'); if (nl) *nl = '\0'; } fclose(fp); *plines = lines; return i; } /************************************************************************/ static void blog_meta_adjust(char **plines[],size_t num,size_t maxnum) { assert(plines != NULL); assert(*plines != NULL); assert(num <= maxnum); assert(num <= ENTRY_MAX); assert(maxnum <= ENTRY_MAX); char **lines = *plines; while(num < maxnum) { assert(lines[num] == NULL); lines[num++] = strdup(""); } } /************************************************************************/ static int blog_meta_write( char const *name, struct btm const *date, char *list[], size_t num ) { assert(name != NULL); assert(date != NULL); assert(list != NULL); FILE *fp = open_file_w(name,date); if (fp == NULL) return errno; for (size_t i = 0 ; i < num ; i++) fprintf(fp,"%s\n",list[i]); fclose(fp); return 0; } /************************************************************************/ static char const *confL_checklstring(lua_State *L,int idx,size_t *ps,char const *table,char const *field) { assert(L != NULL); assert(idx != 0); assert(table != NULL); assert(field != NULL); char const *s = lua_tolstring(L,idx,ps); if (s == NULL) luaL_error(L,"table '%s' missing mandatory field '%s'",table,field); return s; } /***************************************************************************/ static char const *confL_checkstring(lua_State *L,int idx,char const *table,char const *field) { assert(L != NULL); assert(idx != 0); assert(table != NULL); assert(field != NULL); return confL_checklstring(L,idx,NULL,table,field); } /***************************************************************************/ static void confL_tofields(lua_State *L,int idx,struct fields *fields) { assert(L != NULL); assert(idx != 0); assert(fields != NULL); idx = lua_absindex(L,idx); if (lua_isnil(L,idx)) { fields->uid = 0; fields->name = 0; fields->email = 0; } else { lua_getfield(L,idx,"uid"); fields->uid = luaL_optinteger(L,-1,1) - 1; lua_getfield(L,idx,"name"); fields->name = luaL_optinteger(L,-1,3) - 1; lua_getfield(L,idx,"email"); fields->email = luaL_optinteger(L,-1,4) - 1; lua_pop(L,3); } } /***************************************************************************/ static void confL_toauthor(lua_State *L,int idx,struct author *auth) { assert(L != NULL); assert(idx != 0); assert(auth != NULL); idx = lua_absindex(L,idx); if (lua_isnil(L,idx)) { auth->name = "Joe Blog"; auth->email = "joe@example.com"; auth->file = NULL; auth->fields.uid = 0; auth->fields.name = 0; } else { lua_getfield(L,idx,"name"); auth->name = luaL_optstring(L,-1,"Joe Blog"); lua_getfield(L,idx,"email"); auth->email = luaL_optstring(L,-1,"joe@example.com"); lua_getfield(L,idx,"file"); auth->file = luaL_optstring(L,-1,NULL); lua_getfield(L,idx,"fields"); confL_tofields(L,-1,&auth->fields); lua_pop(L,4); } } /***************************************************************************/ static void confL_toitems(lua_State *L,int idx,template__t *temp) { assert(L != NULL); assert(idx != 0); assert(temp != NULL); if (lua_isnumber(L,idx)) { temp->items = lua_tointeger(L,-1); temp->pagegen = "items"; } else if (lua_isstring(L,idx)) { char const *x = lua_tostring(L,-1); char *p; temp->items = strtoul(x,&p,10); switch(*p) { case 'd': break; case 'w': temp->items *= 7; break; case 'm': temp->items *= 30; break; default: break; } temp->pagegen = "days"; } else if (lua_isnil(L,-1)) { temp->items = 15; temp->pagegen = "items"; } else luaL_error(L,"wrong type for items"); } /***************************************************************************/ static size_t confL_totemplates(lua_State *L,int idx,template__t *ptemps[]) { size_t max; template__t *temps; assert(L != NULL); assert(idx != 0); assert(ptemps != NULL); idx = lua_absindex(L,idx); if (lua_isnil(L,idx)) return luaL_error(L,"missing template secton"); max = lua_rawlen(L,idx); if (max == 0) return luaL_error(L,"at least one template file is required"); temps = lua_newuserdata(L,max * sizeof(template__t)); lua_pushvalue(L,-1); luaL_ref(L,LUA_REGISTRYINDEX); /* prevent GC reclaiming this memory */ for (size_t i = 0 ; i < max ; i++) { int tidx; lua_pushinteger(L,i + 1); lua_gettable(L,idx); tidx = lua_gettop(L); lua_getfield(L,tidx,"template"); temps[i].template = confL_checkstring(L,-1,"template","template"); lua_getfield(L,tidx,"output"); temps[i].file = confL_checkstring(L,-1,"template","output"); lua_getfield(L,tidx,"items"); confL_toitems(L,-1,&temps[i]); lua_getfield(L,tidx,"reverse"); temps[i].reverse = lua_toboolean(L,-1); lua_getfield(L,tidx,"fullurl"); temps[i].fullurl = lua_toboolean(L,-1); lua_getfield(L,tidx,"posthook"); temps[i].posthook = luaL_optstring(L,-1,NULL); lua_pop(L,7); } *ptemps = temps; return max; } /***************************************************************************/ static size_t confL_toaffiliates(lua_State *L,int idx,aflink__t *paffs[]) { size_t max; aflink__t *affs; assert(L != NULL); assert(idx != 0); assert(paffs != NULL); idx = lua_absindex(L,idx); if (lua_isnil(L,idx)) { *paffs = NULL; return 0; } max = lua_rawlen(L,idx); if (max == 0) { *paffs = NULL; return 0; } affs = lua_newuserdata(L,max * sizeof(aflink__t)); lua_pushvalue(L,-1); luaL_ref(L,LUA_REGISTRYINDEX); /* prevent GC reclaiming this memory */ for (size_t i = 0 ; i < max ; i++) { int tidx; lua_pushinteger(L,i + 1); lua_gettable(L,idx); tidx = lua_gettop(L); lua_getfield(L,tidx,"proto"); affs[i].proto = confL_checklstring(L,-1,&affs[i].psize,"affiliate","proto"); lua_getfield(L,tidx,"link"); affs[i].format = confL_checkstring(L,-1,"affiliate","link"); lua_pop(L,3); } *paffs = affs; return max; } /***************************************************************************/ static int confL_config(lua_State *L) { size_t urllen; assert(L != NULL); struct config *config = lua_touserdata(L,1); lua_getglobal(L,"name"); config->name = luaL_optstring(L,-1,"A Blog Grows in Cyberspace"); lua_getglobal(L,"description"); config->description = luaL_optstring(L,-1,"A place where I talk about stuff in cyberspace"); lua_getglobal(L,"class"); config->class = luaL_optstring(L,-1,"blog, rants, random stuff, programming"); lua_getglobal(L,"basedir"); config->basedir = luaL_optstring(L,-1,"."); lua_getglobal(L,"webdir"); config->webdir = luaL_optstring(L,-1,"htdocs"); lua_getglobal(L,"lockfile"); config->lockfile = luaL_optstring(L,-1,".mod_blog.lock"); lua_getglobal(L,"url"); config->url = luaL_optlstring(L,-1,"http://www.example.com/blog/",&urllen); lua_getglobal(L,"adtag"); config->adtag = luaL_optstring(L,-1,"programming"); lua_getglobal(L,"conversion"); config->conversion = luaL_optstring(L,-1,"html"); lua_getglobal(L,"prehook"); config->prehook = luaL_optstring(L,-1,NULL); lua_getglobal(L,"posthook"); config->posthook = luaL_optstring(L,-1,NULL); lua_getglobal(L,"author"); confL_toauthor(L,-1,&config->author); lua_getglobal(L,"templates"); config->templatenum = confL_totemplates(L,-1,&config->templates); lua_getglobal(L,"affiliate"); config->affiliatenum = confL_toaffiliates(L,-1,&config->affiliates); /*-------------------------------------- ; ensure that the URL ends with a '/'. ;---------------------------------------*/ if (config->url[urllen-1] != '/') { lua_pushlstring(L,config->url,urllen); lua_pushliteral(L,"/"); lua_concat(L,2); config->url = lua_tostring(L,-1); } /*--------------------------------------------------- ; XXX - need to think about a messed up URL here ... ;----------------------------------------------------*/ char const *s = strchr(config->url,'/'); /* first slash in authority section */ s = strchr(++s,'/'); /* second slash in authority section */ s = strchr(++s,'/'); /* start of path component */ config->baseurl = s; return 0; } /***************************************************************************/ static bool config_read(char const *conf,Blog *blog) { int rc; assert(blog != NULL); if (conf == NULL) { conf = getenv("REDIRECT_BLOG_CONFIG"); if (conf == NULL) { conf = getenv("BLOG_CONFIG"); if (conf == NULL) { syslog(LOG_ERR,"env BLOG_CONFIG not defined"); return NULL; } } } blog->config.L = luaL_newstate(); if (blog->config.L == NULL) { syslog(LOG_ERR,"cannot create Lua state"); return false; } luaL_openlibs(blog->config.L); rc = luaL_loadfile(blog->config.L,conf); if (rc != LUA_OK) { syslog(LOG_ERR,"Lua error: (%d) %s",rc,lua_tostring(blog->config.L,-1)); return false; } rc = lua_pcall(blog->config.L,0,0,0); if (rc != LUA_OK) { syslog(LOG_ERR,"Lua error: (%d) %s",rc,lua_tostring(blog->config.L,-1)); return false; } /*------------------------------------------------------------------------ ; Ensure that searches are using the C locale, because we give the config ; file the full access to Lua, including os.setlocale(). ;-------------------------------------------------------------------------*/ setlocale(LC_COLLATE,"C"); /*----------------------------------------------------------------------- ; Some items aren't optional, so let Lua do the error checking for us and ; return an error, which is caught here; otherwise, things might blow up. ; Besides, catching common configuration errors is always a good thing. ;------------------------------------------------------------------------*/ lua_pushcfunction(blog->config.L,confL_config); lua_pushlightuserdata(blog->config.L,&blog->config); rc = lua_pcall(blog->config.L,1,0,0); if (rc != LUA_OK) { syslog(LOG_ERR,"Lua error: (%d) %s",rc,lua_tostring(blog->config.L,-1)); return false; } return true; } /***************************************************************************/ Blog *BlogNew(char const *configfile) { struct tm *ptm; Blog *blog = calloc(1,sizeof(struct blog)); if (blog == NULL) { syslog(LOG_ERR,"Can't allocate blog memory"); return NULL; } if (!config_read(configfile,blog)) { BlogFree(blog); return NULL; } umask(022); if (chdir(blog->config.basedir) != 0) { syslog(LOG_ERR,"%s: %s",blog->config.basedir,strerror(errno)); BlogFree(blog); return NULL; } blog->tnow = time(NULL); blog->lastmod = 0; ptm = localtime(&blog->tnow); blog->now.year = ptm->tm_year + 1900; blog->now.month = ptm->tm_mon + 1; blog->now.day = ptm->tm_mday; blog->now.part = 1; if (!set_date(".first",&blog->first,&blog->now) || !set_date(".last",&blog->last,&blog->now)) { BlogFree(blog); return NULL; } if ( (blog->last.year == blog->now.year) && (blog->last.month == blog->now.month) && (blog->last.day == blog->now.day) ) { blog->now.part = blog->last.part; } return blog; } /***********************************************************************/ void BlogFree(Blog *blog) { assert(blog != NULL); if (blog->config.L != NULL) lua_close(blog->config.L); free(blog); } /************************************************************************/ BlogEntry *BlogEntryNew(Blog *blog) { assert(blog != NULL); BlogEntry *pbe = malloc(sizeof(struct blogentry)); if (pbe != NULL) { pbe->node.ln_Succ = NULL; pbe->node.ln_Pred = NULL; pbe->blog = blog; pbe->valid = true; pbe->timestamp = time(NULL); pbe->when.year = blog->now.year; pbe->when.month = blog->now.month; pbe->when.day = blog->now.day; pbe->when.part = 0; pbe->title = NULL; pbe->class = NULL; pbe->author = NULL; pbe->status = NULL; pbe->adtag = NULL; pbe->body = NULL; } return pbe; } /***********************************************************************/ BlogEntry *BlogEntryRead(Blog *blog,struct btm const *which) { BlogEntry *entry; char pname[FILENAME_MAX]; FILE *sinbody; struct stat status; assert(blog != NULL); assert(which != NULL); assert(which->part > 0); assert(btm_cmp_date(which,&blog->first) >= 0); if (!date_check(which)) return NULL; date_to_part(pname,which,which->part); if (access(pname,R_OK) != 0) return NULL; entry = malloc(sizeof(struct blogentry)); if (entry == NULL) return NULL; entry->node.ln_Succ = NULL; entry->node.ln_Pred = NULL; entry->valid = true; entry->blog = blog; entry->when.year = which->year; entry->when.month = which->month; entry->when.day = which->day; entry->when.part = which->part; entry->title = blog_meta_entry("titles",which); entry->class = blog_meta_entry("class",which); entry->author = blog_meta_entry("authors",which); entry->status = blog_meta_entry("status",which); entry->adtag = blog_meta_entry("adtag",which); if (stat(pname,&status) == 0) entry->timestamp = status.st_mtime; else entry->timestamp = blog->tnow; sinbody = fopen(pname,"r"); if (sinbody == NULL) entry->body = strdup(""); else { entry->body = malloc(status.st_size + 1); fread(entry->body,1,status.st_size,sinbody); fclose(sinbody); entry->body[status.st_size] = '\0'; } return entry; } /**********************************************************************/ void BlogEntryReadBetweenU( Blog *blog, List *list, struct btm const *restrict start, struct btm const *restrict end ) { BlogEntry *entry; struct btm current; assert(blog != NULL); assert(list != NULL); assert(start != NULL); assert(end != NULL); current = *start; while(btm_cmp(¤t,end) <= 0) { entry = BlogEntryRead(blog,¤t); if (entry != NULL) { if (entry->timestamp > blog->lastmod) blog->lastmod = entry->timestamp; ListAddTail(list,&entry->node); current.part++; } else { current.part = 1; btm_inc_day(¤t); } } } /************************************************************************/ void BlogEntryReadBetweenD( Blog *blog, List *listb, struct btm const *restrict end, struct btm const *restrict start ) { List lista; assert(blog != NULL); assert(listb != NULL); assert(start != NULL); assert(end != NULL); ListInit(&lista); /*---------------------------------------------- ; we read in the entries from earlier to later, ; then just reverse the list. ;----------------------------------------------*/ BlogEntryReadBetweenU(blog,&lista,start,end); for ( Node *node = ListRemHead(&lista); NodeValid(node); node = ListRemHead(&lista) ) { ListAddHead(listb,node); } } /*******************************************************************/ void BlogEntryReadXD( Blog *blog, List *list, struct btm const *start, size_t num ) { struct btm current; assert(blog != NULL); assert(list != NULL); assert(start != NULL); assert(num > 0); current = *start; while(num) { if (btm_cmp_date(¤t,&blog->first) < 0) return; BlogEntry *entry = BlogEntryRead(blog,¤t); if (entry != NULL) { if (entry->timestamp > blog->lastmod) blog->lastmod = entry->timestamp; ListAddTail(list,&entry->node); num--; } current.part--; if (current.part == 0) { current.part = ENTRY_MAX; btm_dec_day(¤t); } } } /*******************************************************************/ void BlogEntryReadXU( Blog *blog, List *list, struct btm const *start, size_t num ) { struct btm current; assert(blog != NULL); assert(list != NULL); assert(num > 0); current = *start; while((num) && (btm_cmp_date(¤t,&blog->now) <= 0)) { BlogEntry *entry = BlogEntryRead(blog,¤t); if (entry != NULL) { if (entry->timestamp > blog->lastmod) blog->lastmod = entry->timestamp; ListAddTail(list,&entry->node); num--; current.part++; } else { current.part = 1; btm_inc_day(¤t); } } } /**************************************************************************/ int BlogEntryWrite(BlogEntry *entry) { char **authors; char **class; char **status; char **titles; char **adtag; size_t numa; size_t numc; size_t nums; size_t numt; size_t numad; size_t maxnum; char filename[FILENAME_MAX]; FILE *out; int rc; int lock; assert(entry != NULL); assert(entry->valid); lock = blog_lock(entry->blog->config.lockfile); rc = date_checkcreate(&entry->when); if (rc != 0) return rc; /*--------------------------------------------------------------------- ; The meta-data for the entries are stored in separate files. When ; updating an entry (or adding an entry), we need to rewrite these ; metafiles. So, we read them all in, make the adjustments required and ; write them out. ;------------------------------------------------------------------------*/ numa = blog_meta_read(&authors,"authors",&entry->when); numc = blog_meta_read(&class, "class", &entry->when); nums = blog_meta_read(&status, "status", &entry->when); numt = blog_meta_read(&titles, "titles", &entry->when); numad = blog_meta_read(&adtag, "adtag", &entry->when); maxnum = max(numa,max(numc,max(nums,numt))); blog_meta_adjust(&authors,numa, maxnum); blog_meta_adjust(&class, numc, maxnum); blog_meta_adjust(&status, nums, maxnum); blog_meta_adjust(&titles, numt, maxnum); blog_meta_adjust(&adtag, numad,maxnum); if (entry->when.part == 0) { if (maxnum == 99) return ENOMEM; authors[maxnum] = strdup(entry->author); class [maxnum] = strdup(entry->class); status [maxnum] = strdup(entry->status); titles [maxnum] = strdup(entry->title); adtag [maxnum] = strdup(entry->adtag); entry->when.part = ++maxnum; } else { free(authors[entry->when.part]); free(class [entry->when.part]); free(status [entry->when.part]); free(titles [entry->when.part]); free(adtag [entry->when.part]); authors[entry->when.part] = strdup(entry->author); class [entry->when.part] = strdup(entry->class); status [entry->when.part] = strdup(entry->status); titles [entry->when.part] = strdup(entry->title); adtag [entry->when.part] = strdup(entry->adtag); } blog_meta_write("authors",&entry->when,authors,maxnum); blog_meta_write("class" ,&entry->when,class, maxnum); blog_meta_write("status" ,&entry->when,status, maxnum); blog_meta_write("titles" ,&entry->when,titles, maxnum); blog_meta_write("adtag" ,&entry->when,adtag, maxnum); /*------------------------------- ; update the actual entry body ;---------------------------------*/ date_to_part(filename,&entry->when,entry->when.part); out = fopen(filename,"w"); fputs(entry->body,out); fclose(out); /*----------------- ; clean up ;------------------*/ for(size_t i = 0 ; i < maxnum ; i++) { free(authors[i]); free(class[i]); free(status[i]); free(titles[i]); free(adtag[i]); } free(authors); free(class); free(status); free(titles); free(adtag); /*------------------------------------------------------------------------ ; Oh, and if this is the latest entry to be added, update the .last file ; to reflect that. ;------------------------------------------------------------------------*/ if (btm_cmp(&entry->when,&entry->blog->last) > 0) { Blog *blog = (Blog *)entry->blog; /* XXX how to handle */ blog->last = entry->when; blog->now = entry->when; out = fopen(".last","w"); if (out) { fprintf( out, "%d/%02d/%02d.%d\n", entry->when.year, entry->when.month, entry->when.day, entry->when.part ); fclose(out); } } entry->blog->lastmod = entry->timestamp; blog_unlock(entry->blog->config.lockfile,lock); return 0; } /***********************************************************************/ size_t BlogLastEntry(Blog *blog,struct btm const *when) { (void)blog; assert(when != NULL); char name[FILENAME_MAX]; for (int i = 1 ; i < ENTRY_MAX ; i++) { date_to_part(name,when,i); if (access(name,R_OK) == -1) return (size_t)i-1; } return 0; } /***********************************************************************/ int BlogEntryFree(BlogEntry *entry) { assert(entry != NULL); if (!entry->valid) { syslog(LOG_ERR,"invalid entry being freed"); return 0; } free(entry->body); free(entry->adtag); free(entry->status); free(entry->author); free(entry->class); free(entry->title); free(entry); return 0; } /***********************************************************************/ .