From: Marijn van Vliet Date: Sat, 18 Jun 2011 20:55:23 +0000 (+0200) Subject: Merge branch 'master' of ssh://git.stderr.nl/projects/chimara/chimara X-Git-Tag: v0.9~73^2~1 X-Git-Url: https://git.stderr.nl/gitweb?a=commitdiff_plain;h=7eda771842a214ee6c9b60aee0a982253fcc29e6;hp=6ad6d826dfa832f43fbba11e8bcb811a5938c108;p=projects%2Fchimara%2Fchimara.git Merge branch 'master' of ssh://git.stderr.nl/projects/chimara/chimara --- diff --git a/libchimara/stream.c b/libchimara/stream.c index a161a4b..96ab73f 100644 --- a/libchimara/stream.c +++ b/libchimara/stream.c @@ -284,6 +284,7 @@ glk_stream_open_memory(char *buf, glui32 buflen, glui32 fmode, glui32 rock) str->file_mode = fmode; str->type = STREAM_TYPE_MEMORY; str->mark = 0; + str->endmark = 0; str->unicode = FALSE; if(buf && buflen) @@ -322,6 +323,7 @@ glk_stream_open_memory_uni(glui32 *buf, glui32 buflen, glui32 fmode, glui32 rock str->file_mode = fmode; str->type = STREAM_TYPE_MEMORY; str->mark = 0; + str->endmark = 0; str->unicode = TRUE; if(buf && buflen) @@ -342,7 +344,7 @@ file_stream_new(frefid_t fileref, glui32 fmode, glui32 rock, gboolean unicode) { VALID_FILEREF(fileref, return NULL); - gchar *modestr; + const gchar *modestr; /* Binary mode is 0x000, text mode 0x100 */ gboolean binary = !(fileref->usage & fileusage_TextMode); switch(fmode) @@ -352,20 +354,23 @@ file_stream_new(frefid_t fileref, glui32 fmode, glui32 rock, gboolean unicode) ILLEGAL_PARAM("Tried to open a nonexistent file, '%s', in read mode", fileref->filename); return NULL; } - modestr = g_strdup(binary? "rb" : "r"); + modestr = binary? "rb" : "r"; break; case filemode_Write: - modestr = g_strdup(binary? "wb" : "w"); + modestr = binary? "wb" : "w"; break; case filemode_WriteAppend: - modestr = g_strdup(binary? "ab" : "a"); - break; case filemode_ReadWrite: - if( g_file_test(fileref->filename, G_FILE_TEST_EXISTS) ) { - modestr = g_strdup(binary? "r+b" : "r+"); - } else { - modestr = g_strdup(binary? "w+b" : "w+"); + { + /* We have to open the file first and then close it, in order to + both make sure it exists and be able to seek in it later */ + FILE *fp = g_fopen(fileref->filename, binary? "ab" : "a"); + if(fclose(fp) != 0) { + IO_WARNING( "Error opening file", fileref->filename, g_strerror(errno) ); + return NULL; } + modestr = binary? "r+b" : "r+"; + } break; default: ILLEGAL_PARAM("Invalid file mode: %u", fmode); @@ -373,12 +378,17 @@ file_stream_new(frefid_t fileref, glui32 fmode, glui32 rock, gboolean unicode) } FILE *fp = g_fopen(fileref->filename, modestr); - g_free(modestr); if(fp == NULL) { IO_WARNING( "Error opening file", fileref->filename, g_strerror(errno) ); return NULL; } + /* Fast-forward to the end if we are appending */ + if(fmode == filemode_WriteAppend && fseek(fp, 0, SEEK_END) != 0) { + IO_WARNING("Error fast-forwarding file to end", fileref->filename, g_strerror(errno)); + return NULL; + } + /* If they opened a file in write mode but didn't specifically get permission to do so, complain if the file already exists */ if(fileref->orig_filemode == filemode_Read && fmode != filemode_Read) { diff --git a/libchimara/stream.h b/libchimara/stream.h index b60e022..7ff7618 100644 --- a/libchimara/stream.h +++ b/libchimara/stream.h @@ -42,12 +42,14 @@ struct glk_stream_struct gchar *buffer; glui32 *ubuffer; glui32 mark; + glui32 endmark; glui32 buflen; gidispatch_rock_t buffer_rock; /* Specific to file streams */ FILE *file_pointer; gboolean binary; gchar *filename; /* Displayable filename in UTF-8 for error handling */ + glui32 lastop; /* 0, filemode_Write, or filemode_Read */ gchar *style; /* Name of the current style */ gchar *glk_style; /* Name of the current glk style override */ diff --git a/libchimara/strio.c b/libchimara/strio.c index 691773d..b0d0fd1 100644 --- a/libchimara/strio.c +++ b/libchimara/strio.c @@ -7,6 +7,23 @@ #include #include +/* Internal function: ensure that an fseek() is called on a file pointer in + between reading and writing operations, and vice versa. This will only come up + for ReadWrite or WriteAppend files. */ +static void +ensure_file_operation(strid_t str, glui32 op) +{ + if(str->lastop != 0 && str->lastop != op) + { + long pos = ftell(str->file_pointer); + if(pos == -1) + WARNING_S("ftell() failed", g_strerror(errno)); + if(fseek(str->file_pointer, pos, SEEK_SET) != 0) + WARNING_S("fseek() failed", g_strerror(errno)); + } + str->lastop = op; /* Not 0, because we are about to do the operation anyway */ +} + /* * **************** WRITING FUNCTIONS ******************************************** @@ -293,6 +310,10 @@ write_buffer_to_stream(strid_t str, gchar *buf, glui32 len) str->mark += copycount; } + /* Move the EOF marker if we wrote past it */ + if(str->mark > str->endmark) + str->endmark = str->mark; + str->write_count += len; break; @@ -302,11 +323,13 @@ write_buffer_to_stream(strid_t str, gchar *buf, glui32 len) if(str->unicode) { gchar *writebuffer = convert_latin1_to_ucs4be_string(buf, len); + ensure_file_operation(str, filemode_Write); fwrite(writebuffer, sizeof(gchar), len * 4, str->file_pointer); g_free(writebuffer); } else /* Regular file */ { + ensure_file_operation(str, filemode_Write); fwrite(buf, sizeof(gchar), len, str->file_pointer); } } @@ -315,6 +338,7 @@ write_buffer_to_stream(strid_t str, gchar *buf, glui32 len) gchar *utf8 = convert_latin1_to_utf8(buf, len); if(utf8 != NULL) { + ensure_file_operation(str, filemode_Write); g_fprintf(str->file_pointer, "%s", utf8); g_free(utf8); } @@ -382,6 +406,10 @@ write_buffer_to_stream_uni(strid_t str, glui32 *buf, glui32 len) str->mark += copycount; } + /* Move the EOF marker if we wrote past it */ + if(str->mark > str->endmark) + str->endmark = str->mark; + str->write_count += len; break; @@ -391,12 +419,14 @@ write_buffer_to_stream_uni(strid_t str, glui32 *buf, glui32 len) if(str->unicode) { gchar *writebuffer = convert_ucs4_to_ucs4be_string(buf, len); + ensure_file_operation(str, filemode_Write); fwrite(writebuffer, sizeof(gchar), len * 4, str->file_pointer); g_free(writebuffer); } else /* Regular file */ { gchar *latin1 = convert_ucs4_to_latin1_binary(buf, len); + ensure_file_operation(str, filemode_Write); fwrite(latin1, sizeof(gchar), len, str->file_pointer); g_free(latin1); } @@ -406,6 +436,7 @@ write_buffer_to_stream_uni(strid_t str, glui32 *buf, glui32 len) gchar *utf8 = convert_ucs4_to_utf8(buf, len); if(utf8 != NULL) { + ensure_file_operation(str, filemode_Write); g_fprintf(str->file_pointer, "%s", utf8); g_free(utf8); } @@ -554,10 +585,11 @@ glk_put_buffer_stream_uni(strid_t str, glui32 *buf, glui32 len) /* Internal function: Read one big-endian four-byte character from file fp and return it as a Unicode code point, or -1 on EOF */ static glsi32 -read_ucs4be_char_from_file(FILE *fp) +read_ucs4be_char_from_file(strid_t str) { unsigned char readbuffer[4]; - if(fread(readbuffer, sizeof(unsigned char), 4, fp) < 4) + ensure_file_operation(str, filemode_Read); + if(fread(readbuffer, sizeof(unsigned char), 4, str->file_pointer) < 4) return -1; /* EOF */ return readbuffer[0] << 24 | @@ -569,14 +601,15 @@ read_ucs4be_char_from_file(FILE *fp) /* Internal function: Read one UTF-8 character, which may be more than one byte, from file fp and return it as a Unicode code point, or -1 on EOF */ static glsi32 -read_utf8_char_from_file(FILE *fp) +read_utf8_char_from_file(strid_t str) { gchar readbuffer[4] = {0, 0, 0, 0}; /* Max UTF-8 width */ int foo; gunichar charresult = (gunichar)-2; + ensure_file_operation(str, filemode_Read); for(foo = 0; foo < 4 && charresult == (gunichar)-2; foo++) { - int ch = fgetc(fp); + int ch = fgetc(str->file_pointer); if(ch == EOF) return -1; readbuffer[foo] = (gchar)ch; @@ -595,16 +628,18 @@ file pointer and eight-bit flag are included in case the newline is a CR (U+000D). If the next character is LF (U+000A) then it also belongs to the newline. */ static gboolean -is_unicode_newline(glsi32 ch, FILE *fp, gboolean utf8) +is_unicode_newline(glsi32 ch, strid_t str, gboolean utf8) { if(ch == 0x0A || ch == 0x85 || ch == 0x0C || ch == 0x2028 || ch == 0x2029) return TRUE; if(ch == 0x0D) { - glsi32 ch2 = utf8? read_utf8_char_from_file(fp) : - read_ucs4be_char_from_file(fp); - if(ch2 != 0x0A) - if(fseek(fp, utf8? -1 : -4, SEEK_CUR) == -1); + glsi32 ch2 = utf8? read_utf8_char_from_file(str) : + read_ucs4be_char_from_file(str); + if(ch2 != 0x0A) { + if(fseek(str->file_pointer, utf8? -1 : -4, SEEK_CUR) == -1); WARNING_S("Seek failed on stream", g_strerror(errno) ); + str->lastop = 0; /* can read or write after a seek */ + } return TRUE; } return FALSE; @@ -642,7 +677,7 @@ get_char_stream_common(strid_t str) { if(str->unicode) { - glsi32 ch = read_ucs4be_char_from_file(str->file_pointer); + glsi32 ch = read_ucs4be_char_from_file(str); if(ch == -1) return -1; str->read_count++; @@ -650,6 +685,7 @@ get_char_stream_common(strid_t str) } else /* Regular file */ { + ensure_file_operation(str, filemode_Read); int ch = fgetc(str->file_pointer); if(ch == EOF) return -1; @@ -660,7 +696,7 @@ get_char_stream_common(strid_t str) } else /* Text mode is the same for Unicode and regular files */ { - glsi32 ch = read_utf8_char_from_file(str->file_pointer); + glsi32 ch = read_utf8_char_from_file(str); if(ch == -1) return -1; @@ -778,6 +814,7 @@ glk_get_buffer_stream(strid_t str, char *buf, glui32 len) { /* Read len characters of 4 bytes each */ unsigned char *readbuffer = g_new0(unsigned char, 4 * len); + ensure_file_operation(str, filemode_Read); size_t count = fread(readbuffer, sizeof(unsigned char), 4 * len, str->file_pointer); /* If there was an incomplete character */ if(count % 4 != 0) @@ -801,6 +838,7 @@ glk_get_buffer_stream(strid_t str, char *buf, glui32 len) } else /* Regular binary file */ { + ensure_file_operation(str, filemode_Read); size_t count = fread(buf, sizeof(char), len, str->file_pointer); str->read_count += count; return count; @@ -812,7 +850,7 @@ glk_get_buffer_stream(strid_t str, char *buf, glui32 len) int foo; for(foo = 0; foo < len; foo++) { - glsi32 ch = read_utf8_char_from_file(str->file_pointer); + glsi32 ch = read_utf8_char_from_file(str); if(ch == -1) break; str->read_count++; @@ -875,6 +913,7 @@ glk_get_buffer_stream_uni(strid_t str, glui32 *buf, glui32 len) { /* Read len characters of 4 bytes each */ unsigned char *readbuffer = g_new0(unsigned char, 4 * len); + ensure_file_operation(str, filemode_Read); size_t count = fread(readbuffer, sizeof(unsigned char), 4 * len, str->file_pointer); /* If there was an incomplete character */ if(count % 4 != 0) @@ -896,6 +935,7 @@ glk_get_buffer_stream_uni(strid_t str, glui32 *buf, glui32 len) else /* Regular binary file */ { unsigned char *readbuffer = g_new0(unsigned char, len); + ensure_file_operation(str, filemode_Read); size_t count = fread(readbuffer, sizeof(unsigned char), len, str->file_pointer); int foo; for(foo = 0; foo < count; foo++) @@ -911,7 +951,7 @@ glk_get_buffer_stream_uni(strid_t str, glui32 *buf, glui32 len) int foo; for(foo = 0; foo < len; foo++) { - glsi32 ch = read_utf8_char_from_file(str->file_pointer); + glsi32 ch = read_utf8_char_from_file(str); if(ch == -1) break; str->read_count++; @@ -937,7 +977,7 @@ glk_get_buffer_stream_uni(strid_t str, glui32 *buf, glui32 len) * @len - 1 * * characters have been read or a newline has been read. It then puts a - * terminal null ('\0') aracter on + * terminal null ('\0') character on * the end. It returns the number of characters actually read, including the * newline (if there is one) but not including the terminal null. * @@ -999,29 +1039,30 @@ glk_get_line_stream(strid_t str, char *buf, glui32 len) if(str->unicode) /* Binary file with 4-byte characters */ { /* Do it character-by-character */ - int foo; - for(foo = 0; foo < len - 1; foo++) + int copycount; + for(copycount = 0; copycount < len - 1; copycount++) { - glsi32 ch = read_ucs4be_char_from_file(str->file_pointer); + glsi32 ch = read_ucs4be_char_from_file(str); if(ch == -1) { - buf[foo] = '\0'; - return foo - 1; + buf[copycount] = '\0'; + return copycount; } str->read_count++; - if(is_unicode_newline(ch, str->file_pointer, FALSE)) + if(is_unicode_newline(ch, str, FALSE)) { - buf[foo] = '\n'; - buf[foo + 1] = '\0'; - return foo; + buf[copycount++] = '\n'; + buf[copycount] = '\0'; + return copycount; } - buf[foo] = (ch > 0xFF)? '?' : (char)ch; + buf[copycount] = (ch > 0xFF)? '?' : (char)ch; } buf[len] = '\0'; - return foo; + return copycount; } else /* Regular binary file */ { + ensure_file_operation(str, filemode_Read); if( !fgets(buf, len, str->file_pointer) ) { *buf = 0; return 0; @@ -1038,14 +1079,14 @@ glk_get_line_stream(strid_t str, char *buf, glui32 len) int foo; for(foo = 0; foo < len - 1; foo++) { - glsi32 ch = read_utf8_char_from_file(str->file_pointer); + glsi32 ch = read_utf8_char_from_file(str); if(ch == -1) { buf[foo] = '\0'; return foo - 1; } str->read_count++; - if(is_unicode_newline(ch, str->file_pointer, TRUE)) + if(is_unicode_newline(ch, str, TRUE)) { buf[foo] = '\n'; buf[foo + 1] = '\0'; @@ -1141,30 +1182,31 @@ glk_get_line_stream_uni(strid_t str, glui32 *buf, glui32 len) if(str->unicode) /* Binary file with 4-byte characters */ { /* Do it character-by-character */ - int foo; - for(foo = 0; foo < len - 1; foo++) + int copycount; + for(copycount = 0; copycount < len - 1; copycount++) { - glsi32 ch = read_ucs4be_char_from_file(str->file_pointer); + glsi32 ch = read_ucs4be_char_from_file(str); if(ch == -1) { - buf[foo] = 0; - return foo - 1; + buf[copycount] = 0; + return copycount; } str->read_count++; - if(is_unicode_newline(ch, str->file_pointer, FALSE)) + if(is_unicode_newline(ch, str, FALSE)) { - buf[foo] = ch; /* Preserve newline types??? */ - buf[foo + 1] = 0; - return foo; + buf[copycount++] = ch; /* Preserve newline types??? */ + buf[copycount] = 0; + return copycount; } - buf[foo] = ch; + buf[copycount] = ch; } buf[len] = 0; - return foo; + return copycount; } else /* Regular binary file */ { gchar *readbuffer = g_new0(gchar, len); + ensure_file_operation(str, filemode_Read); if( !fgets(readbuffer, len, str->file_pointer) ) { *buf = 0; return 0; @@ -1184,14 +1226,14 @@ glk_get_line_stream_uni(strid_t str, glui32 *buf, glui32 len) int foo; for(foo = 0; foo < len - 1; foo++) { - glsi32 ch = read_utf8_char_from_file(str->file_pointer); + glsi32 ch = read_utf8_char_from_file(str); if(ch == -1) { buf[foo] = 0; return foo - 1; } str->read_count++; - if(is_unicode_newline(ch, str->file_pointer, TRUE)) + if(is_unicode_newline(ch, str, TRUE)) { buf[foo] = ch; /* Preserve newline types??? */ buf[foo + 1] = 0; @@ -1309,7 +1351,7 @@ glk_stream_set_position(strid_t str, glsi32 pos, glui32 seekmode) { case seekmode_Start: str->mark = pos; break; case seekmode_Current: str->mark += pos; break; - case seekmode_End: str->mark = str->buflen + pos; break; + case seekmode_End: str->mark = str->endmark + pos; break; default: g_return_if_reached(); return; @@ -1329,6 +1371,7 @@ glk_stream_set_position(strid_t str, glsi32 pos, glui32 seekmode) } if(fseek(str->file_pointer, pos, whence) == -1) WARNING("Seek failed on file stream"); + str->lastop = 0; /* Either reading or writing is legal after fseek() */ break; } case STREAM_TYPE_WINDOW: diff --git a/tests/Makefile.am b/tests/Makefile.am index f2f1e87..6d12176 100644 --- a/tests/Makefile.am +++ b/tests/Makefile.am @@ -30,7 +30,7 @@ test_close_CFLAGS = @TEST_CFLAGS@ $(AM_CFLAGS) test_close_LDADD = @TEST_LIBS@ $(top_builddir)/libchimara/libchimara.la noinst_LTLIBRARIES = first.la model.la gridtest.la splittest.la multiwin.la \ - styletest.la soundtest.la test-userstyle.la + styletest.la soundtest.la test-userstyle.la fileio.la first_la_SOURCES = first.c first_la_LDFLAGS = $(TEST_PLUGIN_LIBTOOL_FLAGS) @@ -56,5 +56,9 @@ test_userstyle_la_LDFLAGS = $(TEST_PLUGIN_LIBTOOL_FLAGS) soundtest_la_SOURCES = soundtest.c soundtest_la_LDFLAGS = $(TEST_PLUGIN_LIBTOOL_FLAGS) +fileio_la_SOURCES = fileio.c +fileio_la_CFLAGS = @TEST_CFLAGS@ $(AM_CFLAGS) +fileio_la_LDFLAGS = $(TEST_PLUGIN_LIBTOOL_FLAGS) + -include $(top_srcdir)/git.mk diff --git a/tests/fileio.c b/tests/fileio.c new file mode 100644 index 0000000..d96a734 --- /dev/null +++ b/tests/fileio.c @@ -0,0 +1,101 @@ +/* Test for file I/O bug */ + +#include +#include +#include + +#define MAGIC_STRING "Zapp\xF6licious.\n" +#define BUFLEN 80 + +static void +delete_if_exists(frefid_t ref) +{ + if(glk_fileref_does_file_exist(ref) == 1) { + g_print("(Deleting existing file) "); + glk_fileref_delete_file(ref); + } +} + +void +glk_main(void) +{ + char buffer[BUFLEN + 1]; + + g_print("Test getline... "); + + /* Open a temporary file */ + frefid_t ref = glk_fileref_create_temp(fileusage_Data | fileusage_BinaryMode, 0); + strid_t file = glk_stream_open_file_uni(ref, filemode_Write, 0); + + /* Write the string to the file */ + glk_put_string_stream(file, MAGIC_STRING); + + /* Close and check result counts */ + stream_result_t counts; + glk_stream_close(file, &counts); + g_assert_cmpint(counts.readcount, ==, 0); + g_assert_cmpint(counts.writecount, ==, 14); + + file = glk_stream_open_file_uni(ref, filemode_Read, 0); + glui32 readcount = glk_get_line_stream(file, buffer, BUFLEN); + g_print("(String: %s) ", buffer); + g_assert_cmpint(readcount, ==, strlen(buffer)); + + g_print("PASS\n"); + + glk_stream_close(file, &counts); + glk_fileref_destroy(ref); + + /* testfile7 - append, seek, write, close, read. */ + g_print("Test append-seek-write-close-read... "); + + ref = glk_fileref_create_by_name(fileusage_Data | fileusage_BinaryMode, "testfile7", 0); + delete_if_exists(ref); + strid_t str = glk_stream_open_file(ref, filemode_WriteAppend, 0); + glk_put_string_stream(str, "Purple monkey chef.\n"); + glk_stream_set_position(str, 14, seekmode_Start); + g_assert_cmpuint(glk_stream_get_position(str), ==, 14); + glk_put_string_stream(str, "dishwasher.\n"); + glk_stream_close(str, &counts); + g_assert_cmpuint(counts.readcount, ==, 0); + g_assert_cmpuint(counts.writecount, ==, 32); + + str = glk_stream_open_file(ref, filemode_Read, 0); + readcount = glk_get_buffer_stream(str, buffer, BUFLEN); + buffer[readcount] = '\0'; + g_assert_cmpstr(buffer, ==, "Purple monkey dishwasher.\n"); + + glk_stream_close(str, &counts); + g_assert_cmpuint(counts.readcount, ==, 26); + g_assert_cmpuint(counts.writecount, ==, 0); + + g_print("PASS\n"); + + /* testfile10 - Write, close, read, write, close, read. */ + g_print("Test write-close-read-write-close-read... "); + + ref = glk_fileref_create_by_name(fileusage_Data | fileusage_BinaryMode, "testfile10", 0); + delete_if_exists(ref); + str = glk_stream_open_file(ref, filemode_ReadWrite, 0); + glk_put_string_stream(str, "Purple synchroscopes.\n"); + glk_stream_close(str, &counts); + g_assert_cmpuint(counts.readcount, ==, 0); + g_assert_cmpuint(counts.writecount, ==, 22); + + str = glk_stream_open_file(ref, filemode_ReadWrite, 0); + readcount = glk_get_buffer_stream(str, buffer, 7); + buffer[readcount] = '\0'; + g_assert_cmpstr(buffer, ==, "Purple "); + g_assert_cmpuint(glk_stream_get_position(str), ==, 7); + glk_put_string_stream(str, "monkey dishwasher.\n"); + glk_stream_set_position(str, 0, seekmode_Start); + g_assert_cmpuint(glk_stream_get_position(str), ==, 0); + readcount = glk_get_buffer_stream(str, buffer, BUFLEN); + buffer[readcount] = '\0'; + g_assert_cmpstr(buffer, ==, "Purple monkey dishwasher.\n"); + glk_stream_close(str, &counts); + g_assert_cmpuint(counts.readcount, ==, 33); + g_assert_cmpuint(counts.writecount, ==, 19); + + g_print("PASS\n"); +} diff --git a/tests/plugin-loader.c b/tests/plugin-loader.c index 0915b3c..24a9889 100644 --- a/tests/plugin-loader.c +++ b/tests/plugin-loader.c @@ -61,7 +61,6 @@ create_window(void) g_signal_connect(window, "delete-event", G_CALLBACK(quit), NULL); glk = chimara_glk_new(); g_object_ref(glk); - g_signal_connect(glk, "stopped", G_CALLBACK(gtk_main_quit), NULL); gtk_container_add(GTK_CONTAINER(window), glk); }