723773f94be3e678afe6c0f88bae6a3355a961ce
[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         le32 length;
49
50         /* Variable length data  */
51         u8 data[];
52 };
53
54 struct object_id_disk {
55         u8 object_id[16];
56         u8 birth_volume_id[16];
57         u8 birth_object_id[16];
58         u8 domain_id[16];
59 };
60
61 struct wimlib_unix_data_disk {
62         le32 uid;
63         le32 gid;
64         le32 mode;
65         le32 rdev;
66 };
67
68 /* Retrieves the first tagged item with the specified tag and minimum length
69  * from the WIM inode.  Returns a pointer to the tagged data, which can be read
70  * and/or modified in place.  Or, if no matching tagged item is found, returns
71  * NULL.  */
72 static void *
73 inode_get_tagged_item(const struct wim_inode *inode,
74                       u32 desired_tag, u32 min_data_len)
75 {
76         size_t minlen_with_hdr = sizeof(struct tagged_item_header) + min_data_len;
77         size_t len_remaining;
78         u8 *p;
79
80         if (!inode->i_extra)
81                 return NULL;
82
83         len_remaining = inode->i_extra->size;
84         p = inode->i_extra->data;
85
86         /* Iterate through the tagged items.  */
87         while (len_remaining >= minlen_with_hdr) {
88                 struct tagged_item_header *hdr;
89                 u32 tag;
90                 u32 len;
91
92                 hdr = (struct tagged_item_header *)p;
93                 tag = le32_to_cpu(hdr->tag);
94                 len = ALIGN(le32_to_cpu(hdr->length), 8);
95
96                 /* Length overflow?  */
97                 if (unlikely(len > len_remaining - sizeof(struct tagged_item_header)))
98                         return NULL;
99
100                 /* Matches the item we wanted?  */
101                 if (tag == desired_tag && len >= min_data_len)
102                         return hdr->data;
103
104                 len_remaining -= sizeof(struct tagged_item_header) + len;
105                 p += sizeof(struct tagged_item_header) + len;
106         }
107         return NULL;
108 }
109
110 /* Adds a tagged item to a WIM inode and returns a pointer to its uninitialized
111  * data, which must be initialized in-place by the caller.  */
112 static void *
113 inode_add_tagged_item(struct wim_inode *inode, u32 tag, u32 len)
114 {
115         size_t itemsize;
116         size_t newsize;
117         struct wim_inode_extra *extra;
118         struct tagged_item_header *hdr;
119
120         /* We prepend the item instead of appending it because it's easier.  */
121
122         itemsize = sizeof(struct tagged_item_header) + ALIGN(len, 8);
123         newsize = itemsize;
124         if (inode->i_extra)
125                 newsize += inode->i_extra->size;
126
127         extra = MALLOC(sizeof(struct wim_inode_extra) + newsize);
128         if (!extra)
129                 return NULL;
130         if (inode->i_extra) {
131                 memcpy(&extra->data[itemsize], inode->i_extra->data,
132                        inode->i_extra->size);
133                 FREE(inode->i_extra);
134         }
135         extra->size = newsize;
136         inode->i_extra = extra;
137
138         hdr = (struct tagged_item_header *)extra->data;
139         hdr->tag = cpu_to_le32(tag);
140         hdr->length = cpu_to_le32(len);
141         return memset(hdr->data, 0, ALIGN(len, 8));
142 }
143
144 static inline struct wimlib_unix_data_disk *
145 inode_get_unix_data_disk(const struct wim_inode *inode)
146 {
147         return inode_get_tagged_item(inode, TAG_WIMLIB_UNIX_DATA,
148                                      sizeof(struct wimlib_unix_data_disk));
149 }
150
151 static inline struct wimlib_unix_data_disk *
152 inode_add_unix_data_disk(struct wim_inode *inode)
153 {
154         return inode_add_tagged_item(inode, TAG_WIMLIB_UNIX_DATA,
155                                      sizeof(struct wimlib_unix_data_disk));
156 }
157
158 /* Returns %true if the specified WIM inode has UNIX data; otherwise %false.
159  * This is a wimlib extension.  */
160 bool
161 inode_has_unix_data(const struct wim_inode *inode)
162 {
163         return inode_get_unix_data_disk(inode) != NULL;
164 }
165
166 /* Retrieves UNIX data from the specified WIM inode.
167  * This is a wimlib extension.
168  *
169  * Returns %true and fills @unix_data if the inode has UNIX data.
170  * Otherwise returns %false.  */
171 bool
172 inode_get_unix_data(const struct wim_inode *inode,
173                     struct wimlib_unix_data *unix_data)
174 {
175         const struct wimlib_unix_data_disk *p;
176
177         p = inode_get_unix_data_disk(inode);
178         if (!p)
179                 return false;
180
181         unix_data->uid = le32_to_cpu(p->uid);
182         unix_data->gid = le32_to_cpu(p->gid);
183         unix_data->mode = le32_to_cpu(p->mode);
184         unix_data->rdev = le32_to_cpu(p->rdev);
185         return true;
186 }
187
188 /* Sets UNIX data on the specified WIM inode.
189  * This is a wimlib extension.
190  *
191  * Callers must specify all members in @unix_data.  If the inode does not yet
192  * have UNIX data, it is given these values.  Otherwise, only the values that
193  * also have the corresponding flags in @which set are changed.
194  *
195  * Returns %true if successful, %false if failed (out of memory).  */
196 bool
197 inode_set_unix_data(struct wim_inode *inode, struct wimlib_unix_data *unix_data,
198                     int which)
199 {
200         struct wimlib_unix_data_disk *p;
201
202         p = inode_get_unix_data_disk(inode);
203         if (!p) {
204                 p = inode_add_unix_data_disk(inode);
205                 if (!p)
206                         return false;
207                 which = UNIX_DATA_ALL;
208         }
209         if (which & UNIX_DATA_UID)
210                 p->uid = cpu_to_le32(unix_data->uid);
211         if (which & UNIX_DATA_GID)
212                 p->gid = cpu_to_le32(unix_data->gid);
213         if (which & UNIX_DATA_MODE)
214                 p->mode = cpu_to_le32(unix_data->mode);
215         if (which & UNIX_DATA_RDEV)
216                 p->rdev = cpu_to_le32(unix_data->rdev);
217         return true;
218 }