Files
dvdisaster/large-io.c
2009-11-21 16:29:02 +09:00

772 lines
17 KiB
C

/* dvdisaster: Additional error correction for optical media.
* Copyright (C) 2004-2009 Carsten Gnoerlich.
* Project home page: http://www.dvdisaster.com
* Email: carsten@dvdisaster.com -or- cgnoerlich@fsfe.org
*
* 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,
* or direct your browser at http://www.gnu.org.
*/
#include "dvdisaster.h"
/***
*** Wrappers around the standard low level file system interface.
***
* This is pointless for Linux, but gives us the possibility to
* hide differences in Linux/Windows semantics and to
* do split image files on VFAT in a transparent way.
*
* Note the different return value semantics from standard functions:
* - LargeOpen() returns a LargeFile pointer on success and NULL otherwise;
* - LargeRead() and LargeWrite() return the number of bytes read/written;
* - the remaining functions return True on success or False on failure.
*
* Also, individual behaviour may deviate from standard functions especially
* when in split file mode.
*/
//#define MAX_FILE_SIZE (128*1024*1024)
//#define MAX_FILE_SIZE (8*1024*1024)
#define MAX_FILE_SIZE (2048LL*1024LL*1024LL)
#ifdef SYS_MINGW
#include <windows.h>
#define stat _stati64
#define lseek _lseeki64
/* The original windows ftruncate has off_size (32bit) */
int large_ftruncate(int fd, gint64 size)
{ gint32 handle;
if((handle=_get_osfhandle(fd)) == -1)
return -1;
if(_lseeki64(fd, size, SEEK_SET) == -1)
return -1;
if(SetEndOfFile((HANDLE)handle) == 0)
return -1;
return 0;
}
#else
#define large_ftruncate ftruncate
#endif /* SYS_MINGW */
/*
* convert special chars in file names to correct OS encoding
*/
static gchar* os_path(char *path_in)
{ gchar *cp_path = g_locale_from_utf8(path_in, -1, NULL, NULL, NULL);
if(cp_path == NULL)
{ errno = EINVAL;
return NULL;
}
REMEMBER(cp_path);
return cp_path;
}
/*
* local aux function: opens the given segment of a large file.
*/
static int open_segment(LargeFile *lf, int n)
{ char name[lf->namelen];
gchar *cp_path;
if(!lf->suffix) g_sprintf(name, "%s%02d", lf->basename, n);
else g_sprintf(name, "%s%02d.%s", lf->basename, n, lf->suffix);
cp_path = os_path(name);
if(!cp_path) return FALSE;
lf->fileSegment[n] = open(cp_path, lf->flags, lf->mode);
g_free(cp_path);
if(lf->fileSegment[n] == -1)
{ PrintLog("open_segment(\"%s*\", %d) failed\n", lf->basename, n);
return FALSE;
}
return TRUE;
}
/*
* Large stat replacement (queries only file size)
*/
int LargeStat(char *path, gint64 *length_return)
{ struct stat mystat;
char name[strlen(path)+3];
char prefix[strlen(path)+1];
char *suffix = NULL, *c;
int i;
/* Unsplit file case */
if(!Closure->splitFiles)
{ gchar *cp_path = os_path(path);
if(!cp_path) return FALSE;
if(stat(cp_path, &mystat) == -1)
{ g_free(cp_path);
return FALSE;
}
g_free(cp_path);
if(!S_ISREG(mystat.st_mode))
return FALSE;
*length_return = mystat.st_size;
return TRUE;
}
/* stat() all segments and add up their sizes */
*length_return = 0;
strcpy(prefix, path);
c = strrchr(prefix, '.');
if(c)
{ suffix = c+1;
*c = 0;
}
for(i=0; i<MAX_FILE_SEGMENTS; i++)
{ gchar *cp_path;
int result;
if(!suffix) g_sprintf(name, "%s%02d", prefix, i);
else g_sprintf(name, "%s%02d.%s", prefix,i,suffix);
cp_path = os_path(name);
if(!cp_path) return FALSE;
result = stat(cp_path, &mystat);
g_free(cp_path);
if( result == -1)
return i != 0;
else if(!S_ISREG(mystat.st_mode))
return FALSE;
else *length_return += mystat.st_size;
}
return TRUE;
}
/*
* Stat() variant for testing directories
*/
int DirStat(char *path)
{ struct stat mystat;
gchar *cp_path = os_path(path);
if(!cp_path) return FALSE;
if(stat(cp_path, &mystat) == -1)
{ g_free(cp_path);
return FALSE;
}
g_free(cp_path);
if(!S_ISDIR(mystat.st_mode))
return FALSE;
return TRUE;
}
/*
* Open a file
*/
LargeFile* LargeOpen(char *name, int flags, mode_t mode)
{ LargeFile *lf = g_malloc0(sizeof(LargeFile));
struct stat mystat;
gchar *cp_path;
char *c;
#ifdef HAVE_O_LARGEFILE
flags |= O_LARGEFILE;
#endif
#ifdef SYS_MINGW
flags |= O_BINARY;
#endif
/* Unsplit file case */
if(!Closure->splitFiles)
{ cp_path = os_path(name);
if(!cp_path) return FALSE;
/* Do not try to open directories etc. */
if( (stat(cp_path, &mystat) == 0)
&& !S_ISREG(mystat.st_mode))
{ g_free(cp_path), g_free(lf); return NULL;
}
lf->fileSegment[0] = open(cp_path, flags, mode);
g_free(cp_path);
if(lf->fileSegment[0] == -1)
{ g_free(lf); return NULL;
}
LargeStat(name, &lf->size); /* Do NOT use cp_path! */
return lf;
}
/* Prepare for using split files.
* Note that we're only trying to open the first segment,
* so a failure condition of LargeOpen() is weaker for segmented
* files than for the single file case.
*/
lf->flags = flags;
if(lf->flags & (O_RDWR | O_WRONLY)) /* these imply O_CREAT here to create */
lf->flags |= O_CREAT; /* the additional file segments */
lf->mode = mode;
lf->namelen = strlen(name+3);
lf->basename = g_strdup(name);
c = strrchr(lf->basename, '.');
if(c)
{ lf->suffix = c+1;
*c = 0;
}
cp_path = os_path(name);
if(!cp_path) return NULL;
if( (stat(cp_path, &mystat) == 0)
&& !S_ISREG(mystat.st_mode))
{ g_free(cp_path); g_free(lf); return NULL;
}
g_free(cp_path);
if(!open_segment(lf, 0))
{ g_free(lf); return NULL;
}
LargeStat(name, &lf->size);
return lf;
}
/*
* Seeking in large files.
* Note: Seeking beyond the end of a split file is undefined.
*/
int LargeSeek(LargeFile *lf, gint64 pos)
{
/* Unsplit file case */
if(!Closure->splitFiles)
{ lf->offset = pos;
if(lseek(lf->fileSegment[0], pos, SEEK_SET) != pos)
return FALSE;
}
/* Split file case */
else
{ gint64 seg = pos / MAX_FILE_SIZE;
gint64 segpos = pos - seg * MAX_FILE_SIZE;
if(seg >= MAX_FILE_SEGMENTS) /* Hit the maximum segment limit? */
{ PrintLog("LargeSeek(\"%s*\", %lld [%d:%d]) out of file descriptors\n",
lf->basename, pos, seg, segpos);
return FALSE;
}
/* Open the respective segment */
if(!lf->fileSegment[seg])
if(!open_segment(lf, seg))
{ PrintLog("LargeSeek(\"%s*\", %lld [%d:%d]) failed opening segment\n",
lf->basename, pos, seg, segpos);
return FALSE;
}
/* lseek() within the segment */
if(lseek(lf->fileSegment[seg], segpos, SEEK_SET) != segpos)
{ PrintLog("LargeSeek(\"%s*\", %lld [%d:%d]) failed seeking in segment\n",
lf->basename, pos, seg, segpos);
return FALSE;
}
/* remember segment and offset within */
lf->segment = seg;
lf->offset = segpos;
}
return TRUE;
}
/*
* EOF predicate for large files.
*
* Note: Works only correctly for read only files!
*/
int LargeEOF(LargeFile *lf)
{ int filepos;
if(!Closure->splitFiles)
filepos = lf->offset;
else filepos = MAX_FILE_SIZE * lf->segment + lf->offset;
return filepos >= lf->size;
}
/*
* Reading in segmented files
*/
ssize_t LargeRead(LargeFile *lf, void *buf, size_t count)
{ ssize_t n;
/* Simple unsegmented case */
if(!Closure->splitFiles)
{ n = read(lf->fileSegment[0], buf, count);
lf->offset += n;
return n;
}
/* Segmented file case; open first segment if necessary */
if(!lf->fileSegment[lf->segment])
if(!open_segment(lf, lf->segment))
return -1;
/* If buffer does not cross a segment boundary,
simply read from the current segment and return */
if(lf->offset + count <= MAX_FILE_SIZE)
{ n = read(lf->fileSegment[lf->segment], buf, count);
lf->offset += n;
/* If the segment boundary was touched,
wrap to next segment */
if(lf->offset >= MAX_FILE_SIZE)
{ lf->offset = 0;
lf->segment++;
if(lf->fileSegment[lf->segment] && lseek(lf->fileSegment[lf->segment], 0, SEEK_SET) != 0)
{ PrintLog("LargeRead(\"%s*\", ...) failed wrapping to next segment\n",
lf->segment);
return -1;
}
}
return n;
}
/* Read is spread over two or more segments */
else
{ /* Handle portion coming from current segment */
size_t first = MAX_FILE_SIZE - lf->offset;
size_t chunk = 0;
size_t read_in = 0;
n = read(lf->fileSegment[lf->segment], buf, first);
lf->offset += n;
if(n != first) return n;
count -= n;
/* Handle remainder which comes from the next segments */
while(count > 0)
{
/* Open next segment */
lf->segment++;
if(!lf->fileSegment[lf->segment])
{ if(!open_segment(lf, lf->segment))
return -1;
}
else
{ if(lseek(lf->fileSegment[lf->segment], 0, SEEK_SET) != 0)
{ PrintLog("LargeRead(\"%s*\", ...) failed switching to next segment\n",
lf->segment);
return n;
}
}
chunk = count > MAX_FILE_SIZE ? MAX_FILE_SIZE : count;
read_in = read(lf->fileSegment[lf->segment], buf+n, chunk);
n += read_in;
count -= read_in;
if(read_in != chunk) return n;
}
/* If the segment boundary was touched, wrap to next segment */
lf->offset = read_in;
if(lf->offset >= MAX_FILE_SIZE)
{ lf->offset = 0;
lf->segment++;
if(lf->fileSegment[lf->segment] && lseek(lf->fileSegment[lf->segment], 0, SEEK_SET) != 0)
{ PrintLog("LargeRead(\"%s*\", ...) failed wrapping to next segment\n",
lf->segment);
return -1;
}
}
return n;
}
}
/*
* Writing in segmented files
*/
static void insert_buttons(GtkDialog *dialog)
{
gtk_dialog_add_buttons(dialog,
GTK_STOCK_REDO , 1,
GTK_STOCK_CANCEL, 0, NULL);
}
static ssize_t xwrite(int fdes, void *buf_base, size_t count)
{ unsigned char *buf = (unsigned char*)buf_base;
ssize_t total = 0;
/* Simply fail when going out of space in command line mode */
if(!Closure->guiMode)
{ while(count)
{ ssize_t n = write(fdes, buf, count);
if(n<=0) return total; /* error occurred */
if(n>0) /* write at least partially successful */
{ total += n;
count -= n;
buf += n;
}
}
return total;
}
/* Give the user a chance to free more space in GUI mode.
When running out of space, the last write() may complete
with n<count but no error condition, so we try writing
until a real error hits (n = -1). */
while(count)
{ ssize_t n = write(fdes, buf, count);
if(n <= 0) /* error occurred */
{ int answer;
if(errno != ENOSPC) return total;
answer = ModalDialog(GTK_MESSAGE_QUESTION, GTK_BUTTONS_NONE, insert_buttons,
_("Error while writing the file:\n\n%s\n\n"
"You can redo this operation after freeing some space."),
strerror(errno),n);
if(!answer) return total;
}
if(n>0) /* write at least partially successful */
{ total += n;
count -= n;
buf += n;
}
}
return total;
}
ssize_t LargeWrite(LargeFile *lf, void *buf, size_t count)
{ ssize_t n;
/* Simple unsegmented case */
if(!Closure->splitFiles)
{ n = xwrite(lf->fileSegment[0], buf, count);
lf->offset += n;
return n;
}
/* Segmented file case; open first segment if necessary */
if(!lf->fileSegment[lf->segment])
if(!open_segment(lf, lf->segment))
return -1;
/* If buffer does not cross a segment boundary,
simply write it to the current segment and return */
if(lf->offset + count <= MAX_FILE_SIZE)
{ n = xwrite(lf->fileSegment[lf->segment], buf, count);
lf->offset += n;
/* If the segment boundary was touched,
wrap to next segment */
if(lf->offset >= MAX_FILE_SIZE)
{ lf->offset = 0;
lf->segment++;
if(lf->fileSegment[lf->segment] && lseek(lf->fileSegment[lf->segment], 0, SEEK_SET) != 0)
{ PrintLog("LargeWrite(\"%s*\", ...) failed wrapping to next segment\n",
lf->segment);
return -1;
}
}
return n;
}
/* Write is spread over two or more segments */
else
{ /* Handle portion going to current segment */
size_t first = MAX_FILE_SIZE - lf->offset;
size_t chunk = 0;
size_t written = 0;
n = xwrite(lf->fileSegment[lf->segment], buf, first);
lf->offset += n;
if(n != first) return n;
count -= n;
/* Handle remainder which goes into the next segments */
while(count > 0)
{
/* Open next segment */
lf->segment++;
if(!lf->fileSegment[lf->segment])
{ if(!open_segment(lf, lf->segment))
return -1;
}
else
{ if(lseek(lf->fileSegment[lf->segment], 0, SEEK_SET) != 0)
{ PrintLog("LargeWrite(\"%s*\", ...) failed switching to next segment\n",
lf->segment);
return n;
}
}
chunk = count > MAX_FILE_SIZE ? MAX_FILE_SIZE : count;
written = xwrite(lf->fileSegment[lf->segment], buf+n, chunk);
n += written;
count -= written;
if(written != chunk) return n;
}
/* If the segment boundary was touched, wrap to next segment */
lf->offset = written;
if(lf->offset >= MAX_FILE_SIZE)
{ lf->offset = 0;
lf->segment++;
if(lf->fileSegment[lf->segment] && lseek(lf->fileSegment[lf->segment], 0, SEEK_SET) != 0)
{ PrintLog("LargeWrite(\"%s*\", ...) failed wrapping to next segment\n",
lf->segment);
return -1;
}
}
return n;
}
}
/*
* Large file closing
*/
int LargeClose(LargeFile *lf)
{ int result = TRUE;
/* Simple unsegmented case */
if(!Closure->splitFiles)
result = (close(lf->fileSegment[0]) == 0);
/* Segmented case */
else
{ int i;
for(i=0; i<MAX_FILE_SEGMENTS; i++)
{ if(lf->fileSegment[i] && close(lf->fileSegment[i]) != 0)
{ result = FALSE;
PrintLog("LargeClose(\"%s*\") failed closing segment %d\n", lf->basename, i);
}
}
}
/* Free the LargeFile struct and return results */
if(lf->basename) g_free(lf->basename);
g_free(lf);
return result;
}
/*
* Large file truncation
*/
int LargeTruncate(LargeFile *lf, gint64 length)
{
/* Simple unsegmented case */
if(!Closure->splitFiles)
return large_ftruncate(lf->fileSegment[0], length) == 0;
/* Segmented case; first truncate the last segment */
else
{ gint64 seg = length / MAX_FILE_SIZE;
gint64 seglen = length - seg * MAX_FILE_SIZE;
int i;
if(!lf->fileSegment[seg])
{ if(!open_segment(lf, seg))
return FALSE;
}
if(large_ftruncate(lf->fileSegment[seg], seglen) != 0)
return FALSE;
/* In case of large truncation, close and delete excess segments */
for(i=seg+1; i<MAX_FILE_SEGMENTS; i++)
{ char name[lf->namelen];
gchar *cp_path;
close(lf->fileSegment[i]); /* no need for error testing */
if(!lf->suffix) g_sprintf(name, "%s%02d", lf->basename, i);
else g_sprintf(name, "%s%02d.%s", lf->basename, i, lf->suffix);
cp_path = os_path(name);
unlink(cp_path);
g_free(cp_path);
}
}
return TRUE;
}
/*
* Large file unlinking
*/
int LargeUnlink(char *path)
{ char name[strlen(path)+3];
char prefix[strlen(path)+1];
char *suffix = NULL, *c;
gchar *cp_path;
int i;
/* Simple unsegmented case */
if(!Closure->splitFiles)
{ int result;
cp_path = os_path(path);
if(!cp_path) return FALSE;
result = unlink(cp_path);
g_free(cp_path);
return result == 0;
}
/* Segmented case. This will unlink name00..name99 */
strcpy(prefix, path);
c = strrchr(prefix, '.');
if(c)
{ suffix = c+1;
*c = 0;
}
for(i=0; i<MAX_FILE_SEGMENTS; i++)
{ int result;
if(!suffix) g_sprintf(name, "%s%02d", prefix, i);
else g_sprintf(name, "%s%02d.%s", prefix, i, suffix);
cp_path = os_path(name);
if(!cp_path) return FALSE;
result = unlink(cp_path);
g_free(cp_path);
if(result == -1)
return i != 0;
}
return TRUE;
}
/***
*** Wrappers around other IO
***/
FILE *portable_fopen(char *path, char *modes)
{ FILE *file;
char *cp_path;
cp_path = os_path(path);
file = fopen(cp_path, modes);
g_free(cp_path);
return file;
}
#ifdef SYS_MINGW
int portable_mkdir(char *path)
{ int status;
char *cp_path;
cp_path = os_path(path);
status = mkdir(cp_path);
g_free(cp_path);
return status;
}
#endif