/* * Copyright 2018, University Corporation for Atmospheric Research * See netcdf/COPYRIGHT file for copying and redistribution conditions. */ /* $Id: ffio.c,v 1.56 2006/09/15 20:40:30 ed Exp $ */ /* addition by O. Heudecker, AWI-Bremerhaven, 12.3.1998 */ /* added correction by John Sheldon and Hans Vahlenkamp 15.4.1998*/ #if HAVE_CONFIG_H #include #endif #include #include #include /* DEBUG */ #include #include #include #ifndef NC_NOERR #define NC_NOERR 0 #endif #ifdef HAVE_FCNTL_H #include #endif #ifdef HAVE_SYS_STAT_H #include #endif #ifdef HAVE_UNISTD_H #include #endif #if 0 /* Insertion by O. R. Heudecker, AWI-Bremerhaven 12.3.98 (1 line)*/ #include #include #endif #include "ncio.h" #include "fbits.h" #include "rnd.h" #if !defined(NDEBUG) && !defined(X_INT_MAX) #define X_INT_MAX 2147483647 #endif #if 0 /* !defined(NDEBUG) && !defined(X_ALIGN) */ #define X_ALIGN 4 #endif #define ALWAYS_NC_SHARE 0 /* DEBUG */ /* Forward */ static int ncio_ffio_filesize(ncio *nciop, off_t *filesizep); static int ncio_ffio_pad_length(ncio *nciop, off_t length); static int ncio_ffio_close(ncio *nciop, int doUnlink); /* Begin OS */ /* * What is the preferred I/O block size? * (This becomes the default *sizehint == ncp->chunk in the higher layers.) * TODO: What is the the best answer here? */ static size_t blksize(int fd) { struct ffc_stat_s sb; struct ffsw sw; #ifdef HAVE_STRUCT_STAT_ST_BLKSIZE #ifdef HAVE_FCNTL_H if (fffcntl(fd, FC_STAT, &sb, &sw) > -1) { #ifdef __crayx1 if(sb.st_blksize > 0) return (size_t) sb.st_blksize; #else if(sb.st_oblksize > 0) return (size_t) sb.st_oblksize; #endif } #endif #endif /* else, silent in the face of error */ return (size_t) 32768; } /* * Sortof like ftruncate, except won't make the * file shorter. */ static int fgrow(const int fd, const off_t len) { struct ffc_stat_s sb; struct ffsw sw; if (fffcntl(fd, FC_STAT, &sb, &sw) < 0) return errno; if (len < sb.st_size) return NC_NOERR; { const long dumb = 0; /* cache current position */ const off_t pos = ffseek(fd, 0, SEEK_CUR); if(pos < 0) return errno; if (ffseek(fd, len-sizeof(dumb), SEEK_SET) < 0) return errno; if(ffwrite(fd, (void *)&dumb, sizeof(dumb)) < 0) return errno; if (ffseek(fd, pos, SEEK_SET) < 0) return errno; } /* else */ return NC_NOERR; } /* * Sortof like ftruncate, except won't make the file shorter. Differs * from fgrow by only writing one byte at designated seek position, if * needed. */ static int fgrow2(const int fd, const off_t len) { struct ffc_stat_s sb; struct ffsw sw; if (fffcntl(fd, FC_STAT, &sb, &sw) < 0) return errno; if (len <= sb.st_size) return NC_NOERR; { const char dumb = 0; /* we don't use ftruncate() due to problem with FAT32 file systems */ /* cache current position */ const off_t pos = ffseek(fd, 0, SEEK_CUR); if(pos < 0) return errno; if (ffseek(fd, len-1, SEEK_SET) < 0) return errno; if(ffwrite(fd, (void *)&dumb, sizeof(dumb)) < 0) return errno; if (ffseek(fd, pos, SEEK_SET) < 0) return errno; } return NC_NOERR; } /* End OS */ /* Begin ffio */ static int ffio_pgout(ncio *const nciop, off_t const offset, const size_t extent, const void *const vp, off_t *posp) { #ifdef X_ALIGN assert(offset % X_ALIGN == 0); assert(extent % X_ALIGN == 0); #endif if(*posp != offset) { if(ffseek(nciop->fd, offset, SEEK_SET) != offset) { return errno; } *posp = offset; } if(ffwrite(nciop->fd, vp, extent) != extent) { return errno; } *posp += extent; return NC_NOERR; } static int ffio_pgin(ncio *const nciop, off_t const offset, const size_t extent, void *const vp, size_t *nreadp, off_t *posp) { int status; ssize_t nread; #ifdef X_ALIGN assert(offset % X_ALIGN == 0); assert(extent % X_ALIGN == 0); #endif if(*posp != offset) { if(ffseek(nciop->fd, offset, SEEK_SET) != offset) { status = errno; return status; } *posp = offset; } errno = 0; nread = ffread(nciop->fd, vp, extent); if(nread != extent) { status = errno; if(nread == -1 || status != NC_NOERR) return status; /* else it's okay we read 0. */ } *nreadp = nread; *posp += nread; return NC_NOERR; } /* */ typedef struct ncio_ffio { off_t pos; /* buffer */ off_t bf_offset; size_t bf_extent; size_t bf_cnt; void *bf_base; } ncio_ffio; static int ncio_ffio_rel(ncio *const nciop, off_t offset, int rflags) { ncio_ffio *ffp = (ncio_ffio *)nciop->pvt; int status = NC_NOERR; assert(ffp->bf_offset <= offset); assert(ffp->bf_cnt != 0); assert(ffp->bf_cnt <= ffp->bf_extent); #ifdef X_ALIGN assert(offset < ffp->bf_offset + X_ALIGN); assert(ffp->bf_cnt % X_ALIGN == 0 ); #endif if(fIsSet(rflags, RGN_MODIFIED)) { if(!fIsSet(nciop->ioflags, NC_WRITE)) return EPERM; /* attempt to write readonly file */ status = ffio_pgout(nciop, ffp->bf_offset, ffp->bf_cnt, ffp->bf_base, &ffp->pos); /* if error, invalidate buffer anyway */ } ffp->bf_offset = OFF_NONE; ffp->bf_cnt = 0; return status; } static int ncio_ffio_get(ncio *const nciop, off_t offset, size_t extent, int rflags, void **const vpp) { ncio_ffio *ffp = (ncio_ffio *)nciop->pvt; int status = NC_NOERR; #ifdef X_ALIGN size_t rem; #endif if(fIsSet(rflags, RGN_WRITE) && !fIsSet(nciop->ioflags, NC_WRITE)) return EPERM; /* attempt to write readonly file */ assert(extent != 0); assert(extent < X_INT_MAX); /* sanity check */ assert(ffp->bf_cnt == 0); #ifdef X_ALIGN /* round to seekable boundaries */ rem = offset % X_ALIGN; if(rem != 0) { offset -= rem; extent += rem; } { const size_t rndup = extent % X_ALIGN; if(rndup != 0) extent += X_ALIGN - rndup; } assert(offset % X_ALIGN == 0); assert(extent % X_ALIGN == 0); #endif if(ffp->bf_extent < extent) { if(ffp->bf_base != NULL) { free(ffp->bf_base); ffp->bf_base = NULL; ffp->bf_extent = 0; } assert(ffp->bf_extent == 0); ffp->bf_base = malloc(extent); if(ffp->bf_base == NULL) return ENOMEM; ffp->bf_extent = extent; } status = ffio_pgin(nciop, offset, extent, ffp->bf_base, &ffp->bf_cnt, &ffp->pos); if(status != NC_NOERR) return status; ffp->bf_offset = offset; if(ffp->bf_cnt < extent) { (void) memset((char *)ffp->bf_base + ffp->bf_cnt, 0, extent - ffp->bf_cnt); ffp->bf_cnt = extent; } #ifdef X_ALIGN *vpp = (char *)ffp->bf_base + rem; #else *vpp = (char *)ffp->bf_base; #endif return NC_NOERR; } static int ncio_ffio_move(ncio *const nciop, off_t to, off_t from, size_t nbytes, int rflags) { int status = NC_NOERR; off_t lower = from; off_t upper = to; char *base; size_t diff = upper - lower; size_t extent = diff + nbytes; rflags &= RGN_NOLOCK; /* filter unwanted flags */ if(to == from) return NC_NOERR; /* NOOP */ if(to > from) { /* growing */ lower = from; upper = to; } else { /* shrinking */ lower = to; upper = from; } diff = upper - lower; extent = diff + nbytes; status = ncio_ffio_get(nciop, lower, extent, RGN_WRITE|rflags, (void **)&base); if(status != NC_NOERR) return status; if(to > from) (void) memmove(base + diff, base, nbytes); else (void) memmove(base, base + diff, nbytes); (void) ncio_ffio_rel(nciop, lower, RGN_MODIFIED); return status; } #ifdef NOFFFLUSH /* ncio_ffio_sync_noffflush is only needed if the FFIO global layer is * used, because it currently has a bug that causes the PEs to hang * RKO 06/26/98 */ static int ncio_ffio_sync_noffflush(ncio *const nciop) { struct ffc_stat_s si; /* for call to fffcntl() */ struct ffsw ffstatus; /* to return ffsw.sw_error */ /* run some innocuous ffio routine to get if any errno */ if(fffcntl(nciop->fd, FC_STAT, &si, &ffstatus) < 0) return ffstatus.sw_error; return NC_NOERR; } /* this tests to see if the global FFIO layer is being called for * returns ~0 if it is, else returns 0 */ static int ncio_ffio_global_test(const char *ControlString) { if (strstr(ControlString,"global") != (char *) NULL) { return ~0; } else { return 0; } } #endif static int ncio_ffio_sync(ncio *const nciop) { int test_flush; #ifdef __crayx1 struct ffsw stat; test_flush = ffflush(nciop->fd,&stat) < 0; #else test_flush = ffflush(nciop->fd) < 0; #endif if(test_flush) return errno; return NC_NOERR; } static void ncio_ffio_free(void *const pvt) { ncio_ffio *ffp = (ncio_ffio *)pvt; if(ffp == NULL) return; if(ffp->bf_base != NULL) { free(ffp->bf_base); ffp->bf_base = NULL; ffp->bf_offset = OFF_NONE; ffp->bf_extent = 0; ffp->bf_cnt = 0; } } static int ncio_ffio_init2(ncio *const nciop, size_t *sizehintp) { ncio_ffio *ffp = (ncio_ffio *)nciop->pvt; assert(nciop->fd >= 0); ffp->bf_extent = *sizehintp; assert(ffp->bf_base == NULL); /* this is separate allocation because it may grow */ ffp->bf_base = malloc(ffp->bf_extent); if(ffp->bf_base == NULL) { ffp->bf_extent = 0; return ENOMEM; } /* else */ return NC_NOERR; } static void ncio_ffio_init(ncio *const nciop) { ncio_ffio *ffp = (ncio_ffio *)nciop->pvt; *((ncio_relfunc **)&nciop->rel) = ncio_ffio_rel; /* cast away const */ *((ncio_getfunc **)&nciop->get) = ncio_ffio_get; /* cast away const */ *((ncio_movefunc **)&nciop->move) = ncio_ffio_move; /* cast away const */ *((ncio_syncfunc **)&nciop->sync) = ncio_ffio_sync; /* cast away const */ *((ncio_filesizefunc **)&nciop->filesize) = ncio_ffio_filesize; /* cast away const */ *((ncio_pad_lengthfunc **)&nciop->pad_length) = ncio_ffio_pad_length; /* cast away const */ *((ncio_closefunc **)&nciop->close) = ncio_ffio_close; /* cast away const */ ffp->pos = -1; ffp->bf_offset = OFF_NONE; ffp->bf_extent = 0; ffp->bf_cnt = 0; ffp->bf_base = NULL; } /* */ static void ncio_free(ncio *nciop) { if(nciop == NULL) return; if(nciop->free != NULL) nciop->free(nciop->pvt); free(nciop); } static ncio * ncio_ffio_new(const char *path, int ioflags) { size_t sz_ncio = M_RNDUP(sizeof(ncio)); size_t sz_path = M_RNDUP(strlen(path) +1); size_t sz_ncio_pvt; ncio *nciop; #if ALWAYS_NC_SHARE /* DEBUG */ fSet(ioflags, NC_SHARE); #endif if(fIsSet(ioflags, NC_SHARE)) fprintf(stderr, "NC_SHARE not implemented for ffio\n"); sz_ncio_pvt = sizeof(ncio_ffio); nciop = (ncio *) malloc(sz_ncio + sz_path + sz_ncio_pvt); if(nciop == NULL) return NULL; nciop->ioflags = ioflags; *((int *)&nciop->fd) = -1; /* cast away const */ nciop->path = (char *) ((char *)nciop + sz_ncio); (void) strcpy((char *)nciop->path, path); /* cast away const */ /* cast away const */ *((void **)&nciop->pvt) = (void *)(nciop->path + sz_path); ncio_ffio_init(nciop); return nciop; } /* put all the FFIO assign specific code here * returns a pointer to an internal static char location * which may change when the function is called again * if the returned pointer is NULL this indicates that an error occurred * check errno for the netCDF error value */ /* prototype fortran subroutines */ #ifdef __crayx1 void ASNQFILE(const char *filename, const char *attribute, int *istat, int flen, int alen); void ASNFILE(const char *filename, const char *attribute, int *istat, int flen, int alen); #else void ASNQFILE(_fcd filename, _fcd attribute, int *istat); void ASNFILE(_fcd filename, _fcd attribute, int *istat); #endif #define BUFLEN 256 static const char * ncio_ffio_assign(const char *filename) { static char buffer[BUFLEN]; int istat; #ifndef __crayx1 _fcd fnp, fbp; #endif char *envstr; char *xtra_assign; char emptystr='\0'; /* put things into known states */ memset(buffer,'\0',BUFLEN); errno = NC_NOERR; /* set up Fortran character pointers */ #ifdef __crayx1 ASNQFILE(filename, buffer, &istat, strlen(filename)+1, BUFLEN); #else fnp = _cptofcd((char *)filename, strlen(filename)); fbp = _cptofcd(buffer, BUFLEN); /* see if the user has "assigned" to this file */ ASNQFILE(fnp, fbp, &istat); #endif if (istat == 0) { /* user has already specified an assign */ return buffer; } else if (istat > 0 || istat < -1) { /* error occurred */ errno = EINVAL; return (const char *) NULL; } /* istat = -1 -> no assign for file */ envstr = getenv("NETCDF_FFIOSPEC"); if(envstr == (char *) NULL) { envstr = "bufa:336:2"; /* this should be macroized */ } /* Insertion by Olaf Heudecker, AWI-Bremerhaven, 12.8.1998 to allow more versatile FFIO-assigns */ /* this is unnecessary and could have been included * into the NETCDF_FFIOSPEC environment variable */ xtra_assign = getenv("NETCDF_XFFIOSPEC"); if(xtra_assign == (char *) NULL) { xtra_assign=&emptystr; } if (strlen(envstr)+strlen(xtra_assign) + 4 > BUFLEN) { /* Error: AssignCommand too long */ errno=E2BIG; return (const char *) NULL; } (void) sprintf(buffer,"-F %s %s", envstr,xtra_assign); #ifdef __crayx1 ASNFILE(filename, buffer, &istat, strlen(filename)+1, strlen(buffer)+1); #else fbp = _cptofcd(buffer, strlen(buffer)); ASNFILE(fnp, fbp, &istat); #endif if (istat == 0) { /* success */ return buffer; } else { /* error */ errno = EINVAL; return (const char *) NULL; } } /* Public below this point */ /* TODO: Is this reasonable for this platform? */ static const size_t NCIO_MINBLOCKSIZE = 256; static const size_t NCIO_MAXBLOCKSIZE = 268435456; /* sanity check, about X_SIZE_T_MAX/8 */ int ffio_create(const char *path, int ioflags, size_t initialsz, off_t igeto, size_t igetsz, size_t *sizehintp, void* parameters, ncio **nciopp, void **const igetvpp) { ncio *nciop; const char *ControlString; int oflags = (O_RDWR|O_CREAT|O_TRUNC); int fd; int status; struct ffsw stat; if(initialsz < (size_t)igeto + igetsz) initialsz = (size_t)igeto + igetsz; fSet(ioflags, NC_WRITE); if(path == NULL || *path == 0) return EINVAL; nciop = ncio_ffio_new(path, ioflags); if(nciop == NULL) return ENOMEM; if ((ControlString = ncio_ffio_assign(path)) == (const char *)NULL) { /* an error occurred - just punt */ status = errno; goto unwind_new; } #ifdef NOFFFLUSH /* test whether the global layer is being called for * this file ... if so then can't call FFIO ffflush() * RKO 06/26/98 */ if (strstr(ControlString,"global") != (char *) NULL) { /* use no ffflush version */ *((ncio_syncfunc **)&nciop->sync) = ncio_ffio_sync_noffflush; } #endif if(fIsSet(ioflags, NC_NOCLOBBER)) fSet(oflags, O_EXCL); /* Orig: fd = ffopens(path, oflags, 0666, 0, &stat, ControlString); */ fd = ffopen(path, oflags, 0666, 0, &stat); if(fd < 0) { status = errno; goto unwind_new; } *((int *)&nciop->fd) = fd; /* cast away const */ if(*sizehintp < NCIO_MINBLOCKSIZE || *sizehintp > NCIO_MAXBLOCKSIZE) { /* Use default */ *sizehintp = blksize(fd); } else { *sizehintp = M_RNDUP(*sizehintp); } status = ncio_ffio_init2(nciop, sizehintp); if(status != NC_NOERR) goto unwind_open; if(initialsz != 0) { status = fgrow(fd, (off_t)initialsz); if(status != NC_NOERR) goto unwind_open; } if(igetsz != 0) { status = nciop->get(nciop, igeto, igetsz, RGN_WRITE, igetvpp); if(status != NC_NOERR) goto unwind_open; } *nciopp = nciop; return NC_NOERR; unwind_open: (void) ffclose(fd); /* ?? unlink */ /*FALLTHRU*/ unwind_new: ncio_close(nciop,!fIsSet(ioflags, NC_NOCLOBBER)); return status; } int ffio_open(const char *path, int ioflags, off_t igeto, size_t igetsz, size_t *sizehintp, void* parameters, ncio **nciopp, void **const igetvpp) { ncio *nciop; const char *ControlString; int oflags = fIsSet(ioflags, NC_WRITE) ? O_RDWR : O_RDONLY; int fd; int status; struct ffsw stat; if(path == NULL || *path == 0) return EINVAL; nciop = ncio_ffio_new(path, ioflags); if(nciop == NULL) return ENOMEM; if ((ControlString = ncio_ffio_assign(path)) == (const char *)NULL) { /* an error occurred - just punt */ status = errno; goto unwind_new; } #ifdef NOFFFLUSH /* test whether the global layer is being called for * this file ... if so then can't call FFIO ffflush() * RKO 06/26/98 */ if (strstr(ControlString,"global") != (char *) NULL) { /* use no ffflush version */ *((ncio_syncfunc **)&nciop->sync) = ncio_ffio_sync_noffflush; } #endif /* Orig: fd = ffopens(path, oflags, 0, 0, &stat, ControlString); */ fd = ffopen(path, oflags, 0, 0, &stat); if(fd < 0) { status = errno; goto unwind_new; } *((int *)&nciop->fd) = fd; /* cast away const */ if(*sizehintp < NCIO_MINBLOCKSIZE || *sizehintp > NCIO_MAXBLOCKSIZE) { /* Use default */ *sizehintp = blksize(fd); } else { *sizehintp = M_RNDUP(*sizehintp); } status = ncio_ffio_init2(nciop, sizehintp); if(status != NC_NOERR) goto unwind_open; if(igetsz != 0) { status = nciop->get(nciop, igeto, igetsz, 0, igetvpp); if(status != NC_NOERR) goto unwind_open; } *nciopp = nciop; return NC_NOERR; unwind_open: (void) ffclose(fd); /*FALLTHRU*/ unwind_new: ncio_close(nciop,0); return status; } /* * Get file size in bytes. * Is use of ffseek() really necessary, or could we use standard fstat() call * and get st_size member? */ static int ncio_ffio_filesize(ncio *nciop, off_t *filesizep) { off_t filesize, current, reset; if(nciop == NULL) return EINVAL; current = ffseek(nciop->fd, 0, SEEK_CUR); /* save current */ *filesizep = ffseek(nciop->fd, 0, SEEK_END); /* get size */ reset = ffseek(nciop->fd, current, SEEK_SET); /* reset */ if(reset != current) return EINVAL; return NC_NOERR; } /* * Sync any changes to disk, then extend file so its size is length. * This is only intended to be called before close, if the file is * open for writing and the actual size does not match the calculated * size, perhaps as the result of having been previously written in * NOFILL mode. */ static int ncio_ffio_pad_length(ncio *nciop, off_t length) { int status = NC_NOERR; if(nciop == NULL) return EINVAL; if(!fIsSet(nciop->ioflags, NC_WRITE)) return EPERM; /* attempt to write readonly file */ status = nciop->sync(nciop); if(status != NC_NOERR) return status; status = fgrow2(nciop->fd, length); if(status != NC_NOERR) return errno; return NC_NOERR; } static int ncio_ffio_close(ncio *nciop, int doUnlink) { /* * TODO: I believe this function is lacking the de-assignment of the * Fortran LUN assigned by ASNFILE in ncio_ffio_assign(...) -- SRE * 2002-07-10. */ int status = NC_NOERR; if(nciop == NULL) return EINVAL; status = nciop->sync(nciop); (void) ffclose(nciop->fd); if(doUnlink) (void) unlink(nciop->path); ncio__ffio_free(nciop); return status; }