aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorPeter Jones <pjones@redhat.com>2016-02-08 14:48:15 -0500
committerMatt Fleming <matt@codeblueprint.co.uk>2016-02-10 11:25:52 -0500
commited8b0de5a33d2a2557dce7f9429dca8cb5bc5879 (patch)
tree1dcd2e49cc432ae312677ec47453ec3abc18f1d2
parent8282f5d9c17fe15a9e658c06e3f343efae1a2a2f (diff)
efi: Make efivarfs entries immutable by default
"rm -rf" is bricking some peoples' laptops because of variables being used to store non-reinitializable firmware driver data that's required to POST the hardware. These are 100% bugs, and they need to be fixed, but in the mean time it shouldn't be easy to *accidentally* brick machines. We have to have delete working, and picking which variables do and don't work for deletion is quite intractable, so instead make everything immutable by default (except for a whitelist), and make tools that aren't quite so broad-spectrum unset the immutable flag. Signed-off-by: Peter Jones <pjones@redhat.com> Tested-by: Lee, Chun-Yi <jlee@suse.com> Acked-by: Matthew Garrett <mjg59@coreos.com> Signed-off-by: Matt Fleming <matt@codeblueprint.co.uk>
-rw-r--r--Documentation/filesystems/efivarfs.txt7
-rw-r--r--drivers/firmware/efi/vars.c87
-rw-r--r--fs/efivarfs/file.c70
-rw-r--r--fs/efivarfs/inode.c30
-rw-r--r--fs/efivarfs/internal.h3
-rw-r--r--fs/efivarfs/super.c9
-rw-r--r--include/linux/efi.h2
-rwxr-xr-xtools/testing/selftests/efivarfs/efivarfs.sh19
-rw-r--r--tools/testing/selftests/efivarfs/open-unlink.c72
9 files changed, 258 insertions, 41 deletions
diff --git a/Documentation/filesystems/efivarfs.txt b/Documentation/filesystems/efivarfs.txt
index c477af086e65..686a64bba775 100644
--- a/Documentation/filesystems/efivarfs.txt
+++ b/Documentation/filesystems/efivarfs.txt
@@ -14,3 +14,10 @@ filesystem.
14efivarfs is typically mounted like this, 14efivarfs is typically mounted like this,
15 15
16 mount -t efivarfs none /sys/firmware/efi/efivars 16 mount -t efivarfs none /sys/firmware/efi/efivars
17
18Due to the presence of numerous firmware bugs where removing non-standard
19UEFI variables causes the system firmware to fail to POST, efivarfs
20files that are not well-known standardized variables are created
21as immutable files. This doesn't prevent removal - "chattr -i" will work -
22but it does prevent this kind of failure from being accomplished
23accidentally.
diff --git a/drivers/firmware/efi/vars.c b/drivers/firmware/efi/vars.c
index 9a53da21e7b6..50f10bad2604 100644
--- a/drivers/firmware/efi/vars.c
+++ b/drivers/firmware/efi/vars.c
@@ -172,10 +172,12 @@ struct variable_validate {
172}; 172};
173 173
174/* 174/*
175 * This is the list of variables we need to validate. 175 * This is the list of variables we need to validate, as well as the
176 * whitelist for what we think is safe not to default to immutable.
176 * 177 *
177 * If it has a validate() method that's not NULL, it'll go into the 178 * If it has a validate() method that's not NULL, it'll go into the
178 * validation routine. If not, it is assumed valid. 179 * validation routine. If not, it is assumed valid, but still used for
180 * whitelisting.
179 * 181 *
180 * Note that it's sorted by {vendor,name}, but globbed names must come after 182 * Note that it's sorted by {vendor,name}, but globbed names must come after
181 * any other name with the same prefix. 183 * any other name with the same prefix.
@@ -193,11 +195,37 @@ static const struct variable_validate variable_validate[] = {
193 { EFI_GLOBAL_VARIABLE_GUID, "ErrOut", validate_device_path }, 195 { EFI_GLOBAL_VARIABLE_GUID, "ErrOut", validate_device_path },
194 { EFI_GLOBAL_VARIABLE_GUID, "ErrOutDev", validate_device_path }, 196 { EFI_GLOBAL_VARIABLE_GUID, "ErrOutDev", validate_device_path },
195 { EFI_GLOBAL_VARIABLE_GUID, "Lang", validate_ascii_string }, 197 { EFI_GLOBAL_VARIABLE_GUID, "Lang", validate_ascii_string },
198 { EFI_GLOBAL_VARIABLE_GUID, "OsIndications", NULL },
196 { EFI_GLOBAL_VARIABLE_GUID, "PlatformLang", validate_ascii_string }, 199 { EFI_GLOBAL_VARIABLE_GUID, "PlatformLang", validate_ascii_string },
197 { EFI_GLOBAL_VARIABLE_GUID, "Timeout", validate_uint16 }, 200 { EFI_GLOBAL_VARIABLE_GUID, "Timeout", validate_uint16 },
198 { NULL_GUID, "", NULL }, 201 { NULL_GUID, "", NULL },
199}; 202};
200 203
204static bool
205variable_matches(const char *var_name, size_t len, const char *match_name,
206 int *match)
207{
208 for (*match = 0; ; (*match)++) {
209 char c = match_name[*match];
210 char u = var_name[*match];
211
212 /* Wildcard in the matching name means we've matched */
213 if (c == '*')
214 return true;
215
216 /* Case sensitive match */
217 if (!c && *match == len)
218 return true;
219
220 if (c != u)
221 return false;
222
223 if (!c)
224 return true;
225 }
226 return true;
227}
228
201bool 229bool
202efivar_validate(efi_guid_t vendor, efi_char16_t *var_name, u8 *data, 230efivar_validate(efi_guid_t vendor, efi_char16_t *var_name, u8 *data,
203 unsigned long data_size) 231 unsigned long data_size)
@@ -221,35 +249,48 @@ efivar_validate(efi_guid_t vendor, efi_char16_t *var_name, u8 *data,
221 if (efi_guidcmp(vendor, variable_validate[i].vendor)) 249 if (efi_guidcmp(vendor, variable_validate[i].vendor))
222 continue; 250 continue;
223 251
224 for (match = 0; ; match++) { 252 if (variable_matches(utf8_name, utf8_size+1, name, &match)) {
225 char c = name[match]; 253 if (variable_validate[i].validate == NULL)
226 char u = utf8_name[match];
227
228 /* Wildcard in the matching name means we've matched */
229 if (c == '*') {
230 kfree(utf8_name);
231 return variable_validate[i].validate(var_name,
232 match, data, data_size);
233 }
234
235 /* Case sensitive match */
236 if (c != u)
237 break; 254 break;
238 255 kfree(utf8_name);
239 /* Reached the end of the string while matching */ 256 return variable_validate[i].validate(var_name, match,
240 if (!c) { 257 data, data_size);
241 kfree(utf8_name);
242 return variable_validate[i].validate(var_name,
243 match, data, data_size);
244 }
245 } 258 }
246 } 259 }
247
248 kfree(utf8_name); 260 kfree(utf8_name);
249 return true; 261 return true;
250} 262}
251EXPORT_SYMBOL_GPL(efivar_validate); 263EXPORT_SYMBOL_GPL(efivar_validate);
252 264
265bool
266efivar_variable_is_removable(efi_guid_t vendor, const char *var_name,
267 size_t len)
268{
269 int i;
270 bool found = false;
271 int match = 0;
272
273 /*
274 * Check if our variable is in the validated variables list
275 */
276 for (i = 0; variable_validate[i].name[0] != '\0'; i++) {
277 if (efi_guidcmp(variable_validate[i].vendor, vendor))
278 continue;
279
280 if (variable_matches(var_name, len,
281 variable_validate[i].name, &match)) {
282 found = true;
283 break;
284 }
285 }
286
287 /*
288 * If it's in our list, it is removable.
289 */
290 return found;
291}
292EXPORT_SYMBOL_GPL(efivar_variable_is_removable);
293
253static efi_status_t 294static efi_status_t
254check_var_size(u32 attributes, unsigned long size) 295check_var_size(u32 attributes, unsigned long size)
255{ 296{
diff --git a/fs/efivarfs/file.c b/fs/efivarfs/file.c
index c424e4813ec8..d48e0d261d78 100644
--- a/fs/efivarfs/file.c
+++ b/fs/efivarfs/file.c
@@ -10,6 +10,7 @@
10#include <linux/efi.h> 10#include <linux/efi.h>
11#include <linux/fs.h> 11#include <linux/fs.h>
12#include <linux/slab.h> 12#include <linux/slab.h>
13#include <linux/mount.h>
13 14
14#include "internal.h" 15#include "internal.h"
15 16
@@ -103,9 +104,78 @@ out_free:
103 return size; 104 return size;
104} 105}
105 106
107static int
108efivarfs_ioc_getxflags(struct file *file, void __user *arg)
109{
110 struct inode *inode = file->f_mapping->host;
111 unsigned int i_flags;
112 unsigned int flags = 0;
113
114 i_flags = inode->i_flags;
115 if (i_flags & S_IMMUTABLE)
116 flags |= FS_IMMUTABLE_FL;
117
118 if (copy_to_user(arg, &flags, sizeof(flags)))
119 return -EFAULT;
120 return 0;
121}
122
123static int
124efivarfs_ioc_setxflags(struct file *file, void __user *arg)
125{
126 struct inode *inode = file->f_mapping->host;
127 unsigned int flags;
128 unsigned int i_flags = 0;
129 int error;
130
131 if (!inode_owner_or_capable(inode))
132 return -EACCES;
133
134 if (copy_from_user(&flags, arg, sizeof(flags)))
135 return -EFAULT;
136
137 if (flags & ~FS_IMMUTABLE_FL)
138 return -EOPNOTSUPP;
139
140 if (!capable(CAP_LINUX_IMMUTABLE))
141 return -EPERM;
142
143 if (flags & FS_IMMUTABLE_FL)
144 i_flags |= S_IMMUTABLE;
145
146
147 error = mnt_want_write_file(file);
148 if (error)
149 return error;
150
151 inode_lock(inode);
152 inode_set_flags(inode, i_flags, S_IMMUTABLE);
153 inode_unlock(inode);
154
155 mnt_drop_write_file(file);
156
157 return 0;
158}
159
160long
161efivarfs_file_ioctl(struct file *file, unsigned int cmd, unsigned long p)
162{
163 void __user *arg = (void __user *)p;
164
165 switch (cmd) {
166 case FS_IOC_GETFLAGS:
167 return efivarfs_ioc_getxflags(file, arg);
168 case FS_IOC_SETFLAGS:
169 return efivarfs_ioc_setxflags(file, arg);
170 }
171
172 return -ENOTTY;
173}
174
106const struct file_operations efivarfs_file_operations = { 175const struct file_operations efivarfs_file_operations = {
107 .open = simple_open, 176 .open = simple_open,
108 .read = efivarfs_file_read, 177 .read = efivarfs_file_read,
109 .write = efivarfs_file_write, 178 .write = efivarfs_file_write,
110 .llseek = no_llseek, 179 .llseek = no_llseek,
180 .unlocked_ioctl = efivarfs_file_ioctl,
111}; 181};
diff --git a/fs/efivarfs/inode.c b/fs/efivarfs/inode.c
index 3381b9da9ee6..e2ab6d0497f2 100644
--- a/fs/efivarfs/inode.c
+++ b/fs/efivarfs/inode.c
@@ -15,7 +15,8 @@
15#include "internal.h" 15#include "internal.h"
16 16
17struct inode *efivarfs_get_inode(struct super_block *sb, 17struct inode *efivarfs_get_inode(struct super_block *sb,
18 const struct inode *dir, int mode, dev_t dev) 18 const struct inode *dir, int mode,
19 dev_t dev, bool is_removable)
19{ 20{
20 struct inode *inode = new_inode(sb); 21 struct inode *inode = new_inode(sb);
21 22
@@ -23,6 +24,7 @@ struct inode *efivarfs_get_inode(struct super_block *sb,
23 inode->i_ino = get_next_ino(); 24 inode->i_ino = get_next_ino();
24 inode->i_mode = mode; 25 inode->i_mode = mode;
25 inode->i_atime = inode->i_mtime = inode->i_ctime = CURRENT_TIME; 26 inode->i_atime = inode->i_mtime = inode->i_ctime = CURRENT_TIME;
27 inode->i_flags = is_removable ? 0 : S_IMMUTABLE;
26 switch (mode & S_IFMT) { 28 switch (mode & S_IFMT) {
27 case S_IFREG: 29 case S_IFREG:
28 inode->i_fop = &efivarfs_file_operations; 30 inode->i_fop = &efivarfs_file_operations;
@@ -102,22 +104,17 @@ static void efivarfs_hex_to_guid(const char *str, efi_guid_t *guid)
102static int efivarfs_create(struct inode *dir, struct dentry *dentry, 104static int efivarfs_create(struct inode *dir, struct dentry *dentry,
103 umode_t mode, bool excl) 105 umode_t mode, bool excl)
104{ 106{
105 struct inode *inode; 107 struct inode *inode = NULL;
106 struct efivar_entry *var; 108 struct efivar_entry *var;
107 int namelen, i = 0, err = 0; 109 int namelen, i = 0, err = 0;
110 bool is_removable = false;
108 111
109 if (!efivarfs_valid_name(dentry->d_name.name, dentry->d_name.len)) 112 if (!efivarfs_valid_name(dentry->d_name.name, dentry->d_name.len))
110 return -EINVAL; 113 return -EINVAL;
111 114
112 inode = efivarfs_get_inode(dir->i_sb, dir, mode, 0);
113 if (!inode)
114 return -ENOMEM;
115
116 var = kzalloc(sizeof(struct efivar_entry), GFP_KERNEL); 115 var = kzalloc(sizeof(struct efivar_entry), GFP_KERNEL);
117 if (!var) { 116 if (!var)
118 err = -ENOMEM; 117 return -ENOMEM;
119 goto out;
120 }
121 118
122 /* length of the variable name itself: remove GUID and separator */ 119 /* length of the variable name itself: remove GUID and separator */
123 namelen = dentry->d_name.len - EFI_VARIABLE_GUID_LEN - 1; 120 namelen = dentry->d_name.len - EFI_VARIABLE_GUID_LEN - 1;
@@ -125,6 +122,16 @@ static int efivarfs_create(struct inode *dir, struct dentry *dentry,
125 efivarfs_hex_to_guid(dentry->d_name.name + namelen + 1, 122 efivarfs_hex_to_guid(dentry->d_name.name + namelen + 1,
126 &var->var.VendorGuid); 123 &var->var.VendorGuid);
127 124
125 if (efivar_variable_is_removable(var->var.VendorGuid,
126 dentry->d_name.name, namelen))
127 is_removable = true;
128
129 inode = efivarfs_get_inode(dir->i_sb, dir, mode, 0, is_removable);
130 if (!inode) {
131 err = -ENOMEM;
132 goto out;
133 }
134
128 for (i = 0; i < namelen; i++) 135 for (i = 0; i < namelen; i++)
129 var->var.VariableName[i] = dentry->d_name.name[i]; 136 var->var.VariableName[i] = dentry->d_name.name[i];
130 137
@@ -138,7 +145,8 @@ static int efivarfs_create(struct inode *dir, struct dentry *dentry,
138out: 145out:
139 if (err) { 146 if (err) {
140 kfree(var); 147 kfree(var);
141 iput(inode); 148 if (inode)
149 iput(inode);
142 } 150 }
143 return err; 151 return err;
144} 152}
diff --git a/fs/efivarfs/internal.h b/fs/efivarfs/internal.h
index b5ff16addb7c..b4505188e799 100644
--- a/fs/efivarfs/internal.h
+++ b/fs/efivarfs/internal.h
@@ -15,7 +15,8 @@ extern const struct file_operations efivarfs_file_operations;
15extern const struct inode_operations efivarfs_dir_inode_operations; 15extern const struct inode_operations efivarfs_dir_inode_operations;
16extern bool efivarfs_valid_name(const char *str, int len); 16extern bool efivarfs_valid_name(const char *str, int len);
17extern struct inode *efivarfs_get_inode(struct super_block *sb, 17extern struct inode *efivarfs_get_inode(struct super_block *sb,
18 const struct inode *dir, int mode, dev_t dev); 18 const struct inode *dir, int mode, dev_t dev,
19 bool is_removable);
19 20
20extern struct list_head efivarfs_list; 21extern struct list_head efivarfs_list;
21 22
diff --git a/fs/efivarfs/super.c b/fs/efivarfs/super.c
index 8651ac28ec0d..dd029d13ea61 100644
--- a/fs/efivarfs/super.c
+++ b/fs/efivarfs/super.c
@@ -120,6 +120,7 @@ static int efivarfs_callback(efi_char16_t *name16, efi_guid_t vendor,
120 char *name; 120 char *name;
121 int len; 121 int len;
122 int err = -ENOMEM; 122 int err = -ENOMEM;
123 bool is_removable = false;
123 124
124 entry = kzalloc(sizeof(*entry), GFP_KERNEL); 125 entry = kzalloc(sizeof(*entry), GFP_KERNEL);
125 if (!entry) 126 if (!entry)
@@ -137,13 +138,17 @@ static int efivarfs_callback(efi_char16_t *name16, efi_guid_t vendor,
137 138
138 ucs2_as_utf8(name, entry->var.VariableName, len); 139 ucs2_as_utf8(name, entry->var.VariableName, len);
139 140
141 if (efivar_variable_is_removable(entry->var.VendorGuid, name, len))
142 is_removable = true;
143
140 name[len] = '-'; 144 name[len] = '-';
141 145
142 efi_guid_to_str(&entry->var.VendorGuid, name + len + 1); 146 efi_guid_to_str(&entry->var.VendorGuid, name + len + 1);
143 147
144 name[len + EFI_VARIABLE_GUID_LEN+1] = '\0'; 148 name[len + EFI_VARIABLE_GUID_LEN+1] = '\0';
145 149
146 inode = efivarfs_get_inode(sb, d_inode(root), S_IFREG | 0644, 0); 150 inode = efivarfs_get_inode(sb, d_inode(root), S_IFREG | 0644, 0,
151 is_removable);
147 if (!inode) 152 if (!inode)
148 goto fail_name; 153 goto fail_name;
149 154
@@ -199,7 +204,7 @@ static int efivarfs_fill_super(struct super_block *sb, void *data, int silent)
199 sb->s_d_op = &efivarfs_d_ops; 204 sb->s_d_op = &efivarfs_d_ops;
200 sb->s_time_gran = 1; 205 sb->s_time_gran = 1;
201 206
202 inode = efivarfs_get_inode(sb, NULL, S_IFDIR | 0755, 0); 207 inode = efivarfs_get_inode(sb, NULL, S_IFDIR | 0755, 0, true);
203 if (!inode) 208 if (!inode)
204 return -ENOMEM; 209 return -ENOMEM;
205 inode->i_op = &efivarfs_dir_inode_operations; 210 inode->i_op = &efivarfs_dir_inode_operations;
diff --git a/include/linux/efi.h b/include/linux/efi.h
index 16ca611aabc8..47be3ad7d3e5 100644
--- a/include/linux/efi.h
+++ b/include/linux/efi.h
@@ -1201,6 +1201,8 @@ struct efivar_entry *efivar_entry_find(efi_char16_t *name, efi_guid_t guid,
1201 1201
1202bool efivar_validate(efi_guid_t vendor, efi_char16_t *var_name, u8 *data, 1202bool efivar_validate(efi_guid_t vendor, efi_char16_t *var_name, u8 *data,
1203 unsigned long data_size); 1203 unsigned long data_size);
1204bool efivar_variable_is_removable(efi_guid_t vendor, const char *name,
1205 size_t len);
1204 1206
1205extern struct work_struct efivar_work; 1207extern struct work_struct efivar_work;
1206void efivar_run_worker(void); 1208void efivar_run_worker(void);
diff --git a/tools/testing/selftests/efivarfs/efivarfs.sh b/tools/testing/selftests/efivarfs/efivarfs.sh
index 77edcdcc016b..057278448515 100755
--- a/tools/testing/selftests/efivarfs/efivarfs.sh
+++ b/tools/testing/selftests/efivarfs/efivarfs.sh
@@ -88,7 +88,11 @@ test_delete()
88 exit 1 88 exit 1
89 fi 89 fi
90 90
91 rm $file 91 rm $file 2>/dev/null
92 if [ $? -ne 0 ]; then
93 chattr -i $file
94 rm $file
95 fi
92 96
93 if [ -e $file ]; then 97 if [ -e $file ]; then
94 echo "$file couldn't be deleted" >&2 98 echo "$file couldn't be deleted" >&2
@@ -111,6 +115,7 @@ test_zero_size_delete()
111 exit 1 115 exit 1
112 fi 116 fi
113 117
118 chattr -i $file
114 printf "$attrs" > $file 119 printf "$attrs" > $file
115 120
116 if [ -e $file ]; then 121 if [ -e $file ]; then
@@ -141,7 +146,11 @@ test_valid_filenames()
141 echo "$file could not be created" >&2 146 echo "$file could not be created" >&2
142 ret=1 147 ret=1
143 else 148 else
144 rm $file 149 rm $file 2>/dev/null
150 if [ $? -ne 0 ]; then
151 chattr -i $file
152 rm $file
153 fi
145 fi 154 fi
146 done 155 done
147 156
@@ -174,7 +183,11 @@ test_invalid_filenames()
174 183
175 if [ -e $file ]; then 184 if [ -e $file ]; then
176 echo "Creating $file should have failed" >&2 185 echo "Creating $file should have failed" >&2
177 rm $file 186 rm $file 2>/dev/null
187 if [ $? -ne 0 ]; then
188 chattr -i $file
189 rm $file
190 fi
178 ret=1 191 ret=1
179 fi 192 fi
180 done 193 done
diff --git a/tools/testing/selftests/efivarfs/open-unlink.c b/tools/testing/selftests/efivarfs/open-unlink.c
index 8c0764407b3c..4af74f733036 100644
--- a/tools/testing/selftests/efivarfs/open-unlink.c
+++ b/tools/testing/selftests/efivarfs/open-unlink.c
@@ -1,10 +1,68 @@
1#include <errno.h>
1#include <stdio.h> 2#include <stdio.h>
2#include <stdint.h> 3#include <stdint.h>
3#include <stdlib.h> 4#include <stdlib.h>
4#include <unistd.h> 5#include <unistd.h>
6#include <sys/ioctl.h>
5#include <sys/types.h> 7#include <sys/types.h>
6#include <sys/stat.h> 8#include <sys/stat.h>
7#include <fcntl.h> 9#include <fcntl.h>
10#include <linux/fs.h>
11
12static int set_immutable(const char *path, int immutable)
13{
14 unsigned int flags;
15 int fd;
16 int rc;
17 int error;
18
19 fd = open(path, O_RDONLY);
20 if (fd < 0)
21 return fd;
22
23 rc = ioctl(fd, FS_IOC_GETFLAGS, &flags);
24 if (rc < 0) {
25 error = errno;
26 close(fd);
27 errno = error;
28 return rc;
29 }
30
31 if (immutable)
32 flags |= FS_IMMUTABLE_FL;
33 else
34 flags &= ~FS_IMMUTABLE_FL;
35
36 rc = ioctl(fd, FS_IOC_SETFLAGS, &flags);
37 error = errno;
38 close(fd);
39 errno = error;
40 return rc;
41}
42
43static int get_immutable(const char *path)
44{
45 unsigned int flags;
46 int fd;
47 int rc;
48 int error;
49
50 fd = open(path, O_RDONLY);
51 if (fd < 0)
52 return fd;
53
54 rc = ioctl(fd, FS_IOC_GETFLAGS, &flags);
55 if (rc < 0) {
56 error = errno;
57 close(fd);
58 errno = error;
59 return rc;
60 }
61 close(fd);
62 if (flags & FS_IMMUTABLE_FL)
63 return 1;
64 return 0;
65}
8 66
9int main(int argc, char **argv) 67int main(int argc, char **argv)
10{ 68{
@@ -27,7 +85,7 @@ int main(int argc, char **argv)
27 buf[4] = 0; 85 buf[4] = 0;
28 86
29 /* create a test variable */ 87 /* create a test variable */
30 fd = open(path, O_WRONLY | O_CREAT); 88 fd = open(path, O_WRONLY | O_CREAT, 0600);
31 if (fd < 0) { 89 if (fd < 0) {
32 perror("open(O_WRONLY)"); 90 perror("open(O_WRONLY)");
33 return EXIT_FAILURE; 91 return EXIT_FAILURE;
@@ -41,6 +99,18 @@ int main(int argc, char **argv)
41 99
42 close(fd); 100 close(fd);
43 101
102 rc = get_immutable(path);
103 if (rc < 0) {
104 perror("ioctl(FS_IOC_GETFLAGS)");
105 return EXIT_FAILURE;
106 } else if (rc) {
107 rc = set_immutable(path, 0);
108 if (rc < 0) {
109 perror("ioctl(FS_IOC_SETFLAGS)");
110 return EXIT_FAILURE;
111 }
112 }
113
44 fd = open(path, O_RDONLY); 114 fd = open(path, O_RDONLY);
45 if (fd < 0) { 115 if (fd < 0) {
46 perror("open"); 116 perror("open");