module: layout_and_allocate
layout_and_allocate() does everything up to and including the final struct module placement inside the allocated module memory. We have to store the symbol layout information in our struct load_info though. This avoids the nasty code we had before where 'mod' pointed first to the version inside the temporary allocation containing the entire file, then later was moved to point to the real struct module: now the main code only ever sees the final module address. (Includes fix for the Tony Luck-found Linus-diagnosed failure path error). Signed-off-by: Rusty Russell <rusty@rustcorp.com.au>
This commit is contained in:
Родитель
511ca6ae43
Коммит
d913188c75
226
kernel/module.c
226
kernel/module.c
|
@ -115,6 +115,8 @@ struct load_info {
|
||||||
unsigned long len;
|
unsigned long len;
|
||||||
Elf_Shdr *sechdrs;
|
Elf_Shdr *sechdrs;
|
||||||
char *secstrings, *args, *strtab;
|
char *secstrings, *args, *strtab;
|
||||||
|
unsigned long *strmap;
|
||||||
|
unsigned long symoffs, stroffs;
|
||||||
struct {
|
struct {
|
||||||
unsigned int sym, str, mod, vers, info, pcpu;
|
unsigned int sym, str, mod, vers, info, pcpu;
|
||||||
} index;
|
} index;
|
||||||
|
@ -402,7 +404,8 @@ static int percpu_modalloc(struct module *mod,
|
||||||
mod->percpu = __alloc_reserved_percpu(size, align);
|
mod->percpu = __alloc_reserved_percpu(size, align);
|
||||||
if (!mod->percpu) {
|
if (!mod->percpu) {
|
||||||
printk(KERN_WARNING
|
printk(KERN_WARNING
|
||||||
"Could not allocate %lu bytes percpu data\n", size);
|
"%s: Could not allocate %lu bytes percpu data\n",
|
||||||
|
mod->name, size);
|
||||||
return -ENOMEM;
|
return -ENOMEM;
|
||||||
}
|
}
|
||||||
mod->percpu_size = size;
|
mod->percpu_size = size;
|
||||||
|
@ -2032,10 +2035,7 @@ static unsigned long layout_symtab(struct module *mod,
|
||||||
return symoffs;
|
return symoffs;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void add_kallsyms(struct module *mod, struct load_info *info,
|
static void add_kallsyms(struct module *mod, struct load_info *info)
|
||||||
unsigned long symoffs,
|
|
||||||
unsigned long stroffs,
|
|
||||||
unsigned long *strmap)
|
|
||||||
{
|
{
|
||||||
unsigned int i, ndst;
|
unsigned int i, ndst;
|
||||||
const Elf_Sym *src;
|
const Elf_Sym *src;
|
||||||
|
@ -2052,21 +2052,22 @@ static void add_kallsyms(struct module *mod, struct load_info *info,
|
||||||
for (i = 0; i < mod->num_symtab; i++)
|
for (i = 0; i < mod->num_symtab; i++)
|
||||||
mod->symtab[i].st_info = elf_type(&mod->symtab[i], info);
|
mod->symtab[i].st_info = elf_type(&mod->symtab[i], info);
|
||||||
|
|
||||||
mod->core_symtab = dst = mod->module_core + symoffs;
|
mod->core_symtab = dst = mod->module_core + info->symoffs;
|
||||||
src = mod->symtab;
|
src = mod->symtab;
|
||||||
*dst = *src;
|
*dst = *src;
|
||||||
for (ndst = i = 1; i < mod->num_symtab; ++i, ++src) {
|
for (ndst = i = 1; i < mod->num_symtab; ++i, ++src) {
|
||||||
if (!is_core_symbol(src, info->sechdrs, info->hdr->e_shnum))
|
if (!is_core_symbol(src, info->sechdrs, info->hdr->e_shnum))
|
||||||
continue;
|
continue;
|
||||||
dst[ndst] = *src;
|
dst[ndst] = *src;
|
||||||
dst[ndst].st_name = bitmap_weight(strmap, dst[ndst].st_name);
|
dst[ndst].st_name = bitmap_weight(info->strmap,
|
||||||
|
dst[ndst].st_name);
|
||||||
++ndst;
|
++ndst;
|
||||||
}
|
}
|
||||||
mod->core_num_syms = ndst;
|
mod->core_num_syms = ndst;
|
||||||
|
|
||||||
mod->core_strtab = s = mod->module_core + stroffs;
|
mod->core_strtab = s = mod->module_core + info->stroffs;
|
||||||
for (*s = 0, i = 1; i < info->sechdrs[info->index.str].sh_size; ++i)
|
for (*s = 0, i = 1; i < info->sechdrs[info->index.str].sh_size; ++i)
|
||||||
if (test_bit(i, strmap))
|
if (test_bit(i, info->strmap))
|
||||||
*++s = mod->strtab[i];
|
*++s = mod->strtab[i];
|
||||||
}
|
}
|
||||||
#else
|
#else
|
||||||
|
@ -2082,10 +2083,7 @@ static inline unsigned long layout_symtab(struct module *mod,
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void add_kallsyms(struct module *mod, struct load_info *info,
|
static void add_kallsyms(struct module *mod, struct load_info *info)
|
||||||
unsigned long symoffs,
|
|
||||||
unsigned long stroffs,
|
|
||||||
unsigned long *strmap)
|
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
#endif /* CONFIG_KALLSYMS */
|
#endif /* CONFIG_KALLSYMS */
|
||||||
|
@ -2150,8 +2148,10 @@ static inline void kmemleak_load_module(struct module *mod, Elf_Ehdr *hdr,
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
/* Sets info->hdr and info->len. */
|
/* Sets info->hdr, info->len and info->args. */
|
||||||
static int copy_and_check(struct load_info *info, const void __user *umod, unsigned long len)
|
static int copy_and_check(struct load_info *info,
|
||||||
|
const void __user *umod, unsigned long len,
|
||||||
|
const char __user *uargs)
|
||||||
{
|
{
|
||||||
int err;
|
int err;
|
||||||
Elf_Ehdr *hdr;
|
Elf_Ehdr *hdr;
|
||||||
|
@ -2183,6 +2183,14 @@ static int copy_and_check(struct load_info *info, const void __user *umod, unsig
|
||||||
err = -ENOEXEC;
|
err = -ENOEXEC;
|
||||||
goto free_hdr;
|
goto free_hdr;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Now copy in args */
|
||||||
|
info->args = strndup_user(uargs, ~0UL >> 1);
|
||||||
|
if (IS_ERR(info->args)) {
|
||||||
|
err = PTR_ERR(info->args);
|
||||||
|
goto free_hdr;
|
||||||
|
}
|
||||||
|
|
||||||
info->hdr = hdr;
|
info->hdr = hdr;
|
||||||
info->len = len;
|
info->len = len;
|
||||||
return 0;
|
return 0;
|
||||||
|
@ -2192,6 +2200,12 @@ free_hdr:
|
||||||
return err;
|
return err;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void free_copy(struct load_info *info)
|
||||||
|
{
|
||||||
|
kfree(info->args);
|
||||||
|
vfree(info->hdr);
|
||||||
|
}
|
||||||
|
|
||||||
static int rewrite_section_headers(struct load_info *info)
|
static int rewrite_section_headers(struct load_info *info)
|
||||||
{
|
{
|
||||||
unsigned int i;
|
unsigned int i;
|
||||||
|
@ -2385,9 +2399,9 @@ static void find_module_sections(struct module *mod, Elf_Ehdr *hdr,
|
||||||
mod->name);
|
mod->name);
|
||||||
}
|
}
|
||||||
|
|
||||||
static struct module *move_module(struct module *mod,
|
static int move_module(struct module *mod,
|
||||||
Elf_Ehdr *hdr, Elf_Shdr *sechdrs,
|
Elf_Ehdr *hdr, Elf_Shdr *sechdrs,
|
||||||
const char *secstrings, unsigned modindex)
|
const char *secstrings, unsigned modindex)
|
||||||
{
|
{
|
||||||
int i;
|
int i;
|
||||||
void *ptr;
|
void *ptr;
|
||||||
|
@ -2401,7 +2415,7 @@ static struct module *move_module(struct module *mod,
|
||||||
*/
|
*/
|
||||||
kmemleak_not_leak(ptr);
|
kmemleak_not_leak(ptr);
|
||||||
if (!ptr)
|
if (!ptr)
|
||||||
return ERR_PTR(-ENOMEM);
|
return -ENOMEM;
|
||||||
|
|
||||||
memset(ptr, 0, mod->core_size);
|
memset(ptr, 0, mod->core_size);
|
||||||
mod->module_core = ptr;
|
mod->module_core = ptr;
|
||||||
|
@ -2416,7 +2430,7 @@ static struct module *move_module(struct module *mod,
|
||||||
kmemleak_ignore(ptr);
|
kmemleak_ignore(ptr);
|
||||||
if (!ptr && mod->init_size) {
|
if (!ptr && mod->init_size) {
|
||||||
module_free(mod, mod->module_core);
|
module_free(mod, mod->module_core);
|
||||||
return ERR_PTR(-ENOMEM);
|
return -ENOMEM;
|
||||||
}
|
}
|
||||||
memset(ptr, 0, mod->init_size);
|
memset(ptr, 0, mod->init_size);
|
||||||
mod->module_init = ptr;
|
mod->module_init = ptr;
|
||||||
|
@ -2443,10 +2457,8 @@ static struct module *move_module(struct module *mod,
|
||||||
DEBUGP("\t0x%lx %s\n",
|
DEBUGP("\t0x%lx %s\n",
|
||||||
sechdrs[i].sh_addr, secstrings + sechdrs[i].sh_name);
|
sechdrs[i].sh_addr, secstrings + sechdrs[i].sh_name);
|
||||||
}
|
}
|
||||||
/* Module has been moved. */
|
|
||||||
mod = (void *)sechdrs[modindex].sh_addr;
|
return 0;
|
||||||
kmemleak_load_module(mod, hdr, sechdrs, secstrings);
|
|
||||||
return mod;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static int check_module_license_and_versions(struct module *mod,
|
static int check_module_license_and_versions(struct module *mod,
|
||||||
|
@ -2503,6 +2515,76 @@ static void flush_module_icache(const struct module *mod)
|
||||||
set_fs(old_fs);
|
set_fs(old_fs);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static struct module *layout_and_allocate(struct load_info *info)
|
||||||
|
{
|
||||||
|
/* Module within temporary copy. */
|
||||||
|
struct module *mod;
|
||||||
|
int err;
|
||||||
|
|
||||||
|
mod = setup_load_info(info);
|
||||||
|
if (IS_ERR(mod))
|
||||||
|
return mod;
|
||||||
|
|
||||||
|
err = check_modinfo(mod, info->sechdrs, info->index.info, info->index.vers);
|
||||||
|
if (err)
|
||||||
|
return ERR_PTR(err);
|
||||||
|
|
||||||
|
/* Allow arches to frob section contents and sizes. */
|
||||||
|
err = module_frob_arch_sections(info->hdr, info->sechdrs, info->secstrings, mod);
|
||||||
|
if (err < 0)
|
||||||
|
goto free_args;
|
||||||
|
|
||||||
|
if (info->index.pcpu) {
|
||||||
|
/* We have a special allocation for this section. */
|
||||||
|
err = percpu_modalloc(mod, info->sechdrs[info->index.pcpu].sh_size,
|
||||||
|
info->sechdrs[info->index.pcpu].sh_addralign);
|
||||||
|
if (err)
|
||||||
|
goto free_args;
|
||||||
|
info->sechdrs[info->index.pcpu].sh_flags &= ~(unsigned long)SHF_ALLOC;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Determine total sizes, and put offsets in sh_entsize. For now
|
||||||
|
this is done generically; there doesn't appear to be any
|
||||||
|
special cases for the architectures. */
|
||||||
|
layout_sections(mod, info->hdr, info->sechdrs, info->secstrings);
|
||||||
|
|
||||||
|
info->strmap = kzalloc(BITS_TO_LONGS(info->sechdrs[info->index.str].sh_size)
|
||||||
|
* sizeof(long), GFP_KERNEL);
|
||||||
|
if (!info->strmap) {
|
||||||
|
err = -ENOMEM;
|
||||||
|
goto free_percpu;
|
||||||
|
}
|
||||||
|
info->symoffs = layout_symtab(mod, info->sechdrs, info->index.sym, info->index.str, info->hdr,
|
||||||
|
info->secstrings, &info->stroffs, info->strmap);
|
||||||
|
|
||||||
|
/* Allocate and move to the final place */
|
||||||
|
err = move_module(mod, info->hdr, info->sechdrs, info->secstrings, info->index.mod);
|
||||||
|
if (err)
|
||||||
|
goto free_strmap;
|
||||||
|
|
||||||
|
/* Module has been copied to its final place now: return it. */
|
||||||
|
mod = (void *)info->sechdrs[info->index.mod].sh_addr;
|
||||||
|
kmemleak_load_module(mod, info->hdr, info->sechdrs, info->secstrings);
|
||||||
|
return mod;
|
||||||
|
|
||||||
|
free_strmap:
|
||||||
|
kfree(info->strmap);
|
||||||
|
free_percpu:
|
||||||
|
percpu_modfree(mod);
|
||||||
|
free_args:
|
||||||
|
kfree(info->args);
|
||||||
|
return ERR_PTR(err);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* mod is no longer valid after this! */
|
||||||
|
static void module_deallocate(struct module *mod, struct load_info *info)
|
||||||
|
{
|
||||||
|
kfree(info->strmap);
|
||||||
|
percpu_modfree(mod);
|
||||||
|
module_free(mod, mod->module_init);
|
||||||
|
module_free(mod, mod->module_core);
|
||||||
|
}
|
||||||
|
|
||||||
/* Allocate and load the module: note that size of section 0 is always
|
/* Allocate and load the module: note that size of section 0 is always
|
||||||
zero, and we rely on this for optional sections. */
|
zero, and we rely on this for optional sections. */
|
||||||
static noinline struct module *load_module(void __user *umod,
|
static noinline struct module *load_module(void __user *umod,
|
||||||
|
@ -2512,78 +2594,28 @@ static noinline struct module *load_module(void __user *umod,
|
||||||
struct load_info info = { NULL, };
|
struct load_info info = { NULL, };
|
||||||
struct module *mod;
|
struct module *mod;
|
||||||
long err;
|
long err;
|
||||||
unsigned long symoffs, stroffs, *strmap;
|
|
||||||
void __percpu *percpu;
|
|
||||||
struct _ddebug *debug = NULL;
|
struct _ddebug *debug = NULL;
|
||||||
unsigned int num_debug = 0;
|
unsigned int num_debug = 0;
|
||||||
|
|
||||||
DEBUGP("load_module: umod=%p, len=%lu, uargs=%p\n",
|
DEBUGP("load_module: umod=%p, len=%lu, uargs=%p\n",
|
||||||
umod, len, uargs);
|
umod, len, uargs);
|
||||||
|
|
||||||
err = copy_and_check(&info, umod, len);
|
/* Copy in the blobs from userspace, check they are vaguely sane. */
|
||||||
|
err = copy_and_check(&info, umod, len, uargs);
|
||||||
if (err)
|
if (err)
|
||||||
return ERR_PTR(err);
|
return ERR_PTR(err);
|
||||||
|
|
||||||
mod = setup_load_info(&info);
|
/* Figure out module layout, and allocate all the memory. */
|
||||||
|
mod = layout_and_allocate(&info);
|
||||||
if (IS_ERR(mod)) {
|
if (IS_ERR(mod)) {
|
||||||
err = PTR_ERR(mod);
|
err = PTR_ERR(mod);
|
||||||
goto free_hdr;
|
goto free_copy;
|
||||||
}
|
|
||||||
|
|
||||||
err = check_modinfo(mod, info.sechdrs, info.index.info, info.index.vers);
|
|
||||||
if (err)
|
|
||||||
goto free_hdr;
|
|
||||||
|
|
||||||
/* Now copy in args */
|
|
||||||
info.args = strndup_user(uargs, ~0UL >> 1);
|
|
||||||
if (IS_ERR(info.args)) {
|
|
||||||
err = PTR_ERR(info.args);
|
|
||||||
goto free_hdr;
|
|
||||||
}
|
|
||||||
|
|
||||||
strmap = kzalloc(BITS_TO_LONGS(info.sechdrs[info.index.str].sh_size)
|
|
||||||
* sizeof(long), GFP_KERNEL);
|
|
||||||
if (!strmap) {
|
|
||||||
err = -ENOMEM;
|
|
||||||
goto free_mod;
|
|
||||||
}
|
|
||||||
|
|
||||||
mod->state = MODULE_STATE_COMING;
|
|
||||||
|
|
||||||
/* Allow arches to frob section contents and sizes. */
|
|
||||||
err = module_frob_arch_sections(info.hdr, info.sechdrs, info.secstrings, mod);
|
|
||||||
if (err < 0)
|
|
||||||
goto free_mod;
|
|
||||||
|
|
||||||
if (info.index.pcpu) {
|
|
||||||
/* We have a special allocation for this section. */
|
|
||||||
err = percpu_modalloc(mod, info.sechdrs[info.index.pcpu].sh_size,
|
|
||||||
info.sechdrs[info.index.pcpu].sh_addralign);
|
|
||||||
if (err)
|
|
||||||
goto free_mod;
|
|
||||||
info.sechdrs[info.index.pcpu].sh_flags &= ~(unsigned long)SHF_ALLOC;
|
|
||||||
}
|
|
||||||
/* Keep this around for failure path. */
|
|
||||||
percpu = mod_percpu(mod);
|
|
||||||
|
|
||||||
/* Determine total sizes, and put offsets in sh_entsize. For now
|
|
||||||
this is done generically; there doesn't appear to be any
|
|
||||||
special cases for the architectures. */
|
|
||||||
layout_sections(mod, info.hdr, info.sechdrs, info.secstrings);
|
|
||||||
symoffs = layout_symtab(mod, info.sechdrs, info.index.sym, info.index.str, info.hdr,
|
|
||||||
info.secstrings, &stroffs, strmap);
|
|
||||||
|
|
||||||
/* Allocate and move to the final place */
|
|
||||||
mod = move_module(mod, info.hdr, info.sechdrs, info.secstrings, info.index.mod);
|
|
||||||
if (IS_ERR(mod)) {
|
|
||||||
err = PTR_ERR(mod);
|
|
||||||
goto free_percpu;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Now we've moved module, initialize linked lists, etc. */
|
/* Now we've moved module, initialize linked lists, etc. */
|
||||||
err = module_unload_init(mod);
|
err = module_unload_init(mod);
|
||||||
if (err)
|
if (err)
|
||||||
goto free_init;
|
goto free_module;
|
||||||
|
|
||||||
/* Now we've got everything in the final locations, we can
|
/* Now we've got everything in the final locations, we can
|
||||||
* find optional sections. */
|
* find optional sections. */
|
||||||
|
@ -2600,11 +2632,11 @@ static noinline struct module *load_module(void __user *umod,
|
||||||
err = simplify_symbols(info.sechdrs, info.index.sym, info.strtab, info.index.vers, info.index.pcpu,
|
err = simplify_symbols(info.sechdrs, info.index.sym, info.strtab, info.index.vers, info.index.pcpu,
|
||||||
mod);
|
mod);
|
||||||
if (err < 0)
|
if (err < 0)
|
||||||
goto cleanup;
|
goto free_modinfo;
|
||||||
|
|
||||||
err = apply_relocations(mod, info.hdr, info.sechdrs, info.index.sym, info.index.str);
|
err = apply_relocations(mod, info.hdr, info.sechdrs, info.index.sym, info.index.str);
|
||||||
if (err < 0)
|
if (err < 0)
|
||||||
goto cleanup;
|
goto free_modinfo;
|
||||||
|
|
||||||
/* Set up and sort exception table */
|
/* Set up and sort exception table */
|
||||||
mod->extable = section_objs(info.hdr, info.sechdrs, info.secstrings, "__ex_table",
|
mod->extable = section_objs(info.hdr, info.sechdrs, info.secstrings, "__ex_table",
|
||||||
|
@ -2615,9 +2647,7 @@ static noinline struct module *load_module(void __user *umod,
|
||||||
percpu_modcopy(mod, (void *)info.sechdrs[info.index.pcpu].sh_addr,
|
percpu_modcopy(mod, (void *)info.sechdrs[info.index.pcpu].sh_addr,
|
||||||
info.sechdrs[info.index.pcpu].sh_size);
|
info.sechdrs[info.index.pcpu].sh_size);
|
||||||
|
|
||||||
add_kallsyms(mod, &info, symoffs, stroffs, strmap);
|
add_kallsyms(mod, &info);
|
||||||
kfree(strmap);
|
|
||||||
strmap = NULL;
|
|
||||||
|
|
||||||
if (!mod->taints)
|
if (!mod->taints)
|
||||||
debug = section_objs(info.hdr, info.sechdrs, info.secstrings, "__verbose",
|
debug = section_objs(info.hdr, info.sechdrs, info.secstrings, "__verbose",
|
||||||
|
@ -2625,12 +2655,14 @@ static noinline struct module *load_module(void __user *umod,
|
||||||
|
|
||||||
err = module_finalize(info.hdr, info.sechdrs, mod);
|
err = module_finalize(info.hdr, info.sechdrs, mod);
|
||||||
if (err < 0)
|
if (err < 0)
|
||||||
goto cleanup;
|
goto free_modinfo;
|
||||||
|
|
||||||
flush_module_icache(mod);
|
flush_module_icache(mod);
|
||||||
|
|
||||||
mod->args = info.args;
|
mod->args = info.args;
|
||||||
|
|
||||||
|
mod->state = MODULE_STATE_COMING;
|
||||||
|
|
||||||
/* Now sew it into the lists so we can get lockdep and oops
|
/* Now sew it into the lists so we can get lockdep and oops
|
||||||
* info during argument parsing. Noone should access us, since
|
* info during argument parsing. Noone should access us, since
|
||||||
* strong_try_module_get() will fail.
|
* strong_try_module_get() will fail.
|
||||||
|
@ -2666,8 +2698,9 @@ static noinline struct module *load_module(void __user *umod,
|
||||||
add_sect_attrs(mod, info.hdr->e_shnum, info.secstrings, info.sechdrs);
|
add_sect_attrs(mod, info.hdr->e_shnum, info.secstrings, info.sechdrs);
|
||||||
add_notes_attrs(mod, info.hdr->e_shnum, info.secstrings, info.sechdrs);
|
add_notes_attrs(mod, info.hdr->e_shnum, info.secstrings, info.sechdrs);
|
||||||
|
|
||||||
/* Get rid of temporary copy */
|
/* Get rid of temporary copy and strmap. */
|
||||||
vfree(info.hdr);
|
kfree(info.strmap);
|
||||||
|
free_copy(&info);
|
||||||
|
|
||||||
trace_module_load(mod);
|
trace_module_load(mod);
|
||||||
|
|
||||||
|
@ -2684,21 +2717,14 @@ static noinline struct module *load_module(void __user *umod,
|
||||||
mutex_unlock(&module_mutex);
|
mutex_unlock(&module_mutex);
|
||||||
synchronize_sched();
|
synchronize_sched();
|
||||||
module_arch_cleanup(mod);
|
module_arch_cleanup(mod);
|
||||||
cleanup:
|
free_modinfo:
|
||||||
free_modinfo(mod);
|
free_modinfo(mod);
|
||||||
free_unload:
|
free_unload:
|
||||||
module_unload_free(mod);
|
module_unload_free(mod);
|
||||||
free_init:
|
free_module:
|
||||||
module_free(mod, mod->module_init);
|
module_deallocate(mod, &info);
|
||||||
module_free(mod, mod->module_core);
|
free_copy:
|
||||||
/* mod will be freed with core. Don't access it beyond this line! */
|
free_copy(&info);
|
||||||
free_percpu:
|
|
||||||
free_percpu(percpu);
|
|
||||||
free_mod:
|
|
||||||
kfree(info.args);
|
|
||||||
kfree(strmap);
|
|
||||||
free_hdr:
|
|
||||||
vfree(info.hdr);
|
|
||||||
return ERR_PTR(err);
|
return ERR_PTR(err);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Загрузка…
Ссылка в новой задаче