};
 };
 
-struct bpf_sec_def;
-
-typedef int (*init_fn_t)(struct bpf_program *prog, long cookie);
-typedef int (*preload_fn_t)(struct bpf_program *prog, struct bpf_prog_load_opts *opts, long cookie);
-typedef struct bpf_link *(*attach_fn_t)(const struct bpf_program *prog, long cookie);
+typedef int (*libbpf_prog_setup_fn_t)(struct bpf_program *prog, long cookie);
+typedef int (*libbpf_prog_prepare_load_fn_t)(struct bpf_program *prog,
+                                            struct bpf_prog_load_opts *opts, long cookie);
+/* If auto-attach is not supported, callback should return 0 and set link to NULL */
+typedef int (*libbpf_prog_attach_fn_t)(const struct bpf_program *prog, long cookie,
+                                      struct bpf_link **link);
 
 /* stored as sec_def->cookie for all libbpf-supported SEC()s */
 enum sec_def_flags {
        enum bpf_attach_type expected_attach_type;
        long cookie;
 
-       init_fn_t init_fn;
-       preload_fn_t preload_fn;
-       attach_fn_t attach_fn;
+       libbpf_prog_setup_fn_t prog_setup_fn;
+       libbpf_prog_prepare_load_fn_t prog_prepare_load_fn;
+       libbpf_prog_attach_fn_t prog_attach_fn;
 };
 
 /*
 static int libbpf_find_attach_btf_id(struct bpf_program *prog, const char *attach_name,
                                     int *btf_obj_fd, int *btf_type_id);
 
-/* this is called as prog->sec_def->preload_fn for libbpf-supported sec_defs */
-static int libbpf_preload_prog(struct bpf_program *prog,
-                              struct bpf_prog_load_opts *opts, long cookie)
+/* this is called as prog->sec_def->prog_prepare_load_fn for libbpf-supported sec_defs */
+static int libbpf_prepare_prog_load(struct bpf_program *prog,
+                                   struct bpf_prog_load_opts *opts, long cookie)
 {
        enum sec_def_flags def = cookie;
 
        load_attr.fd_array = obj->fd_array;
 
        /* adjust load_attr if sec_def provides custom preload callback */
-       if (prog->sec_def && prog->sec_def->preload_fn) {
-               err = prog->sec_def->preload_fn(prog, &load_attr, prog->sec_def->cookie);
+       if (prog->sec_def && prog->sec_def->prog_prepare_load_fn) {
+               err = prog->sec_def->prog_prepare_load_fn(prog, &load_attr, prog->sec_def->cookie);
                if (err < 0) {
                        pr_warn("prog '%s': failed to prepare load attributes: %d\n",
                                prog->name, err);
                /* sec_def can have custom callback which should be called
                 * after bpf_program is initialized to adjust its properties
                 */
-               if (prog->sec_def->init_fn) {
-                       err = prog->sec_def->init_fn(prog, prog->sec_def->cookie);
+               if (prog->sec_def->prog_setup_fn) {
+                       err = prog->sec_def->prog_setup_fn(prog, prog->sec_def->cookie);
                        if (err < 0) {
                                pr_warn("prog '%s': failed to initialize: %d\n",
                                        prog->name, err);
        .prog_type = BPF_PROG_TYPE_##ptype,                                 \
        .expected_attach_type = atype,                                      \
        .cookie = (long)(flags),                                            \
-       .preload_fn = libbpf_preload_prog,                                  \
+       .prog_prepare_load_fn = libbpf_prepare_prog_load,                   \
        __VA_ARGS__                                                         \
 }
 
-static struct bpf_link *attach_kprobe(const struct bpf_program *prog, long cookie);
-static struct bpf_link *attach_tp(const struct bpf_program *prog, long cookie);
-static struct bpf_link *attach_raw_tp(const struct bpf_program *prog, long cookie);
-static struct bpf_link *attach_trace(const struct bpf_program *prog, long cookie);
-static struct bpf_link *attach_lsm(const struct bpf_program *prog, long cookie);
-static struct bpf_link *attach_iter(const struct bpf_program *prog, long cookie);
+static int attach_kprobe(const struct bpf_program *prog, long cookie, struct bpf_link **link);
+static int attach_tp(const struct bpf_program *prog, long cookie, struct bpf_link **link);
+static int attach_raw_tp(const struct bpf_program *prog, long cookie, struct bpf_link **link);
+static int attach_trace(const struct bpf_program *prog, long cookie, struct bpf_link **link);
+static int attach_lsm(const struct bpf_program *prog, long cookie, struct bpf_link **link);
+static int attach_iter(const struct bpf_program *prog, long cookie, struct bpf_link **link);
 
 static const struct bpf_sec_def section_defs[] = {
        SEC_DEF("socket",               SOCKET_FILTER, 0, SEC_NONE | SEC_SLOPPY_PFX),
                const struct bpf_sec_def *sec_def = §ion_defs[i];
 
                if (attach_type) {
-                       if (sec_def->preload_fn != libbpf_preload_prog)
+                       if (sec_def->prog_prepare_load_fn != libbpf_prepare_prog_load)
                                continue;
 
                        if (!(sec_def->cookie & SEC_ATTACHABLE))
                return libbpf_err(-EINVAL);
        }
 
-       if (sec_def->preload_fn != libbpf_preload_prog)
+       if (sec_def->prog_prepare_load_fn != libbpf_prepare_prog_load)
                return libbpf_err(-EINVAL);
        if (!(sec_def->cookie & SEC_ATTACHABLE))
                return libbpf_err(-EINVAL);
        return bpf_program__attach_kprobe_opts(prog, func_name, &opts);
 }
 
-static struct bpf_link *attach_kprobe(const struct bpf_program *prog, long cookie)
+static int attach_kprobe(const struct bpf_program *prog, long cookie, struct bpf_link **link)
 {
        DECLARE_LIBBPF_OPTS(bpf_kprobe_opts, opts);
        unsigned long offset = 0;
-       struct bpf_link *link;
        const char *func_name;
        char *func;
-       int n, err;
+       int n;
 
        opts.retprobe = str_has_pfx(prog->sec_name, "kretprobe/");
        if (opts.retprobe)
 
        n = sscanf(func_name, "%m[a-zA-Z0-9_.]+%li", &func, &offset);
        if (n < 1) {
-               err = -EINVAL;
                pr_warn("kprobe name is invalid: %s\n", func_name);
-               return libbpf_err_ptr(err);
+               return -EINVAL;
        }
        if (opts.retprobe && offset != 0) {
                free(func);
-               err = -EINVAL;
                pr_warn("kretprobes do not support offset specification\n");
-               return libbpf_err_ptr(err);
+               return -EINVAL;
        }
 
        opts.offset = offset;
-       link = bpf_program__attach_kprobe_opts(prog, func, &opts);
+       *link = bpf_program__attach_kprobe_opts(prog, func, &opts);
        free(func);
-       return link;
+       return libbpf_get_error(*link);
 }
 
 static void gen_uprobe_legacy_event_name(char *buf, size_t buf_sz,
        return bpf_program__attach_tracepoint_opts(prog, tp_category, tp_name, NULL);
 }
 
-static struct bpf_link *attach_tp(const struct bpf_program *prog, long cookie)
+static int attach_tp(const struct bpf_program *prog, long cookie, struct bpf_link **link)
 {
        char *sec_name, *tp_cat, *tp_name;
-       struct bpf_link *link;
 
        sec_name = strdup(prog->sec_name);
        if (!sec_name)
-               return libbpf_err_ptr(-ENOMEM);
+               return -ENOMEM;
 
        /* extract "tp/<category>/<name>" or "tracepoint/<category>/<name>" */
        if (str_has_pfx(prog->sec_name, "tp/"))
        tp_name = strchr(tp_cat, '/');
        if (!tp_name) {
                free(sec_name);
-               return libbpf_err_ptr(-EINVAL);
+               return -EINVAL;
        }
        *tp_name = '\0';
        tp_name++;
 
-       link = bpf_program__attach_tracepoint(prog, tp_cat, tp_name);
+       *link = bpf_program__attach_tracepoint(prog, tp_cat, tp_name);
        free(sec_name);
-       return link;
+       return libbpf_get_error(*link);
 }
 
 struct bpf_link *bpf_program__attach_raw_tracepoint(const struct bpf_program *prog,
        return link;
 }
 
-static struct bpf_link *attach_raw_tp(const struct bpf_program *prog, long cookie)
+static int attach_raw_tp(const struct bpf_program *prog, long cookie, struct bpf_link **link)
 {
        static const char *const prefixes[] = {
                "raw_tp/",
        if (!tp_name) {
                pr_warn("prog '%s': invalid section name '%s'\n",
                        prog->name, prog->sec_name);
-               return libbpf_err_ptr(-EINVAL);
+               return -EINVAL;
        }
 
-       return bpf_program__attach_raw_tracepoint(prog, tp_name);
+       *link = bpf_program__attach_raw_tracepoint(prog, tp_name);
+       return libbpf_get_error(link);
 }
 
 /* Common logic for all BPF program types that attach to a btf_id */
        return bpf_program__attach_btf_id(prog);
 }
 
-static struct bpf_link *attach_trace(const struct bpf_program *prog, long cookie)
+static int attach_trace(const struct bpf_program *prog, long cookie, struct bpf_link **link)
 {
-       return bpf_program__attach_trace(prog);
+       *link = bpf_program__attach_trace(prog);
+       return libbpf_get_error(*link);
 }
 
-static struct bpf_link *attach_lsm(const struct bpf_program *prog, long cookie)
+static int attach_lsm(const struct bpf_program *prog, long cookie, struct bpf_link **link)
 {
-       return bpf_program__attach_lsm(prog);
+       *link = bpf_program__attach_lsm(prog);
+       return libbpf_get_error(*link);
 }
 
 static struct bpf_link *
        return link;
 }
 
-static struct bpf_link *attach_iter(const struct bpf_program *prog, long cookie)
+static int attach_iter(const struct bpf_program *prog, long cookie, struct bpf_link **link)
 {
-       return bpf_program__attach_iter(prog, NULL);
+       *link = bpf_program__attach_iter(prog, NULL);
+       return libbpf_get_error(*link);
 }
 
 struct bpf_link *bpf_program__attach(const struct bpf_program *prog)
 {
-       if (!prog->sec_def || !prog->sec_def->attach_fn)
-               return libbpf_err_ptr(-ESRCH);
+       struct bpf_link *link = NULL;
+       int err;
+
+       if (!prog->sec_def || !prog->sec_def->prog_attach_fn)
+               return libbpf_err_ptr(-EOPNOTSUPP);
 
-       return prog->sec_def->attach_fn(prog, prog->sec_def->cookie);
+       err = prog->sec_def->prog_attach_fn(prog, prog->sec_def->cookie, &link);
+       if (err)
+               return libbpf_err_ptr(err);
+
+       /* When calling bpf_program__attach() explicitly, auto-attach support
+        * is expected to work, so NULL returned link is considered an error.
+        * This is different for skeleton's attach, see comment in
+        * bpf_object__attach_skeleton().
+        */
+       if (!link)
+               return libbpf_err_ptr(-EOPNOTSUPP);
+
+       return link;
 }
 
 static int bpf_link__detach_struct_ops(struct bpf_link *link)
                        continue;
 
                /* auto-attaching not supported for this program */
-               if (!prog->sec_def || !prog->sec_def->attach_fn)
+               if (!prog->sec_def || !prog->sec_def->prog_attach_fn)
                        continue;
 
-               *link = bpf_program__attach(prog);
-               err = libbpf_get_error(*link);
+               /* if user already set the link manually, don't attempt auto-attach */
+               if (*link)
+                       continue;
+
+               err = prog->sec_def->prog_attach_fn(prog, prog->sec_def->cookie, link);
                if (err) {
-                       pr_warn("failed to auto-attach program '%s': %d\n",
+                       pr_warn("prog '%s': failed to auto-attach: %d\n",
                                bpf_program__name(prog), err);
                        return libbpf_err(err);
                }
+
+               /* It's possible that for some SEC() definitions auto-attach
+                * is supported in some cases (e.g., if definition completely
+                * specifies target information), but is not in other cases.
+                * SEC("uprobe") is one such case. If user specified target
+                * binary and function name, such BPF program can be
+                * auto-attached. But if not, it shouldn't trigger skeleton's
+                * attach to fail. It should just be skipped.
+                * attach_fn signals such case with returning 0 (no error) and
+                * setting link to NULL.
+                */
        }
 
        return 0;