xaizek / vifm (License: GPLv2+) (since 2018-12-07)
Vifm is a file manager with curses interface, which provides Vi[m]-like environment for managing objects within file systems, extended with some useful ideas from mutt.
Commit d943783a33698f563ff2488743e9b025f3cc4706

Fix unstable UID/GID formatting in the UI
If the same id appeared on display in different forms, some instances
could be of wrong from due to caching that did not take the requested
form into account.

Thanks to vuenn.

See https://q2a.vifm.info/2354/show-uid-only
Author: xaizek
Author date (UTC): 2026-03-12 17:27
Committer name: xaizek
Committer date (UTC): 2026-03-12 17:37
Parent(s): 668fb3fd547e9b5a789f21436a647c4bbb20d4f4
Signing key: 99DC5E4DB05F6BE2
Tree: 49f4f9ec760f8bf61feb85735ecf52554444a703
File Lines added Lines deleted
ChangeLog 3 0
src/utils/utils_nix.c 42 32
tests/utils/uid_gid.c 35 0
File ChangeLog changed (mode: 100644) (index 92873de46..5cb80b7f5)
203 203 Fixed incorrect progress indicator for dp and do keys in compare view. Fixed incorrect progress indicator for dp and do keys in compare view.
204 204 Thanks to aleksejrs. Thanks to aleksejrs.
205 205
206 Fixed UID/GID occasionally not being formatted as requested (numeric vs.
207 symbolic form) in the UI. Thanks to vuenn.
208
206 209 0.14-beta to 0.14 (2025-02-08) 0.14-beta to 0.14 (2025-02-08)
207 210
208 211 Improved documentation on zh/zl menu keys a bit. Improved documentation on zh/zl menu keys a bit.
File src/utils/utils_nix.c changed (mode: 100644) (index e540b4bc2..b87a5b507)
... ... update_terminal_settings(void)
892 892 void void
893 893 get_uid_string(const dir_entry_t *entry, int as_num, size_t buf_len, char buf[]) get_uid_string(const dir_entry_t *entry, int as_num, size_t buf_len, char buf[])
894 894 { {
895 /* Cache for the last requested user id. */
895 /* Cache for the last user id requested in symbolic form. */
896 896 static uid_t last_uid = (uid_t)-1; static uid_t last_uid = (uid_t)-1;
897 897 static char uid_buf[26]; static char uid_buf[26];
898 898
899 /* Numeric form doesn't need caching. */
900 if(as_num)
901 {
902 snprintf(buf, buf_len, "%d", (int)entry->uid);
903 return;
904 }
905
899 906 if(entry->uid == last_uid) if(entry->uid == last_uid)
900 907 { {
901 908 copy_str(buf, buf_len, uid_buf); copy_str(buf, buf_len, uid_buf);
902 909 return; return;
903 910 } }
904 911
912 /* Numeric form is a fallback for the case of an unknown user id. */
905 913 last_uid = entry->uid; last_uid = entry->uid;
906 914 snprintf(uid_buf, sizeof(uid_buf), "%d", (int)last_uid); snprintf(uid_buf, sizeof(uid_buf), "%d", (int)last_uid);
907 915
908 if(!as_num)
916 enum { MAX_TRIES = 4 };
917 size_t size = MAX(sysconf(_SC_GETPW_R_SIZE_MAX) + 1, PATH_MAX);
918 int i;
919 for(i = 0; i < MAX_TRIES; ++i, size *= 2)
909 920 { {
910 enum { MAX_TRIES = 4 };
911 size_t size = MAX(sysconf(_SC_GETPW_R_SIZE_MAX) + 1, PATH_MAX);
912 int i;
913 for(i = 0; i < MAX_TRIES; ++i, size *= 2)
914 {
915 char buf[size];
916 struct passwd pwd_b;
917 struct passwd *pwd_buf;
921 char buf[size];
922 struct passwd pwd_b;
923 struct passwd *pwd_buf;
918 924
919 if(getpwuid_r(last_uid, &pwd_b, buf, sizeof(buf), &pwd_buf) == 0 &&
920 pwd_buf != NULL)
921 {
922 copy_str(uid_buf, sizeof(uid_buf), pwd_buf->pw_name);
923 break;
924 }
925 if(getpwuid_r(last_uid, &pwd_b, buf, sizeof(buf), &pwd_buf) == 0 &&
926 pwd_buf != NULL)
927 {
928 copy_str(uid_buf, sizeof(uid_buf), pwd_buf->pw_name);
929 break;
925 930 } }
926 931 } }
927 932
 
... ... get_uid_string(const dir_entry_t *entry, int as_num, size_t buf_len, char buf[])
931 936 void void
932 937 get_gid_string(const dir_entry_t *entry, int as_num, size_t buf_len, char buf[]) get_gid_string(const dir_entry_t *entry, int as_num, size_t buf_len, char buf[])
933 938 { {
934 /* Cache for the last requested group id. */
939 /* Cache for the last group id requested in symbolic form. */
935 940 static gid_t last_gid = (gid_t)-1; static gid_t last_gid = (gid_t)-1;
936 941 static char gid_buf[26]; static char gid_buf[26];
937 942
943 /* Numeric form doesn't need caching. */
944 if(as_num)
945 {
946 snprintf(buf, buf_len, "%d", (int)entry->gid);
947 return;
948 }
949
938 950 if(entry->gid == last_gid) if(entry->gid == last_gid)
939 951 { {
940 952 copy_str(buf, buf_len, gid_buf); copy_str(buf, buf_len, gid_buf);
941 953 return; return;
942 954 } }
943 955
956 /* Numeric form is a fallback for the case of an unknown group id. */
944 957 last_gid = entry->gid; last_gid = entry->gid;
945 958 snprintf(gid_buf, sizeof(gid_buf), "%d", (int)last_gid); snprintf(gid_buf, sizeof(gid_buf), "%d", (int)last_gid);
946 959
947 if(!as_num)
960 enum { MAX_TRIES = 4 };
961 size_t size = MAX(sysconf(_SC_GETGR_R_SIZE_MAX) + 1, PATH_MAX);
962 int i;
963 for(i = 0; i < MAX_TRIES; ++i, size *= 2)
948 964 { {
949 enum { MAX_TRIES = 4 };
950 size_t size = MAX(sysconf(_SC_GETGR_R_SIZE_MAX) + 1, PATH_MAX);
951 int i;
952 for(i = 0; i < MAX_TRIES; ++i, size *= 2)
953 {
954 char buf[size];
955 struct group group_b;
956 struct group *group_buf;
965 char buf[size];
966 struct group group_b;
967 struct group *group_buf;
957 968
958 if(getgrgid_r(last_gid, &group_b, buf, sizeof(buf), &group_buf) == 0 &&
959 group_buf != NULL)
960 {
961 copy_str(gid_buf, sizeof(gid_buf), group_buf->gr_name);
962 break;
963 }
969 if(getgrgid_r(last_gid, &group_b, buf, sizeof(buf), &group_buf) == 0 &&
970 group_buf != NULL)
971 {
972 copy_str(gid_buf, sizeof(gid_buf), group_buf->gr_name);
973 break;
964 974 } }
965 975 } }
966 976
File tests/utils/uid_gid.c added (mode: 100644) (index 000000000..fa82daa1c)
1 #include <stic.h>
2
3 #include <test-utils.h>
4
5 #include "../../src/ui/ui.h"
6 #include "../../src/utils/utils.h"
7
8 TEST(uid_is_cached_correctly, IF(not_windows))
9 {
10 char buf[32];
11 struct dir_entry_t entry = { };
12
13 /* This was caching result for UID which then got returned on the call with
14 * as_num set (and vice versa, but easier to check it in this order). */
15 get_uid_string(&entry, /*as_num=*/0, sizeof(buf), buf);
16
17 get_uid_string(&entry, /*as_num=*/1, sizeof(buf), buf);
18 assert_string_equal("0", buf);
19 }
20
21 TEST(gid_is_cached_correctly, IF(not_windows))
22 {
23 char buf[32];
24 struct dir_entry_t entry = { };
25
26 /* This was caching result for UID which then got returned on the call with
27 * as_num set (and vice versa, but easier to check it in this order). */
28 get_gid_string(&entry, /*as_num=*/0, sizeof(buf), buf);
29
30 get_gid_string(&entry, /*as_num=*/1, sizeof(buf), buf);
31 assert_string_equal("0", buf);
32 }
33
34 /* vim: set tabstop=2 softtabstop=2 shiftwidth=2 noexpandtab cinoptions-=(0 : */
35 /* vim: set cinoptions+=t0 filetype=c : */
Hints

Before first commit, do not forget to setup your git environment:
git config --global user.name "your_name_here"
git config --global user.email "your@email_here"

Clone this repository using HTTP(S):
git clone https://code.reversed.top/user/xaizek/vifm

Clone this repository using ssh (do not forget to upload a key first):
git clone ssh://rocketgit@code.reversed.top/user/xaizek/vifm

You are allowed to anonymously push to this repository.
This means that your pushed commits will automatically be transformed into a pull request:
... clone the repository ...
... make some changes and some commits ...
git push origin master