e3ba53f496612c7d95e8dabaa12d3512865edca6
[wimlib] / src / tagged_items.c
1 /*
2  * tagged_items.c
3  *
4  * Support for tagged metadata items that can be appended to WIM directory
5  * entries.
6  */
7
8 /*
9  * Copyright (C) 2014 Eric Biggers
10  *
11  * This file is free software; you can redistribute it and/or modify it under
12  * the terms of the GNU Lesser General Public License as published by the Free
13  * Software Foundation; either version 3 of the License, or (at your option) any
14  * later version.
15  *
16  * This file is distributed in the hope that it will be useful, but WITHOUT
17  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
18  * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
19  * details.
20  *
21  * You should have received a copy of the GNU Lesser General Public License
22  * along with this file; if not, see http://www.gnu.org/licenses/.
23  */
24
25 #ifdef HAVE_CONFIG_H
26 #  include "config.h"
27 #endif
28
29 #include "wimlib/endianness.h"
30 #include "wimlib/inode.h"
31 #include "wimlib/types.h"
32 #include "wimlib/unix_data.h"
33
34 /* Used by the Microsoft implementation.  */
35 #define TAG_OBJECT_ID           0x00000001
36
37 /* Random number that we'll use for tagging our UNIX data items.  */
38 #define TAG_WIMLIB_UNIX_DATA    0x337DD873
39
40 /* Header that begins each tagged metadata item in the metadata resource  */
41 struct tagged_item_header {
42
43         /* Unique identifier for this item.  */
44         le32 tag;
45
46         /* Size of the data of this tagged item, in bytes.  This excludes this
47          * header and should be a multiple of 8.
48          *
49          * (Actually, the MS implementation seems to round this up to an 8 byte
50          * boundary when calculating the offset to the next tagged item, but
51          * uses this length unmodified when validating the item.  We might as
52          * well do the same.)  */
53         le32 length;
54
55         /* Variable length data  */
56         u8 data[];
57 };
58
59 struct object_id_disk {
60         u8 object_id[16];
61         u8 birth_volume_id[16];
62         u8 birth_object_id[16];
63         u8 domain_id[16];
64 };
65
66 struct wimlib_unix_data_disk {
67         le32 uid;
68         le32 gid;
69         le32 mode;
70         le32 rdev;
71 };
72
73 /* Retrieves the first tagged item with the specified tag and minimum length
74  * from the WIM inode.  Returns a pointer to the tagged data, which can be read
75  * and/or modified in place.  Or, if no matching tagged item is found, returns
76  * NULL.  */
77 static void *
78 inode_get_tagged_item(const struct wim_inode *inode,
79                       u32 desired_tag, u32 min_data_len)
80 {
81         size_t minlen_with_hdr = sizeof(struct tagged_item_header) + min_data_len;
82         size_t len_remaining = inode->i_extra_size;
83         u8 *p = inode->i_extra;
84
85         /* Iterate through the tagged items.  */
86         while (len_remaining >= minlen_with_hdr) {
87                 struct tagged_item_header *hdr;
88                 u32 tag;
89                 u32 len;
90
91                 hdr = (struct tagged_item_header *)p;
92                 tag = le32_to_cpu(hdr->tag);
93                 len = le32_to_cpu(hdr->length);
94
95                 if (tag == desired_tag && len >= min_data_len)
96                         return hdr->data;
97
98                 len = (len + 7) & ~7;
99                 if (len_remaining <= sizeof(struct tagged_item_header) + len)
100                         return NULL;
101                 len_remaining -= sizeof(struct tagged_item_header) + len;
102                 p += sizeof(struct tagged_item_header) + len;
103         }
104         return NULL;
105 }
106
107 /* Adds a tagged item to a WIM inode and returns a pointer to its uninitialized
108  * data, which must be initialized in-place by the caller.  */
109 static void *
110 inode_add_tagged_item(struct wim_inode *inode, u32 tag, u32 len)
111 {
112         size_t itemsize;
113         size_t newsize;
114         u8 *buf;
115         struct tagged_item_header *hdr;
116
117         /* We prepend the item instead of appending it because it's easier.  */
118
119         itemsize = sizeof(struct tagged_item_header) + ((len + 7) & ~7);
120         newsize = itemsize + inode->i_extra_size;
121
122         buf = MALLOC(newsize);
123         if (!buf)
124                 return NULL;
125
126         if (inode->i_extra_size) {
127                 memcpy(buf + itemsize, inode->i_extra, inode->i_extra_size);
128                 FREE(inode->i_extra);
129         }
130         inode->i_extra = buf;
131         inode->i_extra_size = newsize;
132
133         hdr = (struct tagged_item_header *)buf;
134         hdr->tag = cpu_to_le32(tag);
135         hdr->length = cpu_to_le32(len);
136         return memset(hdr->data, 0, (len + 7) & ~7);
137 }
138
139 static inline struct wimlib_unix_data_disk *
140 inode_get_unix_data_disk(const struct wim_inode *inode)
141 {
142         return inode_get_tagged_item(inode, TAG_WIMLIB_UNIX_DATA,
143                                      sizeof(struct wimlib_unix_data_disk));
144 }
145
146 static inline struct wimlib_unix_data_disk *
147 inode_add_unix_data_disk(struct wim_inode *inode)
148 {
149         return inode_add_tagged_item(inode, TAG_WIMLIB_UNIX_DATA,
150                                      sizeof(struct wimlib_unix_data_disk));
151 }
152
153 /* Returns %true if the specified WIM inode has UNIX data; otherwise %false.
154  * This is a wimlib extension.  */
155 bool
156 inode_has_unix_data(const struct wim_inode *inode)
157 {
158         return inode_get_unix_data_disk(inode) != NULL;
159 }
160
161 /* Retrieves UNIX data from the specified WIM inode.
162  * This is a wimlib extension.
163  *
164  * Returns %true and fills @unix_data if the inode has UNIX data.
165  * Otherwise returns %false.  */
166 bool
167 inode_get_unix_data(const struct wim_inode *inode,
168                     struct wimlib_unix_data *unix_data)
169 {
170         const struct wimlib_unix_data_disk *p;
171
172         p = inode_get_unix_data_disk(inode);
173         if (!p)
174                 return false;
175
176         unix_data->uid = le32_to_cpu(p->uid);
177         unix_data->gid = le32_to_cpu(p->gid);
178         unix_data->mode = le32_to_cpu(p->mode);
179         unix_data->rdev = le32_to_cpu(p->rdev);
180         return true;
181 }
182
183 /* Sets UNIX data on the specified WIM inode.
184  * This is a wimlib extension.
185  *
186  * Callers must specify all members in @unix_data.  If the inode does not yet
187  * have UNIX data, it is given these values.  Otherwise, only the values that
188  * also have the corresponding flags in @which set are changed.
189  *
190  * Returns %true if successful, %false if failed (out of memory).  */
191 bool
192 inode_set_unix_data(struct wim_inode *inode, struct wimlib_unix_data *unix_data,
193                     int which)
194 {
195         struct wimlib_unix_data_disk *p;
196
197         p = inode_get_unix_data_disk(inode);
198         if (!p) {
199                 p = inode_add_unix_data_disk(inode);
200                 if (!p)
201                         return false;
202                 which = UNIX_DATA_ALL;
203         }
204         if (which & UNIX_DATA_UID)
205                 p->uid = cpu_to_le32(unix_data->uid);
206         if (which & UNIX_DATA_GID)
207                 p->gid = cpu_to_le32(unix_data->gid);
208         if (which & UNIX_DATA_MODE)
209                 p->mode = cpu_to_le32(unix_data->mode);
210         if (which & UNIX_DATA_RDEV)
211                 p->rdev = cpu_to_le32(unix_data->rdev);
212         return true;
213 }