/* Synchronet message base (SMB) FILE stream and FileBase routines */ /**************************************************************************** * @format.tab-size 4 (Plain Text/Source Code File Header) * * @format.use-tabs true (see http://www.synchro.net/ptsc_hdr.html) * * * * Copyright Rob Swindell - http://www.synchro.net/copyright.html * * * * This library is free software; you can redistribute it and/or * * modify it under the terms of the GNU Lesser General Public License * * as published by the Free Software Foundation; either version 2 * * of the License, or (at your option) any later version. * * See the GNU Lesser General Public License for more details: lgpl.txt or * * http://www.fsf.org/copyleft/lesser.html * * * * For Synchronet coding style and modification guidelines, see * * http://www.synchro.net/source.html * * * * Note: If this box doesn't appear square, then you need to fix your tabs. * ****************************************************************************/ #include "smblib.h" int smb_feof(FILE* fp) { return(feof(fp)); } int smb_ferror(FILE* fp) { return(ferror(fp)); } int smb_fflush(FILE* fp) { return(fflush(fp)); } int smb_fgetc(FILE* fp) { return(fgetc(fp)); } int smb_fputc(int ch, FILE* fp) { return(fputc(ch,fp)); } int smb_fseek(FILE* fp, off_t offset, int whence) { return(fseeko(fp,offset,whence)); } off_t smb_ftell(FILE* fp) { return(ftello(fp)); } off_t smb_fgetlength(FILE* fp) { return(filelength(fileno(fp))); } int smb_fsetlength(FILE* fp, long length) { return(chsize(fileno(fp),length)); } void smb_rewind(FILE* fp) { rewind(fp); } void smb_clearerr(FILE* fp) { clearerr(fp); } size_t smb_fread(smb_t* smb, void* buf, size_t bytes, FILE* fp) { size_t ret; time_t start=0; while(1) { if((ret=fread(buf,sizeof(char),bytes,fp))==bytes) return(ret); if(feof(fp) || (get_errno()!=EDEADLOCK && get_errno()!=EACCES)) return(ret); if(!start) start=time(NULL); else if(time(NULL)-start>=(time_t)smb->retry_time) break; SLEEP(smb->retry_delay); } return(ret); } #if defined(__BORLANDC__) #pragma argsused #endif size_t smb_fwrite(smb_t* smb, const void* buf, size_t bytes, FILE* fp) { return(fwrite(buf,1,bytes,fp)); } /****************************************************************************/ /* Opens a non-shareable file (FILE*) associated with a message base */ /* Retries for retry_time number of seconds */ /* Return 0 on success, non-zero otherwise */ /****************************************************************************/ int smb_open_fp(smb_t* smb, FILE** fp, int share) { int file; char path[MAX_PATH+1]; char* ext; time_t start=0; if(fp==&smb->shd_fp) ext="shd"; else if(fp==&smb->sid_fp) ext="sid"; else if(fp==&smb->sdt_fp) ext="sdt"; else if(fp==&smb->sda_fp) ext="sda"; else if(fp==&smb->sha_fp) ext="sha"; else if(fp==&smb->hash_fp) ext="hash"; else { safe_snprintf(smb->last_error,sizeof(smb->last_error) ,"%s opening %s: Illegal FILE* pointer argument: %p", __FUNCTION__ ,smb->file, fp); return(SMB_ERR_OPEN); } if(*fp!=NULL) /* Already open! */ return(SMB_SUCCESS); SAFEPRINTF2(path,"%s.%s",smb->file,ext); while(1) { if((file=sopen(path,O_RDWR|O_CREAT|O_BINARY,share,DEFFILEMODE))!=-1) break; if(get_errno()!=EACCES && get_errno()!=EAGAIN) { safe_snprintf(smb->last_error,sizeof(smb->last_error) ,"%s %d '%s' opening %s", __FUNCTION__ ,get_errno(),STRERROR(get_errno()),path); return(SMB_ERR_OPEN); } if(!start) start=time(NULL); else if(time(NULL)-start>=(time_t)smb->retry_time) { safe_snprintf(smb->last_error,sizeof(smb->last_error) ,"%s timeout opening %s (errno=%d, retry_time=%lu)", __FUNCTION__ ,path, get_errno(), (ulong)smb->retry_time); return(SMB_ERR_TIMEOUT); } SLEEP(smb->retry_delay); } if((*fp=fdopen(file,"r+b"))==NULL) { safe_snprintf(smb->last_error,sizeof(smb->last_error) ,"%s %d '%s' fdopening %s (%d)", __FUNCTION__ ,get_errno(),STRERROR(get_errno()),path,file); close(file); return(SMB_ERR_OPEN); } setvbuf(*fp,NULL,_IOFBF, 2*1024); return(SMB_SUCCESS); } /****************************************************************************/ /****************************************************************************/ void smb_close_fp(FILE** fp) { if(fp!=NULL) { if(*fp!=NULL) fclose(*fp); *fp=NULL; } } /****************************************************************************/ /* maxlen includes the NUL terminator */ /****************************************************************************/ char* smb_fileidxname(const char* filename, char* buf, size_t maxlen) { size_t fnlen = strlen(filename); char* ext = getfext(filename); if(ext != NULL) { size_t extlen = strlen(ext); if(extlen >= maxlen - 1) { strncpy(buf, filename, maxlen); buf[maxlen - 1] = '\0'; } else { fnlen -= extlen; if(fnlen > (maxlen - 1) - extlen) fnlen = (maxlen - 1) - extlen; safe_snprintf(buf, maxlen, "%-.*s%s", (int)fnlen, filename, ext); } } else { /* no extension */ strncpy(buf, filename, maxlen); buf[maxlen - 1] = '\0'; } return buf; } /****************************************************************************/ /* Find file in index via either/or: */ /* - CASE-INSENSITIVE 'filename' search through index (no wildcards) */ /* - file content size and hash details found in 'file' */ /****************************************************************************/ int smb_findfile(smb_t* smb, const char* filename, smbfile_t* file) { long offset = 0; smbfile_t* f = file; smbfile_t file_ = {0}; if(f == NULL) f = &file_; char fname[SMB_FILEIDX_NAMELEN + 1] = ""; if(filename != NULL) smb_fileidxname(filename, fname, sizeof(fname)); if(smb->sid_fp == NULL) { safe_snprintf(smb->last_error, sizeof(smb->last_error), "%s msgbase not open", __FUNCTION__); return SMB_ERR_NOT_OPEN; } f->dir = smb->dirnum; rewind(smb->sid_fp); while(!feof(smb->sid_fp)) { fileidxrec_t fidx; if(smb_fread(smb, &fidx, sizeof(fidx), smb->sid_fp) != sizeof(fidx)) break; f->idx_offset = offset++; if(filename != NULL) { if(stricmp(fidx.name, fname) != 0) continue; f->file_idx = fidx; return SMB_SUCCESS; } if(file == NULL) continue; if((f->file_idx.hash.flags & SMB_HASH_MASK) != 0 || f->file_idx.idx.size > 0) { if(f->file_idx.idx.size > 0 && f->file_idx.idx.size != fidx.idx.size) continue; if((f->file_idx.hash.flags & SMB_HASH_CRC16) && f->file_idx.hash.data.crc16 != fidx.hash.data.crc16) continue; if((f->file_idx.hash.flags & SMB_HASH_CRC32) && f->file_idx.hash.data.crc32 != fidx.hash.data.crc32) continue; if((f->file_idx.hash.flags & SMB_HASH_MD5) && memcmp(f->file_idx.hash.data.md5, fidx.hash.data.md5, sizeof(fidx.hash.data.md5)) !=0) continue; if((f->file_idx.hash.flags & SMB_HASH_SHA1) && memcmp(f->file_idx.hash.data.sha1, fidx.hash.data.sha1, sizeof(fidx.hash.data.sha1)) !=0) continue; f->file_idx = fidx; return SMB_SUCCESS; } } return SMB_ERR_NOT_FOUND; } /****************************************************************************/ /****************************************************************************/ int smb_loadfile(smb_t* smb, const char* filename, smbfile_t* file, enum file_detail detail) { int result; memset(file, 0, sizeof(*file)); if((result = smb_findfile(smb, filename, file)) != SMB_SUCCESS) return result; return smb_getfile(smb, file, detail); } /****************************************************************************/ /****************************************************************************/ int smb_getfile(smb_t* smb, smbfile_t* file, enum file_detail detail) { int result; file->name = file->file_idx.name; file->hdr.when_written.time = file->idx.time; if(detail > file_detail_index) { if((result = smb_getmsghdr(smb, file)) != SMB_SUCCESS) return result; if(detail >= file_detail_extdesc) file->extdesc = smb_getmsgtxt(smb, file, GETMSGTXT_BODY_ONLY); if(detail >= file_detail_metadata) file->metadata = smb_getmsgtxt(smb, file, GETMSGTXT_TAIL_ONLY); } file->dir = smb->dirnum; return SMB_SUCCESS; } /****************************************************************************/ /* Writes both header and index information for file 'file' */ /* Like smb_putmsg() but doesn't (re)-initialize index (idx) */ /****************************************************************************/ int smb_putfile(smb_t* smb, smbfile_t* file) { int result; if((result = smb_putmsghdr(smb, file))!=SMB_SUCCESS) return result; return smb_putmsgidx(smb, file); } /****************************************************************************/ /****************************************************************************/ void smb_freefilemem(smbfile_t* file) { smb_freemsgmem(file); } /****************************************************************************/ /****************************************************************************/ int smb_addfile(smb_t* smb, smbfile_t* file, int storage, const char* extdesc, const char* metadata, const char* path) { if(file->name == NULL || *file->name == '\0') { safe_snprintf(smb->last_error, sizeof(smb->last_error), "%s missing name", __FUNCTION__); return SMB_ERR_HDR_FIELD; } if(smb_findfile(smb, file->name, NULL) == SMB_SUCCESS) { safe_snprintf(smb->last_error, sizeof(smb->last_error), "%s duplicate name found: %s", __FUNCTION__, file->name); return SMB_DUPE_MSG; } if(path != NULL) { file->size = flength(path); file->hdr.when_written.time = (uint32_t)fdate(path); if(!(smb->status.attr & SMB_NOHASH) && file->file_idx.hash.flags == 0) file->file_idx.hash.flags = smb_hashfile(path, file->size, &file->file_idx.hash.data); } file->hdr.attr |= MSG_FILE; file->hdr.type = SMB_MSG_TYPE_FILE; return smb_addmsg(smb, file, storage, SMB_HASH_SOURCE_NONE, XLAT_NONE ,/* body: */(const uchar*)extdesc, /* tail: */(const uchar*)metadata); } /****************************************************************************/ /* Like smb_addfile(), except 'metadata' is a str_list_t: 'list' */ /****************************************************************************/ int smb_addfile_withlist(smb_t* smb, smbfile_t* file, int storage, const char* extdesc, str_list_t list, const char* path) { char* metadata = NULL; int result; if(list != NULL && *list != NULL) { size_t size = strListCount(list) * 1024; metadata = calloc(1, size); if(metadata == NULL) return SMB_ERR_MEM; strListCombine(list, metadata, size - 1, "\r\n"); } result = smb_addfile(smb, file, storage, extdesc, metadata, path); free(metadata); return result; } /****************************************************************************/ /****************************************************************************/ int smb_renewfile(smb_t* smb, smbfile_t* file, int storage, const char* path) { int result; if((result = smb_removefile(smb, file)) != SMB_SUCCESS) return result; return smb_addfile(smb, file, storage, file->extdesc, file->metadata, path); } /****************************************************************************/ /****************************************************************************/ int smb_removefile(smb_t* smb, smbfile_t* file) { int result; int removed = 0; char fname[SMB_FILEIDX_NAMELEN + 1] = ""; if(!smb->locked && smb_locksmbhdr(smb) != SMB_SUCCESS) return SMB_ERR_LOCK; file->hdr.attr |= MSG_DELETE; if((result = smb_putmsghdr(smb, file)) != SMB_SUCCESS) { smb_unlocksmbhdr(smb); return result; } if((result = smb_getstatus(smb)) != SMB_SUCCESS) { smb_unlocksmbhdr(smb); return result; } if((result = smb_open_ha(smb)) != SMB_SUCCESS) { smb_unlocksmbhdr(smb); return result; } if((result = smb_open_da(smb)) != SMB_SUCCESS) { smb_unlocksmbhdr(smb); return result; } result = smb_freemsg(smb, file); smb_close_ha(smb); smb_close_da(smb); // Now remove from index: smb_fileidxname(file->name, fname, sizeof(fname)); if(result == SMB_SUCCESS) { rewind(smb->sid_fp); fileidxrec_t* fidx = malloc(smb->status.total_files * sizeof(*fidx)); if(fidx == NULL) { smb_unlocksmbhdr(smb); return SMB_ERR_MEM; } if(fread(fidx, sizeof(*fidx), smb->status.total_files, smb->sid_fp) != smb->status.total_files) { free(fidx); smb_unlocksmbhdr(smb); return SMB_ERR_READ; } rewind(smb->sid_fp); for(uint32_t i = 0; i < smb->status.total_files; i++) { if(stricmp(fidx[i].name, fname) == 0) { removed++; continue; } if(fwrite(fidx + i, sizeof(*fidx), 1, smb->sid_fp) != 1) { safe_snprintf(smb->last_error, sizeof(smb->last_error), "%s re-writing index" ,__FUNCTION__); result = SMB_ERR_WRITE; break; } } free(fidx); if(result == SMB_SUCCESS) { if(removed < 1) { safe_snprintf(smb->last_error, sizeof(smb->last_error), "%s name not found: %s" ,__FUNCTION__, fname); result = SMB_ERR_NOT_FOUND; } else { fflush(smb->sid_fp); smb->status.total_files -= removed; if(chsize(fileno(smb->sid_fp), smb->status.total_files * sizeof(*fidx)) != 0) { safe_snprintf(smb->last_error, sizeof(smb->last_error), "%s error %d truncating index" ,__FUNCTION__, errno); result = SMB_ERR_DELETE; } else result = smb_putstatus(smb); } } } smb_unlocksmbhdr(smb); return result; }