diff --git a/fs/deepin_err_notify.c b/fs/deepin_err_notify.c index f3c6a6f0ce849..ac1fdce5ee80e 100644 --- a/fs/deepin_err_notify.c +++ b/fs/deepin_err_notify.c @@ -61,54 +61,56 @@ int deepin_err_notify_enabled(void) return deepin_err_notify_initialized && deepin_err_notify_enable; } +/** + * deepin_err_notify_should_send - Check if error notification should be sent + * + * This function checks both the enable status and rate limiting to determine + * whether an error notification should be sent. + * + * Return: 1 if notification should be sent, 0 otherwise + */ +int deepin_err_notify_should_send(void) +{ + + /* + * Rate limiting: Allow 20 calls per 5 seconds. + * Rationale: Prevent excessive error notifications under high load, + * which could overwhelm the monitoring system or cause log flooding. + * 20 notifications per 5 seconds is sufficient to capture relevant + * filesystem errors without missing critical events. + */ + static DEFINE_RATELIMIT_STATE(deepin_err_notify_ratelimit, 5 * HZ, 20); + + if (!deepin_err_notify_enabled()) + return 0; + + return __ratelimit(&deepin_err_notify_ratelimit); +} + +static int +prepare_and_notify_fs_error(const struct deepin_path_last *path_lasts, + int path_last_count) +{ + /* TODO: Implement in next commit */ + return -EOPNOTSUPP; +} + /* Check if overlay filesystem is mounted on /usr and send read only error notification */ -void deepin_check_and_notify_ro_fs_err(const struct path *path, +void deepin_check_and_notify_ro_fs_err(const struct deepin_path_last *path_last, const char *func_name) { - char *path_buf = NULL; - char *full_path = ""; - /* Rate limiting: allow 100 calls per 5 seconds */ - static DEFINE_RATELIMIT_STATE(deepin_ro_fs_err_ratelimit, - 5 * HZ, /* 5 seconds interval */ - 100); /* 100 calls per interval */ - - /* Check rate limit before proceeding */ - if (!__ratelimit(&deepin_ro_fs_err_ratelimit)) - return; - - /* Early return if path or path->mnt is invalid */ - if (!path || !path->mnt || !path->mnt->mnt_sb) - return; + int ret; - /* Use filesystem callback to decide if notification should be sent. - * If filesystem implements the callback, use it. - */ - if (path->mnt->mnt_sb->s_op && - path->mnt->mnt_sb->s_op->deepin_should_notify_error) { - if (!path->mnt->mnt_sb->s_op->deepin_should_notify_error(path->mnt->mnt_sb)) - return; - } else { - /* If filesystem does not implement the callback, return immediately. */ + /* Early return if path_last is invalid */ + if (!path_last) return; - } + ret = prepare_and_notify_fs_error(path_last, 1); - /* Attempt to get the full path. - * Dynamic allocation is used to avoid excessive frame size. - */ - if (path->dentry) { - path_buf = kmalloc(PATH_MAX, GFP_KERNEL); - if (path_buf) { - char *p = NULL; - - p = d_path(path, path_buf, PATH_MAX); - if (!IS_ERR(p)) - full_path = p; - } + if (ret < 0) { + pr_err( + "deepin_err_notify: Failed to send notification to userspace: %d\n", + ret); } - - deepin_send_ro_fs_err_notification(full_path, func_name); - - kfree(path_buf); } /* Define multicast group */ @@ -192,6 +194,22 @@ void deepin_send_ro_fs_err_notification(const char *filename, kfree_skb(skb); } +/** + * deepin_put_path_last - Release resources held by deepin_path_last structure + * @path_last: Pointer to the deepin_path_last structure to release + * + * This function releases the path reference and frees the allocated filename + * string in the deepin_path_last structure. + */ +void deepin_put_path_last(struct deepin_path_last *path_last) +{ + if (path_last) { + path_put(&path_last->path); + kfree(path_last->last); + path_last->last = NULL; + } +} + /* sysctl table and initialization */ #ifdef CONFIG_SYSCTL static struct ctl_table deepin_err_notify_sysctls[] = { @@ -239,5 +257,22 @@ static int __init deepin_err_notify_init(void) return 0; } +/** + * deepin_notify_rename_ro_fs_err - Notify read-only filesystem errors during rename + * @old_last: qstr for old filename + * @new_last: qstr for new filename + * @old_path: path of old parent directory + * @new_path: path of new parent directory + * + * This function is called when a rename operation fails with EROFS error. + */ +void deepin_notify_rename_ro_fs_err(const struct qstr *old_last, + const struct qstr *new_last, + const struct path *old_path, + const struct path *new_path) +{ + /* Simplified implementation - will be enhanced in next commit */ +} + /* Use fs_initcall to ensure initialization before file system operations */ fs_initcall(deepin_err_notify_init); diff --git a/fs/internal.h b/fs/internal.h index 09e315210ff3c..3391551a582f4 100644 --- a/fs/internal.h +++ b/fs/internal.h @@ -67,10 +67,33 @@ int deepin_get_path_for_err_notify(int dfd, struct filename *name, struct path * /* * deepin_err_notify.c */ +struct deepin_path_last { + struct path path; + const char *last; +}; + +int deepin_lookup_path_or_parent(int dfd, struct filename *name, + unsigned int flags, + struct deepin_path_last *result_path_last); + int deepin_err_notify_enabled(void); -void deepin_check_and_notify_ro_fs_err(const struct path *path, const char *func_name); +int deepin_err_notify_should_send(void); +void deepin_check_and_notify_ro_fs_err(const struct deepin_path_last *path_last, + const char *func_name); +void deepin_notify_rename_ro_fs_err(const struct qstr *old_last, + const struct qstr *new_last, + const struct path *old_path, + const struct path *new_path); +void deepin_put_path_last(struct deepin_path_last *path_last); void deepin_send_ro_fs_err_notification(const char *filename, const char *func_name); +#ifdef CONFIG_DEEPIN_ERR_NOTIFY +#define deepin_should_notify_ro_fs_err(error) \ + unlikely(((error) == -EROFS) && deepin_err_notify_should_send()) +#else +#define deepin_should_notify_ro_fs_err(error) 0 +#endif + /* * namespace.c */ diff --git a/fs/ioctl.c b/fs/ioctl.c index 474bc8eebff94..85260a3598f89 100644 --- a/fs/ioctl.c +++ b/fs/ioctl.c @@ -903,10 +903,13 @@ SYSCALL_DEFINE3(ioctl, unsigned int, fd, unsigned int, cmd, unsigned long, arg) if (error == -ENOIOCTLCMD) error = vfs_ioctl(f.file, cmd, arg); -#ifdef CONFIG_DEEPIN_ERR_NOTIFY - if (unlikely((error == -EROFS) && deepin_err_notify_enabled())) - deepin_check_and_notify_ro_fs_err(&f.file->f_path, "ioctl"); -#endif /* CONFIG_DEEPIN_ERR_NOTIFY */ + if (deepin_should_notify_ro_fs_err(error)) { + struct deepin_path_last tmp_path_last = { + .path = f.file->f_path, + .last = NULL + }; + deepin_check_and_notify_ro_fs_err(&tmp_path_last, "ioctl"); + } out: fdput(f); diff --git a/fs/namei.c b/fs/namei.c index 60c9a0c7474b0..69b2900f87b74 100644 --- a/fs/namei.c +++ b/fs/namei.c @@ -4073,19 +4073,14 @@ static int do_mknodat(int dfd, struct filename *name, umode_t mode, goto retry; } out1: -#ifdef CONFIG_DEEPIN_ERR_NOTIFY - if (unlikely((error == -EROFS) && deepin_err_notify_enabled())) { - struct path file_path; - int get_path_err; - - get_path_err = - deepin_get_path_for_err_notify(dfd, name, &file_path); - if (!get_path_err) { - deepin_check_and_notify_ro_fs_err(&file_path, "mknod"); - path_put(&file_path); + if (deepin_should_notify_ro_fs_err(error)) { + struct deepin_path_last path_last; + + if (!deepin_lookup_path_or_parent(dfd, name, lookup_flags, &path_last)) { + deepin_check_and_notify_ro_fs_err(&path_last, "mknod"); + deepin_put_path_last(&path_last); } } -#endif /* CONFIG_DEEPIN_ERR_NOTIFY */ putname(name); return error; } @@ -4169,19 +4164,14 @@ int do_mkdirat(int dfd, struct filename *name, umode_t mode) goto retry; } out_putname: -#ifdef CONFIG_DEEPIN_ERR_NOTIFY - if (unlikely((error == -EROFS) && deepin_err_notify_enabled())) { - struct path file_path; - int get_path_err; - - get_path_err = - deepin_get_path_for_err_notify(dfd, name, &file_path); - if (!get_path_err) { - deepin_check_and_notify_ro_fs_err(&file_path, "mkdir"); - path_put(&file_path); + if (deepin_should_notify_ro_fs_err(error)) { + struct deepin_path_last path_last; + + if (!deepin_lookup_path_or_parent(dfd, name, lookup_flags, &path_last)) { + deepin_check_and_notify_ro_fs_err(&path_last, "mkdir"); + deepin_put_path_last(&path_last); } } -#endif /* CONFIG_DEEPIN_ERR_NOTIFY */ putname(name); return error; } @@ -4299,21 +4289,18 @@ int do_rmdir(int dfd, struct filename *name) inode_unlock(path.dentry->d_inode); mnt_drop_write(path.mnt); exit2: -#ifdef CONFIG_DEEPIN_ERR_NOTIFY - if (unlikely((error == -EROFS) && deepin_err_notify_enabled())) { - dentry = lookup_one_qstr_excl(&last, path.dentry, 0); - if (!IS_ERR(dentry)) { - if (d_is_positive(dentry)) { - // dentry is positive, so we can get the path - struct path file_path = { .mnt = path.mnt, - .dentry = dentry }; - deepin_check_and_notify_ro_fs_err(&file_path, + if (deepin_should_notify_ro_fs_err(error)) { + struct deepin_path_last path_last; + + if (!deepin_lookup_path_or_parent(dfd, name, lookup_flags, &path_last)) { + if (!path_last.last) { + /* File exists, notify error */ + deepin_check_and_notify_ro_fs_err(&path_last, "rmdir"); } - dput(dentry); + deepin_put_path_last(&path_last); } } -#endif /* CONFIG_DEEPIN_ERR_NOTIFY */ path_put(&path); if (retry_estale(error, lookup_flags)) { lookup_flags |= LOOKUP_REVAL; @@ -4459,21 +4446,18 @@ int do_unlinkat(int dfd, struct filename *name) } mnt_drop_write(path.mnt); exit2: -#ifdef CONFIG_DEEPIN_ERR_NOTIFY - if (unlikely((error == -EROFS) && deepin_err_notify_enabled())) { - dentry = lookup_one_qstr_excl(&last, path.dentry, 0); - if (!IS_ERR(dentry)) { - if (d_is_positive(dentry)) { - // dentry is positive, so we can get the path - struct path file_path = { .mnt = path.mnt, - .dentry = dentry }; - deepin_check_and_notify_ro_fs_err(&file_path, + if (deepin_should_notify_ro_fs_err(error)) { + struct deepin_path_last path_last; + + if (!deepin_lookup_path_or_parent(dfd, name, lookup_flags, &path_last)) { + if (!path_last.last) { + /* File exists, notify error */ + deepin_check_and_notify_ro_fs_err(&path_last, "unlink"); } - dput(dentry); + deepin_put_path_last(&path_last); } } -#endif /* CONFIG_DEEPIN_ERR_NOTIFY */ path_put(&path); if (retry_estale(error, lookup_flags)) { lookup_flags |= LOOKUP_REVAL; @@ -4574,20 +4558,15 @@ int do_symlinkat(struct filename *from, int newdfd, struct filename *to) goto retry; } out_putnames: -#ifdef CONFIG_DEEPIN_ERR_NOTIFY - if (unlikely((error == -EROFS) && deepin_err_notify_enabled())) { - struct path file_path; - int get_path_err; - - get_path_err = - deepin_get_path_for_err_notify(newdfd, to, &file_path); - if (!get_path_err) { - deepin_check_and_notify_ro_fs_err(&file_path, + if (deepin_should_notify_ro_fs_err(error)) { + struct deepin_path_last path_last; + + if (!deepin_lookup_path_or_parent(newdfd, to, lookup_flags, &path_last)) { + deepin_check_and_notify_ro_fs_err(&path_last, "symlink"); - path_put(&file_path); + deepin_put_path_last(&path_last); } } -#endif /* CONFIG_DEEPIN_ERR_NOTIFY */ putname(to); putname(from); return error; @@ -4766,19 +4745,14 @@ int do_linkat(int olddfd, struct filename *old, int newdfd, goto retry; } out_putpath: -#ifdef CONFIG_DEEPIN_ERR_NOTIFY - if (unlikely((error == -EROFS) && deepin_err_notify_enabled())) { - struct path file_path; - int get_path_err; - - get_path_err = - deepin_get_path_for_err_notify(newdfd, new, &file_path); - if (!get_path_err) { - deepin_check_and_notify_ro_fs_err(&file_path, "link"); - path_put(&file_path); + if (deepin_should_notify_ro_fs_err(error)) { + struct deepin_path_last path_last; + + if (!deepin_lookup_path_or_parent(newdfd, new, how, &path_last)) { + deepin_check_and_notify_ro_fs_err(&path_last, "link"); + deepin_put_path_last(&path_last); } } -#endif /* CONFIG_DEEPIN_ERR_NOTIFY */ path_put(&old_path); out_putnames: putname(old); @@ -5133,22 +5107,8 @@ int do_renameat2(int olddfd, struct filename *from, int newdfd, } mnt_drop_write(old_path.mnt); exit2: -#ifdef CONFIG_DEEPIN_ERR_NOTIFY - if (unlikely((error == -EROFS) && deepin_err_notify_enabled())) { - old_dentry = - lookup_one_qstr_excl(&old_last, old_path.dentry, 0); - if (!IS_ERR(old_dentry)) { - if (d_is_positive(old_dentry)) { - struct path file_path = { .mnt = old_path.mnt, - .dentry = - old_dentry }; - deepin_check_and_notify_ro_fs_err(&file_path, - "rename"); - } - dput(old_dentry); - } - } -#endif /* CONFIG_DEEPIN_ERR_NOTIFY */ + if (deepin_should_notify_ro_fs_err(error)) + deepin_notify_rename_ro_fs_err(&old_last, &new_last, &old_path, &new_path); if (retry_estale(error, lookup_flags)) should_retry = true; path_put(&new_path); @@ -5368,37 +5328,76 @@ const struct inode_operations page_symlink_inode_operations = { EXPORT_SYMBOL(page_symlink_inode_operations); #ifdef CONFIG_DEEPIN_ERR_NOTIFY -int deepin_get_path_for_err_notify(int dfd, struct filename *name, - struct path *result_path) +/** + * deepin_lookup_path_or_parent - Prepare path info for read-only FS error notification + * @dfd: Directory file descriptor used as lookup base + * @name: Filename to look up (struct filename) + * @flags: Lookup flags (e.g., LOOKUP_DIRECTORY, LOOKUP_FOLLOW) + * @result_path_last: Output structure carrying the resolved parent path and + * optionally the last component string + * + * Memory/Lifetime management: + * - result_path_last->path: + * The path returned by filename_lookup() or filename_parentat() already + * holds a reference count. You MUST release it after use. Preferred: + * deepin_put_path_last(&pl), which will call path_put() for you. + * Alternatively, call path_put() manually if you manage the string separately. + * + * - result_path_last->last: + * In the -ENOENT (parent found) case, the last component string is duplicated + * via kstrdup() and MUST be freed with kfree(). deepin_put_path_last(&pl) + * will free it for you. If the full path was resolved, this field is set + * to NULL and no extra string free is needed. + * + * Recommended usage pattern: + * struct deepin_path_last pl; + * if (!deepin_lookup_path_or_parent(dfd, name, lookup_flags, &pl)) { + * deepin_check_and_notify_ro_fs_err(&pl, "op"); + * deepin_put_path_last(&pl); // releases path and frees pl.last if allocated + * } + * + * Return: 0 on success, negative errno on failure. + */ +int deepin_lookup_path_or_parent(int dfd, struct filename *name, + unsigned int flags, + struct deepin_path_last *result_path_last) { + struct path result_path; + struct path parent; struct qstr last; - struct path parent_path; int type; - struct dentry *dentry; int error; - error = filename_parentat(dfd, name, 0, &parent_path, &last, &type); + error = filename_lookup(dfd, name, flags, &result_path, NULL); + if (error == -ENOENT) { + error = filename_parentat(dfd, name, flags, &parent, &last, &type); + if (error) + return error; + if (unlikely(type != LAST_NORM)) { + path_put(&parent); + return -EINVAL; + } + /* Duplicate the filename string to avoid dangling pointer */ + result_path_last->last = kstrdup((const char *)last.name, GFP_KERNEL); + if (!result_path_last->last) { + path_put(&parent); + return -ENOMEM; + } + result_path_last->path = parent; + return 0; + } + if (error) return error; - dentry = lookup_one_qstr_excl(&last, parent_path.dentry, 0); - if (!IS_ERR(dentry)) { - result_path->mnt = parent_path.mnt; - result_path->dentry = dentry; - path_get(result_path); // Increment reference count - dput(dentry); - } else { - // If the file does not exist, use the parent directory - *result_path = parent_path; - path_get(result_path); - } - - path_put(&parent_path); + result_path_last->last = NULL; + result_path_last->path = result_path; return 0; } #else -int deepin_get_path_for_err_notify(int dfd, struct filename *name, - struct path *result_path) +int deepin_lookup_path_or_parent(int dfd, struct filename *name, + unsigned int flags, + struct deepin_path_last *result_path_last) { return -EOPNOTSUPP; } diff --git a/fs/open.c b/fs/open.c index 93620b8fdecba..d3bfdc444d0d9 100644 --- a/fs/open.c +++ b/fs/open.c @@ -136,10 +136,13 @@ long do_sys_truncate(const char __user *pathname, loff_t length) error = user_path_at(AT_FDCWD, pathname, lookup_flags, &path); if (!error) { error = vfs_truncate(&path, length); -#ifdef CONFIG_DEEPIN_ERR_NOTIFY - if (unlikely((error == -EROFS) && deepin_err_notify_enabled())) - deepin_check_and_notify_ro_fs_err(&path, "truncate"); -#endif /* CONFIG_DEEPIN_ERR_NOTIFY */ + if (deepin_should_notify_ro_fs_err(error)) { + struct deepin_path_last tmp_path_last = { + .path = path, + .last = NULL + }; + deepin_check_and_notify_ro_fs_err(&tmp_path_last, "truncate"); + } path_put(&path); } if (retry_estale(error, lookup_flags)) { @@ -717,10 +720,13 @@ static int do_fchmodat(int dfd, const char __user *filename, umode_t mode, error = user_path_at(dfd, filename, lookup_flags, &path); if (!error) { error = chmod_common(&path, mode); -#ifdef CONFIG_DEEPIN_ERR_NOTIFY - if (unlikely((error == -EROFS) && deepin_err_notify_enabled())) - deepin_check_and_notify_ro_fs_err(&path, "chmod"); -#endif /* CONFIG_DEEPIN_ERR_NOTIFY */ + if (deepin_should_notify_ro_fs_err(error)) { + struct deepin_path_last tmp_path_last = { + .path = path, + .last = NULL + }; + deepin_check_and_notify_ro_fs_err(&tmp_path_last, "chmod"); + } path_put(&path); if (retry_estale(error, lookup_flags)) { lookup_flags |= LOOKUP_REVAL; @@ -846,10 +852,13 @@ int do_fchownat(int dfd, const char __user *filename, uid_t user, gid_t group, error = chown_common(&path, user, group); mnt_drop_write(path.mnt); out_release: -#ifdef CONFIG_DEEPIN_ERR_NOTIFY - if (unlikely((error == -EROFS) && deepin_err_notify_enabled())) - deepin_check_and_notify_ro_fs_err(&path, "chown"); -#endif /* CONFIG_DEEPIN_ERR_NOTIFY */ + if (deepin_should_notify_ro_fs_err(error)) { + struct deepin_path_last tmp_path_last = { + .path = path, + .last = NULL + }; + deepin_check_and_notify_ro_fs_err(&tmp_path_last, "chown"); + } path_put(&path); if (retry_estale(error, lookup_flags)) { lookup_flags |= LOOKUP_REVAL; @@ -1456,19 +1465,15 @@ static long do_sys_openat2(int dfd, const char __user *filename, if (IS_ERR(f)) { put_unused_fd(fd); fd = PTR_ERR(f); -#ifdef CONFIG_DEEPIN_ERR_NOTIFY - if (unlikely(fd == -EROFS) && - deepin_err_notify_enabled()) { - struct path file_path; - int get_path_err; - - get_path_err = deepin_get_path_for_err_notify(dfd, tmp, &file_path); - if (!get_path_err) { - deepin_check_and_notify_ro_fs_err(&file_path, "open"); - path_put(&file_path); + if (deepin_should_notify_ro_fs_err(fd)) { + struct deepin_path_last path_last; + + if (!deepin_lookup_path_or_parent(dfd, tmp, op.lookup_flags, + &path_last)) { + deepin_check_and_notify_ro_fs_err(&path_last, "open"); + deepin_put_path_last(&path_last); } } -#endif /* CONFIG_DEEPIN_ERR_NOTIFY */ } else { fd_install(fd, f); } diff --git a/fs/utimes.c b/fs/utimes.c index 3e74c4c6741f5..51714c41bd0b0 100644 --- a/fs/utimes.c +++ b/fs/utimes.c @@ -99,10 +99,13 @@ static int do_utimes_path(int dfd, const char __user *filename, return error; error = vfs_utimes(&path, times); -#ifdef CONFIG_DEEPIN_ERR_NOTIFY - if (unlikely((error == -EROFS) && deepin_err_notify_enabled())) - deepin_check_and_notify_ro_fs_err(&path, "utime"); -#endif /* CONFIG_DEEPIN_ERR_NOTIFY */ + if (deepin_should_notify_ro_fs_err(error)) { + struct deepin_path_last tmp_path_last = { + .path = path, + .last = NULL + }; + deepin_check_and_notify_ro_fs_err(&tmp_path_last, "utime"); + } path_put(&path); if (retry_estale(error, lookup_flags)) { lookup_flags |= LOOKUP_REVAL; diff --git a/fs/xattr.c b/fs/xattr.c index 20324eb52e04c..0efd31508cdd5 100644 --- a/fs/xattr.c +++ b/fs/xattr.c @@ -658,10 +658,12 @@ static int path_setxattr(const char __user *pathname, if (!error) { error = do_setxattr(mnt_idmap(path.mnt), path.dentry, &ctx); mnt_drop_write(path.mnt); -#ifdef CONFIG_DEEPIN_ERR_NOTIFY - } else if (unlikely((error == -EROFS) && deepin_err_notify_enabled())) { - deepin_check_and_notify_ro_fs_err(&path, "setxattr"); -#endif /* CONFIG_DEEPIN_ERR_NOTIFY */ + } else if (deepin_should_notify_ro_fs_err(error)) { + struct deepin_path_last tmp_path_last = { + .path = path, + .last = NULL + }; + deepin_check_and_notify_ro_fs_err(&tmp_path_last, "setxattr"); } path_put(&path); if (retry_estale(error, lookup_flags)) { @@ -932,10 +934,12 @@ static int path_removexattr(const char __user *pathname, if (!error) { error = removexattr(mnt_idmap(path.mnt), path.dentry, kname); mnt_drop_write(path.mnt); -#ifdef CONFIG_DEEPIN_ERR_NOTIFY - } else if (unlikely((error == -EROFS) && deepin_err_notify_enabled())) { - deepin_check_and_notify_ro_fs_err(&path, "removexattr"); -#endif /* CONFIG_DEEPIN_ERR_NOTIFY */ + } else if (deepin_should_notify_ro_fs_err(error)) { + struct deepin_path_last tmp_path_last = { + .path = path, + .last = NULL + }; + deepin_check_and_notify_ro_fs_err(&tmp_path_last, "removexattr"); } path_put(&path); if (retry_estale(error, lookup_flags)) { diff --git a/include/linux/fs.h b/include/linux/fs.h index b188fe036954c..971e49d473e8c 100644 --- a/include/linux/fs.h +++ b/include/linux/fs.h @@ -2143,10 +2143,6 @@ struct super_operations { struct shrink_control *); void (*shutdown)(struct super_block *sb); - /* Check if filesystem wants to be notified of read-only errors */ -#ifdef CONFIG_DEEPIN_ERR_NOTIFY - bool (*deepin_should_notify_error)(struct super_block *sb); -#endif /* CONFIG_DEEPIN_ERR_NOTIFY */ DEEPIN_KABI_RESERVE(1) DEEPIN_KABI_RESERVE(2) DEEPIN_KABI_RESERVE(3)