XpmWrite.c revision 74835918
1/*
2 * Copyright (c) 2023, Oracle and/or its affiliates.
3 *
4 * Permission is hereby granted, free of charge, to any person obtaining a
5 * copy of this software and associated documentation files (the "Software"),
6 * to deal in the Software without restriction, including without limitation
7 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
8 * and/or sell copies of the Software, and to permit persons to whom the
9 * Software is furnished to do so, subject to the following conditions:
10 *
11 * The above copyright notice and this permission notice (including the next
12 * paragraph) shall be included in all copies or substantial portions of the
13 * Software.
14 *
15 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
18 * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
20 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
21 * DEALINGS IN THE SOFTWARE.
22 */
23
24#include "config.h"
25
26#include <X11/xpm.h>
27
28#include <glib.h>
29#include <glib/gstdio.h>
30
31#include <stdio.h>
32#include <string.h>
33#include <sys/types.h>
34#include <sys/stat.h>
35#include <fcntl.h>
36#include <unistd.h>
37
38#include "TestAllFiles.h"
39#include "CompareXpmImage.h"
40
41#ifndef g_assert_no_errno /* defined in glib 2.66 & later */
42#define g_assert_no_errno(n) g_assert_cmpint(n, >=, 0)
43#endif
44
45/*
46 * Check if a filename ends in ".Z" or ".gz"
47 */
48static inline gboolean
49is_compressed(const char *filepath)
50{
51    const char *ext = strrchr(filepath, '.');
52
53    if ((ext != NULL) &&
54        (((ext[1] == 'Z') && (ext[2] == 0)) ||
55         ((ext[1] == 'g') && (ext[2] == 'z') && (ext[3] == 0)))) {
56        return TRUE;
57    }
58
59    return FALSE;
60}
61
62/*
63 * If a filename ends in ".Z" or ".gz", remove that extension to avoid
64 * confusing libXpm into applying compression when not desired.
65 */
66static inline void
67strip_compress_ext(char *filepath)
68{
69    char *ext = strrchr(filepath, '.');
70
71    if ((ext != NULL) &&
72        (((ext[1] == 'Z') && (ext[2] == 0)) ||
73         ((ext[1] == 'g') && (ext[2] == 'z') && (ext[3] == 0)))) {
74        *ext = '\0';
75    }
76}
77
78/*
79 * XpmWriteFileFromXpmImage - Write XPM files without requiring an X Display
80  */
81static void
82test_WFFXI_helper(const gchar *newfilepath, XpmImage *imageA, XpmInfo *infoA)
83{
84    XpmImage imageB;
85    XpmInfo infoB;
86    int status;
87
88    g_test_message("...writing %s", newfilepath);
89
90    status = XpmWriteFileFromXpmImage(newfilepath, imageA, infoA);
91    g_assert_cmpint(status, ==, XpmSuccess);
92
93    if (is_compressed(newfilepath)) {
94         /* Wait a moment for the compression command to finish writing,
95          * since OpenWriteFile() does a double fork so we can't just wait
96          * for the child command to exit.
97          */
98         usleep(10000);
99    }
100
101    status = XpmReadFileToXpmImage(newfilepath, &imageB, &infoB);
102    g_assert_cmpint(status, ==, XpmSuccess);
103
104    CompareXpmImage(imageA, &imageB);
105    XpmFreeXpmImage(&imageB);
106    XpmFreeXpmInfo(&infoB);
107
108    status = remove(newfilepath);
109    g_assert_no_errno(status);
110
111}
112
113static int
114TestWriteFileFromXpmImage(const gchar *filepath)
115{
116    XpmImage imageA;
117    XpmInfo infoA;
118    int status;
119    gchar *testdir, *filename, *newfilepath;
120    GError *err = NULL;
121
122#ifndef NO_ZPIPE
123    gchar *cmpfilepath;
124#endif
125
126    status = XpmReadFileToXpmImage(filepath, &imageA, &infoA);
127    g_assert_cmpint(status, ==, XpmSuccess);
128
129    testdir = g_dir_make_tmp("XpmWrite-test-XXXXXX", &err);
130    g_assert_no_error(err);
131
132    filename = g_path_get_basename(filepath);
133    strip_compress_ext(filename);
134    newfilepath = g_build_filename(testdir, filename, NULL);
135
136    test_WFFXI_helper(newfilepath, &imageA, &infoA);
137
138#ifndef NO_ZPIPE
139    cmpfilepath = g_strdup_printf("%s.gz", newfilepath);
140    test_WFFXI_helper(cmpfilepath, &imageA, &infoA);
141    g_free(cmpfilepath);
142
143#ifdef XPM_PATH_COMPRESS
144    cmpfilepath = g_strdup_printf("%s.Z", newfilepath);
145    test_WFFXI_helper(cmpfilepath, &imageA, &infoA);
146    g_free(cmpfilepath);
147#endif
148#endif
149
150    XpmFreeXpmImage(&imageA);
151    XpmFreeXpmInfo(&infoA);
152
153    g_assert_no_errno(g_rmdir(testdir));
154
155    g_free(newfilepath);
156    g_free(filename);
157    g_free(testdir);
158
159    return status;
160}
161
162static void
163test_XpmWriteFileFromXpmImage(void)
164{
165    /* Todo: verify trying to write to an unwritable file fails */
166
167    TestAllNormalFiles("good", XpmSuccess, TestWriteFileFromXpmImage);
168    /* XpmReadFileToXpmImage supports compressed files */
169    TestAllCompressedFiles("good", XpmSuccess, TestWriteFileFromXpmImage);
170}
171
172/*
173 * XpmWriteFileFromData - wrapper around XpmWriteFileFromXpmImage that
174 * converts the image into a list of strings.
175 */
176static void
177test_WFFXD_helper(const gchar *newfilepath, char **dataA)
178{
179    char **dataB;
180    int status;
181
182    g_test_message("...writing %s", newfilepath);
183
184    status = XpmWriteFileFromData(newfilepath, dataA);
185    g_assert_cmpint(status, ==, XpmSuccess);
186
187    if (is_compressed(newfilepath)) {
188         /* Wait a moment for the compression command to finish writing,
189          * since OpenWriteFile() does a double fork so we can't just wait
190          * for the child command to exit.
191          */
192         usleep(10000);
193    }
194
195    status = XpmReadFileToData(newfilepath, &dataB);
196    g_assert_cmpint(status, ==, XpmSuccess);
197
198    /* Todo: compare data fields */
199    XpmFree(dataB);
200
201    status = remove(newfilepath);
202    g_assert_no_errno(status);
203
204}
205
206static int
207TestWriteFileFromData(const gchar *filepath)
208{
209    char **data = NULL;
210    int status;
211    gchar *testdir, *filename, *newfilepath;
212    GError *err = NULL;
213
214#ifndef NO_ZPIPE
215    gchar *cmpfilepath;
216#endif
217
218    status = XpmReadFileToData(filepath, &data);
219    g_assert_cmpint(status, ==, XpmSuccess);
220
221    testdir = g_dir_make_tmp("XpmWrite-test-XXXXXX", &err);
222    g_assert_no_error(err);
223
224    filename = g_path_get_basename(filepath);
225    strip_compress_ext(filename);
226    newfilepath = g_build_filename(testdir, filename, NULL);
227
228    test_WFFXD_helper(newfilepath, data);
229
230#ifndef NO_ZPIPE
231    cmpfilepath = g_strdup_printf("%s.gz", newfilepath);
232    test_WFFXD_helper(cmpfilepath, data);
233    g_free(cmpfilepath);
234
235#ifdef XPM_PATH_COMPRESS
236    cmpfilepath = g_strdup_printf("%s.Z", newfilepath);
237    test_WFFXD_helper(cmpfilepath, data);
238    g_free(cmpfilepath);
239#endif
240#endif
241
242    XpmFree(data);
243
244    g_assert_no_errno(g_rmdir(testdir));
245
246    g_free(newfilepath);
247    g_free(filename);
248    g_free(testdir);
249
250    return status;
251}
252
253static void
254test_XpmWriteFileFromData(void)
255{
256    /* Todo - verify trying to write to an unwritable file fails */
257
258    TestAllNormalFiles("good", XpmSuccess, TestWriteFileFromData);
259    /* XpmReadFileToData calls XpmReadFileToXpmImage so it
260       supports compressed files */
261    TestAllCompressedFiles("good", XpmSuccess, TestWriteFileFromData);
262}
263
264/*
265 * XpmWriteFileFromBuffer - helper function to write files & read them back in
266 * XpmWriteFileFromBuffer() does not support compressed files.
267 */
268static int
269TestWriteFileFromBuffer(const gchar *filepath)
270{
271    char *buffer = NULL;
272    gchar *testdir, *filename, *newfilepath;
273    GError *err = NULL;
274    int status;
275
276    status = XpmReadFileToBuffer(filepath, &buffer);
277    g_assert_cmpint(status, ==, XpmSuccess);
278    g_assert_nonnull(buffer);
279
280    testdir = g_dir_make_tmp("XpmWrite-test-XXXXXX", &err);
281    g_assert_no_error(err);
282
283    filename = g_path_get_basename(filepath);
284    strip_compress_ext(filename);
285    newfilepath = g_build_filename(testdir, filename, NULL);
286    g_test_message("...writing %s", newfilepath);
287
288    status = XpmWriteFileFromBuffer(newfilepath, buffer);
289    g_assert_cmpint(status, ==, XpmSuccess);
290
291    if (status == XpmSuccess) {
292        char readbuf[8192];
293        char *b = buffer;
294        int fd;
295        ssize_t rd;
296
297        /* Read file ourselves and verify the data matches */
298        g_assert_no_errno(fd = open(newfilepath, O_RDONLY));
299        while ((rd = read(fd, readbuf, sizeof(readbuf))) > 0) {
300            g_assert_cmpmem(b, rd, readbuf, rd);
301            b += rd;
302        }
303        /* Verify we're at the end of the buffer */
304        g_assert_cmpint(b[0], ==, '\0');
305
306        g_assert_no_errno(close(fd));
307        g_assert_no_errno(remove(newfilepath));
308    }
309    XpmFree(buffer);
310
311    g_assert_no_errno(g_rmdir(testdir));
312
313    g_free(newfilepath);
314    g_free(filename);
315    g_free(testdir);
316
317    return status;
318}
319
320static void
321test_XpmWriteFileFromBuffer(void)
322{
323    /* Todo: verify trying to write to an unwritable file fails */
324
325    TestAllNormalFiles("good", XpmSuccess, TestWriteFileFromBuffer);
326    /* XpmReadFileToBuffer does not support compressed files */
327}
328
329int
330main(int argc, char** argv)
331{
332    g_test_init(&argc, &argv, NULL);
333    g_test_bug_base(PACKAGE_BUGREPORT);
334
335
336    g_test_add_func("/XpmRead/XpmWriteFileFromXpmImage",
337                    test_XpmWriteFileFromXpmImage);
338    g_test_add_func("/XpmRead/XpmWriteFileFromData",
339                    test_XpmWriteFileFromData);
340    g_test_add_func("/XpmRead/XpmWriteFileFromBuffer",
341                    test_XpmWriteFileFromBuffer);
342
343    return g_test_run();
344}
345