]> git.ipfire.org Git - thirdparty/libarchive.git/commitdiff
Add wchar_t filename support for archive_{read,write}_open_filename().
authorMichihiro NAKAJIMA <ggcueroad@gmail.com>
Mon, 21 Nov 2011 04:20:53 +0000 (23:20 -0500)
committerMichihiro NAKAJIMA <ggcueroad@gmail.com>
Mon, 21 Nov 2011 04:20:53 +0000 (23:20 -0500)
It would be useful for an internationalization programing on Windows.

SVN-Revision: 3825

libarchive/archive.h
libarchive/archive_read_open_filename.c
libarchive/archive_write_open_filename.c
libarchive/test/test_open_filename.c

index 4858a626608779ef93dbcc6163272541a6971248..c0bb26d74ba516b90bca5ee46a1e883ae6044fda 100644 (file)
@@ -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 *);
index c06d1a17d0dd71ea6c7ab4c99950e6aae3b64b0a..088782dbe697645bf2e585799e8f54ee419d3899 100644 (file)
@@ -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);
index 8a4cd3522196917b64e63dccbfaa2e1172d17746..8689866311c6b3d87377f6cf4bd0c24d3b8309b9 100644 (file)
@@ -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
        }
 
        /*
index ca3ac272383cf120c2f48041842c8941f162735e..15646b86162bf6bd897a1cc8671378c8cb8cafc3 100644 (file)
@@ -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();
+}