From: Michihiro NAKAJIMA Date: Mon, 21 Nov 2011 04:20:53 +0000 (-0500) Subject: Add wchar_t filename support for archive_{read,write}_open_filename(). X-Git-Tag: v3.0.1b~32 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=87cc7b92eb771b1dafa8b070d16370ed85516136;p=thirdparty%2Flibarchive.git Add wchar_t filename support for archive_{read,write}_open_filename(). It would be useful for an internationalization programing on Windows. SVN-Revision: 3825 --- diff --git a/libarchive/archive.h b/libarchive/archive.h index 4858a6266..c0bb26d74 100644 --- a/libarchive/archive.h +++ b/libarchive/archive.h @@ -392,6 +392,8 @@ __LA_DECL int archive_read_open2(struct archive *, void *_client_data, /* Use this if you know the filename. Note: NULL indicates stdin. */ __LA_DECL int archive_read_open_filename(struct archive *, const char *_filename, size_t _block_size); +__LA_DECL int archive_read_open_filename_w(struct archive *, + const wchar_t *_filename, size_t _block_size); /* archive_read_open_file() is a deprecated synonym for ..._open_filename(). */ __LA_DECL int archive_read_open_file(struct archive *, const char *_filename, size_t _block_size); @@ -611,6 +613,8 @@ __LA_DECL int archive_write_open(struct archive *, void *, archive_close_callback *); __LA_DECL int archive_write_open_fd(struct archive *, int _fd); __LA_DECL int archive_write_open_filename(struct archive *, const char *_file); +__LA_DECL int archive_write_open_filename_w(struct archive *, + const wchar_t *_file); /* A deprecated synonym for archive_write_open_filename() */ __LA_DECL int archive_write_open_file(struct archive *, const char *_file); __LA_DECL int archive_write_open_FILE(struct archive *, FILE *); diff --git a/libarchive/archive_read_open_filename.c b/libarchive/archive_read_open_filename.c index c06d1a17d..088782dbe 100644 --- a/libarchive/archive_read_open_filename.c +++ b/libarchive/archive_read_open_filename.c @@ -60,6 +60,7 @@ __FBSDID("$FreeBSD: head/lib/libarchive/archive_read_open_filename.c 201093 2009 #endif #include "archive.h" +#include "archive_string.h" #ifndef O_BINARY #define O_BINARY 0 @@ -71,10 +72,16 @@ struct read_file_data { void *buffer; mode_t st_mode; /* Mode bits for opened file. */ char use_lseek; - char filename[1]; /* Must be last! */ + enum fnt_e { FNT_STDIN, FNT_MBS, FNT_WCS } filename_type; + union { + char m[1];/* MBS filename. */ + wchar_t w[1];/* WCS filename. */ + } filename; /* Must be last! */ }; static int file_close(struct archive *, void *); +static int file_open_filename(struct archive *, enum fnt_e, const void *, + size_t); static ssize_t file_read(struct archive *, void *, const void **buff); static int64_t file_seek(struct archive *, void *, int64_t request, int); static int64_t file_skip(struct archive *, void *, int64_t request); @@ -90,10 +97,62 @@ archive_read_open_file(struct archive *a, const char *filename, int archive_read_open_filename(struct archive *a, const char *filename, size_t block_size) +{ + enum fnt_e filename_type; + + if (filename == NULL || filename[0] == '\0') { + filename_type = FNT_STDIN; + } else + filename_type = FNT_MBS; + return (file_open_filename(a, filename_type, filename, block_size)); +} + +int +archive_read_open_filename_w(struct archive *a, const wchar_t *wfilename, + size_t block_size) +{ + enum fnt_e filename_type; + + if (wfilename == NULL || wfilename[0] == L'\0') { + filename_type = FNT_STDIN; + } else { +#if defined(_WIN32) && !defined(__CYGWIN__) + filename_type = FNT_WCS; +#else + /* + * POSIX system does not support a wchar_t interface for + * open() system call, so we have to translate a whcar_t + * filename to multi-byte one and use it. + */ + struct archive_string fn; + int r; + + archive_string_init(&fn); + if (archive_string_append_from_wcs(&fn, wfilename, + wcslen(wfilename)) != 0) { + archive_set_error(a, EINVAL, + "Failed to convert a wide-character filename to" + " a multi-byte filename"); + archive_string_free(&fn); + return (ARCHIVE_FATAL); + } + r = file_open_filename(a, FNT_MBS, fn.s, block_size); + archive_string_free(&fn); + return (r); +#endif + } + return (file_open_filename(a, filename_type, wfilename, block_size)); +} + +static int +file_open_filename(struct archive *a, enum fnt_e filename_type, + const void *_filename, size_t block_size) { struct stat st; struct read_file_data *mine; void *buffer; + const char *filename = NULL; + const wchar_t *wfilename = NULL; int fd; int is_disk_like = 0; #if defined(__FreeBSD__) || defined(__FreeBSD_kernel__) @@ -105,7 +164,7 @@ archive_read_open_filename(struct archive *a, const char *filename, #endif archive_clear_error(a); - if (filename == NULL || filename[0] == '\0') { + if (filename_type == FNT_STDIN) { /* We used to delegate stdin support by * directly calling archive_read_open_fd(a,0,block_size) * here, but that doesn't (and shouldn't) handle the @@ -115,21 +174,49 @@ archive_read_open_filename(struct archive *a, const char *filename, * API is intended to be a little smarter for folks who * want easy handling of the common case. */ - filename = ""; /* Normalize NULL to "" */ fd = 0; #if defined(__CYGWIN__) || defined(_WIN32) setmode(0, O_BINARY); #endif - } else { + filename = ""; + } else if (filename_type == FNT_MBS) { + filename = (const char *)_filename; fd = open(filename, O_RDONLY | O_BINARY); if (fd < 0) { archive_set_error(a, errno, "Failed to open '%s'", filename); return (ARCHIVE_FATAL); } + } else { +#if defined(_WIN32) && !defined(__CYGWIN__) + wfilename = (const wchar_t *)_filename; + fd = _wopen(wfilename, O_RDONLY | O_BINARY); + if (fd < 0 && errno == ENOENT) { + wchar_t *fullpath; + fullpath = __la_win_permissive_name_w(wfilename); + if (fullpath != NULL) { + fd = _wopen(fullpath, O_RDONLY | O_BINARY); + free(fullpath); + } + } + if (fd < 0) { + archive_set_error(a, errno, + "Failed to open '%S'", wfilename); + return (ARCHIVE_FATAL); + } +#else + archive_set_error(a, ARCHIVE_ERRNO_MISC, + "Unexpedted operation in archive_read_open_filename"); + return (ARCHIVE_FATAL); +#endif } if (fstat(fd, &st) != 0) { - archive_set_error(a, errno, "Can't stat '%s'", filename); + if (filename_type == FNT_WCS) + archive_set_error(a, errno, "Can't stat '%S'", + wfilename); + else + archive_set_error(a, errno, "Can't stat '%s'", + filename); return (ARCHIVE_FATAL); } @@ -189,8 +276,12 @@ archive_read_open_filename(struct archive *a, const char *filename, #endif /* TODO: Add an "is_tape_like" variable and appropriate tests. */ - mine = (struct read_file_data *)calloc(1, - sizeof(*mine) + strlen(filename)); + if (filename_type == FNT_WCS) + mine = (struct read_file_data *)calloc(1, + sizeof(*mine) + wcslen(wfilename) * sizeof(wchar_t)); + else + mine = (struct read_file_data *)calloc(1, + sizeof(*mine) + strlen(filename)); /* Disk-like devices prefer power-of-two block sizes. */ /* Use provided block_size as a guide so users have some control. */ if (is_disk_like) { @@ -207,7 +298,11 @@ archive_read_open_filename(struct archive *a, const char *filename, free(buffer); return (ARCHIVE_FATAL); } - strcpy(mine->filename, filename); + if (filename_type == FNT_WCS) + wcscpy(mine->filename.w, wfilename); + else + strcpy(mine->filename.m, filename); + mine->filename_type = filename_type; mine->block_size = block_size; mine->buffer = buffer; mine->fd = fd; @@ -252,11 +347,15 @@ file_read(struct archive *a, void *client_data, const void **buff) if (bytes_read < 0) { if (errno == EINTR) continue; - else if (mine->filename[0] == '\0') - archive_set_error(a, errno, "Error reading stdin"); + else if (mine->filename_type == FNT_STDIN) + archive_set_error(a, errno, + "Error reading stdin"); + else if (mine->filename_type == FNT_MBS) + archive_set_error(a, errno, + "Error reading '%s'", mine->filename.m); else - archive_set_error(a, errno, "Error reading '%s'", - mine->filename); + archive_set_error(a, errno, + "Error reading '%S'", mine->filename.w); } return (bytes_read); } @@ -314,11 +413,14 @@ file_skip_lseek(struct archive *a, void *client_data, int64_t request) return (0); /* If the input is corrupted or truncated, fail. */ - if (mine->filename[0] == '\0') + if (mine->filename_type == FNT_STDIN) archive_set_error(a, errno, "Error seeking in stdin"); - else + else if (mine->filename_type == FNT_MBS) archive_set_error(a, errno, "Error seeking in '%s'", - mine->filename); + mine->filename.m); + else + archive_set_error(a, errno, "Error seeking in '%S'", + mine->filename.w); return (-1); } @@ -354,11 +456,14 @@ file_seek(struct archive *a, void *client_data, int64_t request, int whence) return r; /* If the input is corrupted or truncated, fail. */ - if (mine->filename[0] == '\0') + if (mine->filename_type == FNT_STDIN) archive_set_error(a, errno, "Error seeking in stdin"); - else + else if (mine->filename_type == FNT_MBS) archive_set_error(a, errno, "Error seeking in '%s'", - mine->filename); + mine->filename.m); + else + archive_set_error(a, errno, "Error seeking in '%S'", + mine->filename.w); return (ARCHIVE_FATAL); } @@ -392,7 +497,7 @@ file_close(struct archive *a, void *client_data) } while (bytesRead > 0); } /* If a named file was opened, then it needs to be closed. */ - if (mine->filename[0] != '\0') + if (mine->filename_type != FNT_STDIN) close(mine->fd); } free(mine->buffer); diff --git a/libarchive/archive_write_open_filename.c b/libarchive/archive_write_open_filename.c index 8a4cd3522..868986631 100644 --- a/libarchive/archive_write_open_filename.c +++ b/libarchive/archive_write_open_filename.c @@ -46,6 +46,7 @@ __FBSDID("$FreeBSD: head/lib/libarchive/archive_write_open_filename.c 191165 200 #endif #include "archive.h" +#include "archive_string.h" #ifndef O_BINARY #define O_BINARY 0 @@ -53,7 +54,11 @@ __FBSDID("$FreeBSD: head/lib/libarchive/archive_write_open_filename.c 191165 200 struct write_file_data { int fd; - char filename[1]; + char mbs_filename; + union { + char m[1]; + wchar_t w[1]; + } filename; /* Must be last! */ }; static int file_close(struct archive *, void *); @@ -79,12 +84,60 @@ archive_write_open_filename(struct archive *a, const char *filename) archive_set_error(a, ENOMEM, "No memory"); return (ARCHIVE_FATAL); } - strcpy(mine->filename, filename); + strcpy(mine->filename.m, filename); + mine->mbs_filename = 1; mine->fd = -1; return (archive_write_open(a, mine, file_open, file_write, file_close)); } +int +archive_write_open_filename_w(struct archive *a, const wchar_t *filename) +{ +#if defined(_WIN32) && !defined(__CYGWIN__) + struct write_file_data *mine; + + if (filename == NULL || filename[0] == L'\0') + return (archive_write_open_fd(a, 1)); + + mine = malloc(sizeof(*mine) + wcslen(filename) * sizeof(wchar_t)); + if (mine == NULL) { + archive_set_error(a, ENOMEM, "No memory"); + return (ARCHIVE_FATAL); + } + wcscpy(mine->filename.w, filename); + mine->mbs_filename = 0; + mine->fd = -1; + return (archive_write_open(a, mine, + file_open, file_write, file_close)); +#else + /* + * POSIX system does not support a wchar_t interface for + * open() system call, so we have to translate a wchar_t + * filename to multi-byte one and use it. + */ + struct archive_string fn; + int r; + + if (filename == NULL || filename[0] == L'\0') + return (archive_write_open_fd(a, 1)); + + archive_string_init(&fn); + if (archive_string_append_from_wcs(&fn, filename, + wcslen(filename)) != 0) { + archive_set_error(a, EINVAL, + "Failed to convert a wide-character filename to" + " a multi-byte filename"); + archive_string_free(&fn); + return (ARCHIVE_FATAL); + } + r = archive_write_open_filename(a, fn.s); + archive_string_free(&fn); + return (r); +#endif +} + + static int file_open(struct archive *a, void *client_data) { @@ -98,17 +151,46 @@ file_open(struct archive *a, void *client_data) /* * Open the file. */ - mine->fd = open(mine->filename, flags, 0666); - if (mine->fd < 0) { - archive_set_error(a, errno, "Failed to open '%s'", - mine->filename); - return (ARCHIVE_FATAL); - } + if (mine->mbs_filename) { + mine->fd = open(mine->filename.m, flags, 0666); + if (mine->fd < 0) { + archive_set_error(a, errno, "Failed to open '%s'", + mine->filename.m); + return (ARCHIVE_FATAL); + } + + if (fstat(mine->fd, &st) != 0) { + archive_set_error(a, errno, "Couldn't stat '%s'", + mine->filename.m); + return (ARCHIVE_FATAL); + } + } else { +#if defined(_WIN32) && !defined(__CYGWIN__) + mine->fd = _wopen(mine->filename.w, flags, 0666); + if (mine->fd < 0 && errno == ENOENT) { + wchar_t *fullpath; + fullpath = __la_win_permissive_name_w(mine->filename.w); + if (fullpath != NULL) { + mine->fd = _wopen(fullpath, flags, 0666); + free(fullpath); + } + } + if (mine->fd < 0) { + archive_set_error(a, errno, "Failed to open '%S'", + mine->filename.w); + return (ARCHIVE_FATAL); + } - if (fstat(mine->fd, &st) != 0) { - archive_set_error(a, errno, "Couldn't stat '%s'", - mine->filename); - return (ARCHIVE_FATAL); + if (fstat(mine->fd, &st) != 0) { + archive_set_error(a, errno, "Couldn't stat '%S'", + mine->filename.w); + return (ARCHIVE_FATAL); + } +#else + archive_set_error(a, ARCHIVE_ERRNO_MISC, + "Unexpedted operation in archive_write_open_filename"); + return (ARCHIVE_FATAL); +#endif } /* diff --git a/libarchive/test/test_open_filename.c b/libarchive/test/test_open_filename.c index ca3ac2723..15646b861 100644 --- a/libarchive/test/test_open_filename.c +++ b/libarchive/test/test_open_filename.c @@ -25,7 +25,8 @@ #include "test.h" __FBSDID("$FreeBSD: head/lib/libarchive/test/test_open_filename.c 191183 2009-04-17 01:06:31Z kientzle $"); -DEFINE_TEST(test_open_filename) +static void +test_open_filename_mbs(void) { char buff[64]; struct archive_entry *ae; @@ -107,3 +108,93 @@ DEFINE_TEST(test_open_filename) assertEqualInt(ARCHIVE_OK, archive_read_free(a)); } + +static void +test_open_filename_wcs(void) +{ + char buff[64]; + struct archive_entry *ae; + struct archive *a; + + /* Write an archive through this FILE *. */ + assert((a = archive_write_new()) != NULL); + assertEqualIntA(a, ARCHIVE_OK, archive_write_set_format_ustar(a)); + assertEqualIntA(a, ARCHIVE_OK, archive_write_set_compression_none(a)); + assertEqualIntA(a, ARCHIVE_OK, + archive_write_open_filename_w(a, L"test.tar")); + + /* + * Write a file to it. + */ + assert((ae = archive_entry_new()) != NULL); + archive_entry_set_mtime(ae, 1, 0); + archive_entry_copy_pathname_w(ae, L"file"); + archive_entry_set_mode(ae, S_IFREG | 0755); + archive_entry_set_size(ae, 8); + assertEqualIntA(a, ARCHIVE_OK, archive_write_header(a, ae)); + archive_entry_free(ae); + assertEqualIntA(a, 8, archive_write_data(a, "12345678", 9)); + + /* + * Write a second file to it. + */ + assert((ae = archive_entry_new()) != NULL); + archive_entry_copy_pathname_w(ae, L"file2"); + archive_entry_set_mode(ae, S_IFREG | 0755); + archive_entry_set_size(ae, 819200); + assertEqualIntA(a, ARCHIVE_OK, archive_write_header(a, ae)); + archive_entry_free(ae); + + /* Close out the archive. */ + assertEqualIntA(a, ARCHIVE_OK, archive_write_close(a)); + assertEqualInt(ARCHIVE_OK, archive_write_free(a)); + + /* + * Now, read the data back. + */ + assert((a = archive_read_new()) != NULL); + assertEqualIntA(a, ARCHIVE_OK, archive_read_support_format_all(a)); + assertEqualIntA(a, ARCHIVE_OK, archive_read_support_filter_all(a)); + assertEqualIntA(a, ARCHIVE_OK, + archive_read_open_filename_w(a, L"test.tar", 512)); + + assertEqualIntA(a, ARCHIVE_OK, archive_read_next_header(a, &ae)); + assertEqualInt(1, archive_entry_mtime(ae)); + assertEqualInt(0, archive_entry_mtime_nsec(ae)); + assertEqualInt(0, archive_entry_atime(ae)); + assertEqualInt(0, archive_entry_ctime(ae)); + assertEqualWString(L"file", archive_entry_pathname_w(ae)); + assert((S_IFREG | 0755) == archive_entry_mode(ae)); + assertEqualInt(8, archive_entry_size(ae)); + assertEqualIntA(a, 8, archive_read_data(a, buff, 10)); + assertEqualMem(buff, "12345678", 8); + + assertEqualIntA(a, ARCHIVE_OK, archive_read_next_header(a, &ae)); + assertEqualWString(L"file2", archive_entry_pathname_w(ae)); + assert((S_IFREG | 0755) == archive_entry_mode(ae)); + assertEqualInt(819200, archive_entry_size(ae)); + assertEqualIntA(a, ARCHIVE_OK, archive_read_data_skip(a)); + + /* Verify the end of the archive. */ + assertEqualIntA(a, ARCHIVE_EOF, archive_read_next_header(a, &ae)); + assertEqualIntA(a, ARCHIVE_OK, archive_read_close(a)); + assertEqualInt(ARCHIVE_OK, archive_read_free(a)); + + /* + * Verify some of the error handling. + */ + assert((a = archive_read_new()) != NULL); + assertEqualIntA(a, ARCHIVE_OK, archive_read_support_format_all(a)); + assertEqualIntA(a, ARCHIVE_OK, archive_read_support_filter_all(a)); + assertEqualIntA(a, ARCHIVE_FATAL, + archive_read_open_filename_w(a, L"nonexistent.tar", 512)); + assertEqualIntA(a, ARCHIVE_OK, archive_read_close(a)); + assertEqualInt(ARCHIVE_OK, archive_read_free(a)); + +} + +DEFINE_TEST(test_open_filename) +{ + test_open_filename_mbs(); + test_open_filename_wcs(); +}