1 /*
2  * ghb-dvd.c
3  * Copyright (C) John Stebbins 2008-2021 <stebbins@stebbins>
4  *
5  * ghb-dvd.c is free software.
6  *
7  * You may redistribute it and/or modify it under the terms of the
8  * GNU General Public License version 2, as published by the Free Software
9  * Foundation.
10  *
11  * ghb-dvd.c is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
14  * See the GNU General Public License for more details.
15  *
16  * You should have received a copy of the GNU General Public License
17  * along with main.c.  If not, write to:
18  *  The Free Software Foundation, Inc.,
19  *  51 Franklin Street, Fifth Floor
20  *  Boston, MA  02110-1301, USA.
21  */
22 
23 #if defined(_WIN32)
24 #include <windows.h>
25 #endif
26 
27 #include <glib.h>
28 #include <gio/gio.h>
29 
30 #include "ghb-dvd.h"
31 
32 #if 0
33 #include <unistd.h>
34 #include <fcntl.h>
35 #include <string.h>
36 
37 #ifndef PACKED
38 #define PACKED              __attribute__((packed))
39 #endif
40 
41 struct volume_descriptor {
42     struct descriptor_tag {
43         guint16 id;
44         guint16 version;
45         guint8  checksum;
46         guint8  reserved;
47         guint16 serial;
48         guint16 crc;
49         guint16 crc_len;
50         guint32 location;
51     } PACKED tag;
52     union {
53         struct anchor_descriptor {
54             guint32 length;
55             guint32 location;
56         } PACKED anchor;
57         struct primary_descriptor {
58             guint32 seq_num;
59             guint32 desc_num;
60             struct dstring {
61                 guint8  clen;
62                 guint8  c[31];
63             } PACKED ident;
64         } PACKED primary;
65     } PACKED type;
66 } PACKED;
67 
68 struct volume_structure_descriptor {
69     guint8      type;
70     guint8      id[5];
71     guint8      version;
72 } PACKED;
73 
74 #define VOLUME_ID_LABEL_SIZE        64
75 typedef struct
76 {
77     gint fd;
78     gchar label[VOLUME_ID_LABEL_SIZE+1];
79 } udf_info_t;
80 
81 enum endian {
82     LE = 0,
83     BE = 1
84 };
85 
86 #ifdef __BYTE_ORDER
87 #if (__BYTE_ORDER == __LITTLE_ENDIAN)
88 #define le16_to_cpu(x) (x)
89 #define le32_to_cpu(x) (x)
90 #define le64_to_cpu(x) (x)
91 #define be16_to_cpu(x) bswap_16(x)
92 #define be32_to_cpu(x) bswap_32(x)
93 #define cpu_to_le16(x) (x)
94 #define cpu_to_le32(x) (x)
95 #define cpu_to_be32(x) bswap_32(x)
96 #elif (__BYTE_ORDER == __BIG_ENDIAN)
97 #define le16_to_cpu(x) bswap_16(x)
98 #define le32_to_cpu(x) bswap_32(x)
99 #define le64_to_cpu(x) bswap_64(x)
100 #define be16_to_cpu(x) (x)
101 #define be32_to_cpu(x) (x)
102 #define cpu_to_le16(x) bswap_16(x)
103 #define cpu_to_le32(x) bswap_32(x)
104 #define cpu_to_be32(x) (x)
105 #endif
106 #endif /* __BYTE_ORDER */
107 
108 #define UDF_VSD_OFFSET          0x8000
109 
110 static guint8*
111 get_buffer(int fd, guint64 off, gsize len)
112 {
113     gint buf_len;
114 
115     if (lseek(fd, off, SEEK_SET) < 0)
116     {
117         return NULL;
118     }
119     guint8 *buffer = g_malloc(len);
120     buf_len = read(fd, buffer, len);
121     if (buf_len < 0)
122     {
123         g_free(buffer);
124         return NULL;
125     }
126     return buffer;
127 }
128 
129 static gint
130 set_unicode16(guint8 *str, gsize len, const guint8 *buf, gint endianness, gsize count)
131 {
132     gint ii, jj;
133     guint16 c;
134 
135     jj = 0;
136     for (ii = 0; ii + 2 <= count; ii += 2) {
137         if (endianness == LE)
138             c = (buf[ii+1] << 8) | buf[ii];
139         else
140             c = (buf[ii] << 8) | buf[ii+1];
141         if (c == 0) {
142             str[jj] = '\0';
143             break;
144         } else if (c < 0x80) {
145             if (jj+1 >= len)
146                 break;
147             str[jj++] = (guint8) c;
148         } else if (c < 0x800) {
149             if (jj+2 >= len)
150                 break;
151             str[jj++] = (guint8) (0xc0 | (c >> 6));
152             str[jj++] = (guint8) (0x80 | (c & 0x3f));
153         } else {
154             if (jj+3 >= len)
155                 break;
156             str[jj++] = (guint8) (0xe0 | (c >> 12));
157             str[jj++] = (guint8) (0x80 | ((c >> 6) & 0x3f));
158             str[jj++] = (guint8) (0x80 | (c & 0x3f));
159         }
160     }
161     str[jj] = '\0';
162     return jj;
163 }
164 
165 static void
166 set_label_string(guint8 *str, const guint8 *buf, gsize count)
167 {
168     gint ii;
169 
170     memcpy(str, buf, count);
171     str[count] = 0;
172 
173     /* remove trailing whitespace */
174     ii = strlen((gchar*)str);
175     while (ii--)
176     {
177         if (!g_ascii_isspace(str[ii]))
178             break;
179     }
180     str[ii+1] = 0;
181 }
182 
183 static gint
184 probe_udf(udf_info_t *id)
185 {
186     struct volume_descriptor *vd;
187     struct volume_structure_descriptor *vsd;
188     guint bs;
189     guint b;
190     guint type;
191     guint count;
192     guint loc;
193     guint clen;
194     guint64 off = 0;
195 
196     vsd = (struct volume_structure_descriptor *) get_buffer(id->fd, off + UDF_VSD_OFFSET, 0x200);
197     if (vsd == NULL)
198         return -1;
199 
200     if (memcmp(vsd->id, "NSR02", 5) == 0)
201         goto blocksize;
202     if (memcmp(vsd->id, "NSR03", 5) == 0)
203         goto blocksize;
204     if (memcmp(vsd->id, "BEA01", 5) == 0)
205         goto blocksize;
206     if (memcmp(vsd->id, "BOOT2", 5) == 0)
207         goto blocksize;
208     if (memcmp(vsd->id, "CD001", 5) == 0)
209         goto blocksize;
210     if (memcmp(vsd->id, "CDW02", 5) == 0)
211         goto blocksize;
212     if (memcmp(vsd->id, "TEA03", 5) == 0)
213         goto blocksize;
214     return -1;
215 
216 blocksize:
217     /* search the next VSD to get the logical block size of the volume */
218     for (bs = 0x800; bs < 0x8000; bs += 0x800) {
219         vsd = (struct volume_structure_descriptor *) get_buffer(id->fd, off + UDF_VSD_OFFSET + bs, 0x800);
220         if (vsd == NULL)
221             return -1;
222         if (vsd->id[0] != '\0')
223             goto nsr;
224     }
225     return -1;
226 
227 nsr:
228     /* search the list of VSDs for a NSR descriptor */
229     for (b = 0; b < 64; b++) {
230         vsd = (struct volume_structure_descriptor *) get_buffer(id->fd, off + UDF_VSD_OFFSET + (b * bs), 0x800);
231         if (vsd == NULL)
232             return -1;
233 
234         if (vsd->id[0] == '\0')
235             return -1;
236         if (memcmp(vsd->id, "NSR02", 5) == 0)
237             goto anchor;
238         if (memcmp(vsd->id, "NSR03", 5) == 0)
239             goto anchor;
240     }
241     return -1;
242 
243 anchor:
244     /* read anchor volume descriptor */
245     vd = (struct volume_descriptor *) get_buffer(id->fd, off + (256 * bs), 0x200);
246     if (vd == NULL)
247         return -1;
248 
249     type = le16_to_cpu(vd->tag.id);
250     if (type != 2) /* TAG_ID_AVDP */
251         goto found;
252 
253     /* get descriptor list address and block count */
254     count = le32_to_cpu(vd->type.anchor.length) / bs;
255     loc = le32_to_cpu(vd->type.anchor.location);
256 
257     /* pick the primary descriptor from the list */
258     for (b = 0; b < count; b++) {
259         vd = (struct volume_descriptor *) get_buffer(id->fd, off + ((loc + b) * bs), 0x200);
260         if (vd == NULL)
261             return -1;
262 
263         type = le16_to_cpu(vd->tag.id);
264 
265         /* check validity */
266         if (type == 0)
267             goto found;
268         if (le32_to_cpu(vd->tag.location) != loc + b)
269             goto found;
270 
271         if (type == 1) /* TAG_ID_PVD */
272             goto pvd;
273     }
274     goto found;
275 
276 pvd:
277     clen = vd->type.primary.ident.clen;
278     if (clen == 8)
279         set_label_string((guint8*)id->label, vd->type.primary.ident.c, 31);
280     else if (clen == 16)
281         set_unicode16((guint8*)id->label, sizeof(id->label), vd->type.primary.ident.c, BE, 31);
282 
283 found:
284     return 0;
285 }
286 
287 gchar*
288 ghb_dvd_volname(const gchar *device)
289 {
290     udf_info_t id;
291     gchar *buffer = NULL;
292 
293     id.fd = open(device, O_RDONLY);
294     if (id.fd < 0) {
295         return NULL;
296     }
297     if (probe_udf (&id) == 0)
298     {
299         buffer = g_strdup(id.label);
300     }
301     return buffer;
302 }
303 #endif
304 
305 gchar*
ghb_resolve_symlink(const gchar * name)306 ghb_resolve_symlink(const gchar *name)
307 {
308     gchar *file;
309     GFileInfo *info;
310     GFile *gfile;
311 
312     gfile = g_file_new_for_path(name);
313     info = g_file_query_info(gfile,
314         G_FILE_ATTRIBUTE_STANDARD_NAME ","
315         G_FILE_ATTRIBUTE_STANDARD_SYMLINK_TARGET ","
316         G_FILE_ATTRIBUTE_STANDARD_IS_SYMLINK,
317         G_FILE_QUERY_INFO_NONE, NULL, NULL);
318     while ((info != NULL) && g_file_info_get_is_symlink(info))
319     {
320         GFile *parent;
321         const gchar *target;
322 
323         parent = g_file_get_parent(gfile);
324         g_object_unref(gfile);
325         target = g_file_info_get_symlink_target(info);
326         gfile = g_file_resolve_relative_path(parent, target);
327         g_object_unref(parent);
328 
329         g_object_unref(info);
330         info = g_file_query_info(gfile,
331             G_FILE_ATTRIBUTE_STANDARD_NAME ","
332             G_FILE_ATTRIBUTE_STANDARD_SYMLINK_TARGET ","
333             G_FILE_ATTRIBUTE_STANDARD_IS_SYMLINK,
334             G_FILE_QUERY_INFO_NONE, NULL, NULL);
335     }
336     if (info != NULL)
337     {
338         file = g_file_get_path(gfile);
339         g_object_unref(info);
340     }
341     else
342     {
343         file = g_strdup(name);
344     }
345     g_object_unref(gfile);
346     return file;
347 }
348 
349 void
ghb_dvd_set_current(const gchar * name,signal_user_data_t * ud)350 ghb_dvd_set_current(const gchar *name, signal_user_data_t *ud)
351 {
352 #if !defined(_WIN32)
353     GFile *gfile;
354     GFileInfo *info;
355     gchar *resolved = ghb_resolve_symlink(name);
356 
357     if (ud->current_dvd_device != NULL)
358     {
359         g_free(ud->current_dvd_device);
360         ud->current_dvd_device = NULL;
361     }
362     gfile = g_file_new_for_path(resolved);
363     info = g_file_query_info(gfile,
364         G_FILE_ATTRIBUTE_STANDARD_TYPE,
365         G_FILE_QUERY_INFO_NONE, NULL, NULL);
366     if (info != NULL)
367     {
368         if (g_file_info_get_file_type(info) == G_FILE_TYPE_SPECIAL)
369         {
370             // I could go through the trouble to scan the connected drives and
371             // verify that this device is connected and is a DVD.  But I don't
372             // think its necessary.
373             ud->current_dvd_device = resolved;
374         }
375         else
376         {
377             g_free(resolved);
378         }
379         g_object_unref(info);
380     }
381     else
382     {
383         g_free(resolved);
384     }
385     g_object_unref(gfile);
386 #else
387     gchar drive[4];
388     guint dtype;
389 
390     if (ud->current_dvd_device != NULL)
391     {
392         g_free(ud->current_dvd_device);
393         ud->current_dvd_device = NULL;
394     }
395     g_strlcpy(drive, name, 4);
396     dtype = GetDriveType(drive);
397     if (dtype == DRIVE_CDROM)
398     {
399         ud->current_dvd_device = g_strdup(name);
400     }
401 #endif
402 }
403