1 /*
2 *
3 * CLEX File Manager
4 *
5 * Copyright (C) 2001-2018 Vlado Potisk <vlado_potisk@clex.sk>
6 *
7 * CLEX is free software without warranty of any kind; see the
8 * GNU General Public License as set out in the "COPYING" document
9 * which accompanies the CLEX File Manager package.
10 *
11 * CLEX can be downloaded from http://www.clex.sk
12 *
13 */
14
15 /*
16 * Routines in userdata.c mostly provide access to data stored
17 * in /etc/passwd and /etc/group. All required data is read
18 * into memory to speed up lookups. The data returned by
19 * lookup_xxx() is valid only until next data refresh, the
20 * caller must copy the returned string value if necessary.
21 */
22
23 #include "clexheaders.h"
24
25 #include <sys/stat.h> /* stat() */
26 #ifdef HAVE_UNAME
27 # include <sys/utsname.h> /* uname() */
28 #endif
29 #include <time.h> /* time() */
30 #include <grp.h> /* getgrent() */
31 #include <pwd.h> /* getpwent() */
32 #include <stdarg.h> /* log.h */
33 #include <stdio.h> /* sprintf() */
34 #include <stdlib.h> /* qsort() */
35 #include <string.h> /* strlen() */
36 #include <unistd.h> /* stat() */
37
38 #include "userdata.h"
39
40 #include "edit.h" /* edit_insertchar() */
41 #include "filter.h" /* cx_filter() */
42 #include "log.h" /* msgout() */
43 #include "match.h" /* match_substr() */
44 #include "mbwstring.h" /* convert2w() */
45 #include "util.h" /* emalloc() */
46
47 /*
48 * cached user(group) records are re-read when:
49 * - /etc/passwd (/etc/group) file changes, or
50 * - the cache expires (EXPIRATION in seconds) to allow
51 * changes e.g. in NIS to get detected, or
52 * - explicitly requested
53 */
54 #define EXPIRATION 300 /* 5 minutes */
55
56 typedef struct pwdata {
57 struct pwdata *next; /* tmp pointer */
58 SDSTRINGW login;
59 SDSTRINGW homedir;
60 SDSTRINGW gecos;
61 uid_t uid;
62 } PWDATA;
63
64 typedef struct grdata {
65 struct grdata *next; /* tmp pointer */
66 SDSTRINGW group;
67 gid_t gid;
68 } GRDATA;
69
70 typedef struct {
71 time_t timestamp; /* when the data was obtained, or 0 */
72 dev_t device; /* device/inode for /etc/passwd */
73 ino_t inode;
74 int cnt; /* # of entries */
75 PWDATA **by_name; /* sorted by name (for binary search, ignoring locale) */
76 PWDATA **by_uid; /* sorted by uid */
77 PWDATA *ll; /* linked list, unsorted */
78 } USERDATA;
79
80 typedef struct {
81 time_t timestamp; /* when the data was obtained, or 0 */
82 dev_t device; /* device/inode for /etc/group */
83 ino_t inode;
84 int cnt; /* # of entries */
85 GRDATA **by_name; /* sorted by name (for binary search, ignoring locale) */
86 GRDATA **by_gid; /* sorted by gid */
87 GRDATA *ll; /* linked list, unsorted */
88 } GROUPDATA;
89
90 static USERDATA utable;
91 static GROUPDATA gtable;
92
93 static time_t now;
94
95 static struct {
96 const wchar_t *str;
97 size_t len;
98 int index;
99 } ufind, gfind; /* used by user- and groupname_find() */
100
101 static int
qcmp_name(const void * e1,const void * e2)102 qcmp_name(const void *e1, const void *e2)
103 {
104 return wcscmp( /* not wcscoll() */
105 SDSTR((*(PWDATA **)e1)->login),
106 SDSTR((*(PWDATA **)e2)->login));
107 }
108
109 static int
qcmp_uid(const void * e1,const void * e2)110 qcmp_uid(const void *e1, const void *e2)
111 {
112 return CMP((*(PWDATA **)e1)->uid,(*(PWDATA **)e2)->uid);
113 }
114
115 static void
read_utable(void)116 read_utable(void)
117 {
118 int i, cnt;
119 PWDATA *ud = 0 /* prevent compiler warning */, *old;
120 struct passwd *pw;
121 static FLAG err = 0;
122
123 utable.timestamp = now;
124 setpwent();
125 for (old = utable.ll, cnt = 0; (pw = getpwent()); cnt++) {
126 if (old) {
127 /* use the old PWDATA struct */
128 ud = old;
129 old = ud->next;
130 }
131 else {
132 /* create a new one */
133 ud = emalloc(sizeof(PWDATA));
134 ud->next = utable.ll;
135 utable.ll = ud;
136 SD_INIT(ud->login);
137 SD_INIT(ud->homedir);
138 SD_INIT(ud->gecos);
139 }
140 ud->uid = pw->pw_uid;
141 sdw_copy(&ud->login,convert2w(pw->pw_name));
142 sdw_copy(&ud->homedir,convert2w(pw->pw_dir));
143 sdw_copy(&ud->gecos,convert2w(pw->pw_gecos));
144 }
145 endpwent();
146
147 /* free unused PWDATA structs */
148 if (cnt > 0)
149 for (; old; old = ud->next) {
150 ud->next = old->next;
151 sdw_reset(&old->login);
152 sdw_reset(&old->homedir);
153 sdw_reset(&old->gecos);
154 free(old);
155 }
156
157 if (utable.cnt != cnt && utable.by_name) {
158 free(utable.by_name);
159 free(utable.by_uid);
160 utable.by_name = utable.by_uid = 0;
161 }
162
163 /* I was told using errno for error detection with getpwent() is not portable */
164 if ((utable.cnt = cnt) == 0) {
165 utable.timestamp = 0;
166 if (!TSET(err))
167 msgout(MSG_W,"USER ACCOUNTS: Cannot obtain user account data");
168 return;
169 }
170 if (TCLR(err))
171 msgout(MSG_W,"USER ACCOUNTS: User account data is now available");
172
173 /* linked list -> two sorted arrays */
174 if (utable.by_name == 0) {
175 utable.by_name = emalloc(cnt * sizeof(PWDATA *));
176 utable.by_uid = emalloc(cnt * sizeof(PWDATA *));
177 for (ud = utable.ll, i = 0; i < cnt; i++, ud = ud->next)
178 utable.by_name[i] = utable.by_uid[i] = ud;
179 }
180 qsort(utable.by_name,cnt,sizeof(PWDATA *),qcmp_name);
181 qsort(utable.by_uid ,cnt,sizeof(PWDATA *),qcmp_uid);
182 }
183
184 static int
qcmp_gname(const void * e1,const void * e2)185 qcmp_gname(const void *e1, const void *e2)
186 {
187 return wcscmp( /* not wcscoll() */
188 SDSTR((*(GRDATA **)e1)->group),
189 SDSTR((*(GRDATA **)e2)->group));
190 }
191
192 static int
qcmp_gid(const void * e1,const void * e2)193 qcmp_gid(const void *e1, const void *e2)
194 {
195 return CMP((*(GRDATA **)e1)->gid,(*(GRDATA **)e2)->gid);
196 }
197
198 static void
read_gtable(void)199 read_gtable(void)
200 {
201 int i, cnt;
202 GRDATA *gd = 0 /* prevent compiler warning */, *old;
203 struct group *gr;
204 static FLAG err = 0;
205
206 gtable.timestamp = now;
207 setgrent();
208 for (old = gtable.ll, cnt = 0; (gr = getgrent()); cnt++) {
209 if (old) {
210 /* use the old GRDATA struct */
211 gd = old;
212 old = gd->next;
213 }
214 else {
215 /* create a new one */
216 gd = emalloc(sizeof(GRDATA));
217 gd->next = gtable.ll;
218 gtable.ll = gd;
219 SD_INIT(gd->group);
220 }
221 gd->gid = gr->gr_gid;
222 sdw_copy(&gd->group,convert2w(gr->gr_name));
223 }
224 endgrent();
225
226 /* free unused GRDATA structs */
227 if (cnt > 0)
228 for (; old; old = gd->next) {
229 gd->next = old->next;
230 sdw_reset(&old->group);
231 free(old);
232 }
233
234 if (gtable.cnt != cnt && gtable.by_name) {
235 free(gtable.by_name);
236 free(gtable.by_gid);
237 gtable.by_name = gtable.by_gid = 0;
238 }
239
240 if ((gtable.cnt = cnt) == 0) {
241 gtable.timestamp = 0;
242 if (!TSET(err))
243 msgout(MSG_W,"USER ACCOUNTS: Cannot obtain user group data");
244 return;
245 }
246 if (TCLR(err))
247 msgout(MSG_W,"USER ACCOUNTS: User group data is now available");
248
249 /* linked list -> sorted array */
250 if (gtable.by_name == 0) {
251 gtable.by_name = emalloc(cnt * sizeof(GRDATA *));
252 gtable.by_gid = emalloc(cnt * sizeof(GRDATA *));
253 for (gd = gtable.ll, i = 0; i < cnt; i++, gd = gd->next)
254 gtable.by_name[i] = gtable.by_gid[i] = gd;
255 }
256 qsort(gtable.by_name,cnt,sizeof(GRDATA *),qcmp_gname);
257 qsort(gtable.by_gid ,cnt,sizeof(GRDATA *),qcmp_gid);
258 }
259
260 static int
shelltype(const char * shell)261 shelltype(const char *shell)
262 {
263 size_t len;
264
265 len = strlen(shell);
266 if (len >= 2 && shell[len - 2] == 's' && shell[len - 1] == 'h')
267 return (len >= 3 && shell[len - 3] == 'c') ? SHELL_CSH : SHELL_SH;
268 return SHELL_OTHER;
269 }
270
271 void
userdata_initialize(void)272 userdata_initialize(void)
273 {
274 static char uidstr[24];
275 static SDSTRING host = SDNULL("localhost");
276 struct passwd *pw;
277 const char *name, *xdg;
278 uid_t myuid;
279
280 #ifdef HAVE_UNAME
281 char ch, *pch, *pdot;
282 FLAG ip;
283 struct utsname ut;
284
285 uname(&ut);
286 sd_copy(&host,ut.nodename);
287
288 /* strip the domain part */
289 for (ip = 1, pdot = 0, pch = SDSTR(host); (ch = *pch); pch++) {
290 if (ch == '.') {
291 if (pdot == 0)
292 pdot = pch;
293 }
294 else if (ch < '0' || ch > '9')
295 ip = 0; /* this is a name and not an IP address */
296 if (!ip && pdot != 0) {
297 *pdot = '\0';
298 break;
299 }
300 }
301 #endif
302 user_data.host = SDSTR(host);
303 user_data.hostw = ewcsdup(convert2w(user_data.host));
304
305 user_data.nowrite = 0;
306
307 msgout(MSG_AUDIT,"CLEX version: \""VERSION "\"");
308 msgout(MSG_HEADING,"Examining data of your account");
309
310 myuid = getuid();
311 if ((pw = getpwuid(myuid)) == 0) {
312 sprintf(uidstr,"%d",(int)myuid);
313 msgout(MSG_W,"Cannot find your account (UID=%s)"
314 " in the user database",uidstr);
315 sprintf(uidstr,"UID_%d",(int)myuid);
316 user_data.login = uidstr;
317 user_data.nowrite = 1;
318 }
319 else
320 user_data.login = estrdup(pw->pw_name);
321 user_data.loginw = ewcsdup(convert2w(user_data.login));
322
323 if (checkabs(name = getenv("SHELL")))
324 user_data.shell = name;
325 else if (pw && checkabs(pw->pw_shell))
326 user_data.shell = estrdup(pw->pw_shell);
327 else {
328 msgout(MSG_W,"Cannot obtain the name of your shell program; using \"/bin/sh\"");
329 user_data.shell = "/bin/sh";
330 }
331 name = base_name(user_data.shell);
332 user_data.shellw = ewcsdup(convert2w(name));
333 user_data.shelltype = shelltype(name);
334 msgout(MSG_AUDIT,"Command interpreter: \"%s\"",user_data.shell);
335
336 if (checkabs(name = getenv("HOME"))) {
337 user_data.homedir = name;
338 if (strcmp(name,"/") == 0) {
339 if (pw && *pw->pw_dir && strcmp(pw->pw_dir,"/") != 0) {
340 msgout(MSG_W,"Your home directory is the root directory, "
341 "but according to the password file it should be \"%s\"",pw->pw_dir);
342 user_data.nowrite = 1;
343 }
344 }
345 }
346 else if (pw && checkabs(pw->pw_dir))
347 user_data.homedir = estrdup(pw->pw_dir);
348 else {
349 msgout(MSG_W,"Cannot obtain the name of your home directory; using \"/\"");
350 user_data.homedir = "/";
351 user_data.nowrite = 1;
352 }
353
354 if (!user_data.nowrite && strcmp(user_data.homedir,"/") == 0 && myuid != 0) {
355 msgout(MSG_W,"Your home directory is the root directory, but you are not root");
356 user_data.nowrite = 1;
357 }
358
359 user_data.homedirw = ewcsdup(convert2w(user_data.homedir));
360 msgout(MSG_DEBUG,"Home directory: \"%s\"",user_data.homedir);
361
362 if (user_data.nowrite)
363 msgout(MSG_W,"Due to the problem reported above CLEX will not save any "
364 "data to disk. This includes configuration, options and bookmarks");
365
366 user_data.isroot = geteuid() == 0; /* 0 or 1 */;
367
368 xdg = getenv("XDG_CONFIG_HOME");
369 if (xdg && *xdg) {
370 pathname_set_directory(xdg);
371 user_data.subdir = estrdup(pathname_join("clex"));
372 }
373 else {
374 pathname_set_directory(user_data.homedir);
375 user_data.subdir = estrdup(pathname_join(".config/clex"));
376 }
377 msgout(MSG_DEBUG,"Configuration directory: \"%s\"",user_data.subdir);
378 pathname_set_directory(user_data.subdir);
379 user_data.file_cfg = estrdup(pathname_join("config"));
380 user_data.file_opt = estrdup(pathname_join("options"));
381 user_data.file_bm = estrdup(pathname_join("bookmarks"));
382
383 msgout(MSG_HEADING,0);
384 }
385
386 void
userdata_expire(void)387 userdata_expire(void)
388 {
389 utable.timestamp = gtable.timestamp = 0;
390 }
391
392 /* returns 1 if data was re-read, 0 if unchanged */
393 int
userdata_refresh(void)394 userdata_refresh(void)
395 {
396 FLAG stat_ok;
397 int reloaded;
398 struct stat st;
399
400 reloaded = 0;
401
402 now = time(0);
403 stat_ok = stat("/etc/passwd",&st) == 0;
404 if (!stat_ok || st.st_mtime >= utable.timestamp
405 || st.st_dev != utable.device || st.st_ino != utable.inode
406 || now > utable.timestamp + EXPIRATION ) {
407 read_utable();
408 utable.device = stat_ok ? st.st_dev : 0;
409 utable.inode = stat_ok ? st.st_ino : 0;
410 reloaded = 1;
411 }
412
413 stat_ok = stat("/etc/group",&st) == 0;
414 if (!stat_ok || st.st_mtime >= gtable.timestamp
415 || st.st_dev != gtable.device || st.st_ino != gtable.inode
416 || now > gtable.timestamp + EXPIRATION) {
417 read_gtable();
418 gtable.device = stat_ok ? st.st_dev : 0;
419 gtable.inode = stat_ok ? st.st_ino : 0;
420 reloaded = 1;
421 }
422
423 return reloaded;
424 }
425
426 /* simple binary search algorithm */
427 #define BIN_SEARCH(COUNT,CMPFUNC,RETVAL) \
428 { \
429 int min, med, max, cmp; \
430 for (min = 0, max = COUNT - 1; min <= max; ) { \
431 med = (min + max) / 2; \
432 cmp = CMPFUNC; \
433 if (cmp == 0) \
434 return RETVAL; \
435 if (cmp < 0) \
436 max = med - 1; \
437 else \
438 min = med + 1; \
439 } \
440 return 0; \
441 }
442 /* end of BIN_SEARCH() macro */
443
444 /* numeric uid -> login name */
445 const wchar_t *
lookup_login(uid_t uid)446 lookup_login(uid_t uid)
447 {
448 BIN_SEARCH(utable.cnt,
449 CMP(uid,utable.by_uid[med]->uid),
450 SDSTR(utable.by_uid[med]->login))
451 }
452
453 /* numeric gid -> group name */
454 const wchar_t *
lookup_group(gid_t gid)455 lookup_group(gid_t gid)
456 {
457 BIN_SEARCH(gtable.cnt,
458 CMP(gid,gtable.by_gid[med]->gid),
459 SDSTR(gtable.by_gid[med]->group))
460 }
461
462 static const wchar_t *
lookup_homedir(const wchar_t * user,size_t len)463 lookup_homedir(const wchar_t *user, size_t len)
464 {
465 static SDSTRINGW username = SDNULL(L"");
466
467 if (len == 0)
468 return user_data.homedirw;
469
470 sdw_copyn(&username,user,len);
471 BIN_SEARCH(utable.cnt,
472 wcscmp(SDSTR(username),SDSTR(utable.by_name[med]->login)),
473 SDSTR(utable.by_name[med]->homedir))
474 }
475
476 /*
477 * check if 'dir' is of the form ~username/dir with a valid username
478 * typical usage:
479 * tilde = is_dir_tilde(dir);
480 * ... dequote dir ...
481 * if (tilde) dir = dir_tilde(dir);
482 * note: without dequoting is this sufficient:
483 * tilde = *dir == '~';
484 */
485 int
is_dir_tilde(const wchar_t * dir)486 is_dir_tilde(const wchar_t *dir)
487 {
488 size_t i;
489
490 if (*dir != L'~')
491 return 0;
492
493 for (i = 1; dir[i] != L'\0' && dir[i] != L'/'; i++)
494 ;
495 return lookup_homedir(dir + 1,i - 1) != 0;
496 }
497
498 /*
499 * dir_tilde() function performs tilde substitution. It understands
500 * ~user/dir notation and transforms it to proper directory name.
501 * The result of the substitution (if performed) is stored in
502 * a static buffer that might get overwritten by successive calls.
503 */
504 const wchar_t *
dir_tilde(const wchar_t * dir)505 dir_tilde(const wchar_t *dir)
506 {
507 size_t i;
508 const wchar_t *home;
509 static USTRINGW buff = UNULL;
510
511 if (*dir != L'~')
512 return dir;
513
514 for (i = 1; dir[i] != L'\0' && dir[i] != L'/'; i++)
515 ;
516 home = lookup_homedir(dir + 1,i - 1);
517 if (home == 0)
518 return dir; /* no such user */
519
520 usw_cat(&buff,home,dir + i,(wchar_t *)0);
521 return USTR(buff);
522 }
523
524 /*
525 * Following two functions implement username completion. First
526 * username_find_init() is called to initialize the search, thereafter
527 * each call to username_find() returns one matching entry.
528 */
529 void
username_find_init(const wchar_t * str,size_t len)530 username_find_init(const wchar_t *str, size_t len)
531 {
532 int min, med, max, cmp;
533
534 ufind.str = str;
535 ufind.len = len;
536
537 if (len == 0) {
538 ufind.index = 0;
539 return;
540 }
541
542 for (min = 0, max = utable.cnt - 1; min <= max; ) {
543 med = (min + max) / 2;
544 cmp = wcsncmp(str,SDSTR(utable.by_name[med]->login),len);
545 if (cmp == 0) {
546 /*
547 * the binary search algorithm is slightly altered here,
548 * multiple matches are possible, we need the first one
549 */
550 if (min == max) {
551 ufind.index = med;
552 return;
553 }
554 max = med;
555 }
556 else if (cmp < 0)
557 max = med - 1;
558 else
559 min = med + 1;
560 }
561
562 ufind.index = utable.cnt;
563 }
564
565 const wchar_t *
username_find(const wchar_t ** pgecos)566 username_find(const wchar_t **pgecos)
567 {
568 const wchar_t *login, *gecos;
569
570 if (ufind.index >= utable.cnt)
571 return 0;
572 login = SDSTR(utable.by_name[ufind.index]->login);
573 if (ufind.len && wcsncmp(ufind.str,login,ufind.len))
574 return 0;
575 if (pgecos) {
576 gecos = SDSTR(utable.by_name[ufind.index]->gecos);
577 *pgecos = *gecos == L'\0' ? 0 : gecos;
578 }
579 ufind.index++;
580 return login;
581 }
582
583 /* the same find functions() for groups */
584 void
groupname_find_init(const wchar_t * str,size_t len)585 groupname_find_init(const wchar_t *str, size_t len)
586 {
587 int min, med, max, cmp;
588
589 gfind.str = str;
590 gfind.len = len;
591
592 if (len == 0) {
593 gfind.index = 0;
594 return;
595 }
596
597 for (min = 0, max = gtable.cnt - 1; min <= max; ) {
598 med = (min + max) / 2;
599 cmp = wcsncmp(str,SDSTR(gtable.by_name[med]->group),len);
600 if (cmp == 0) {
601 /*
602 * the binary search algorithm is slightly altered here,
603 * multiple matches are possible, we need the first one
604 */
605 if (min == max) {
606 gfind.index = med;
607 return;
608 }
609 max = med;
610 }
611 else if (cmp < 0)
612 max = med - 1;
613 else
614 min = med + 1;
615 }
616
617 gfind.index = gtable.cnt;
618 }
619
620 const wchar_t *
groupname_find(void)621 groupname_find(void)
622 {
623 const wchar_t *group;
624
625 if (gfind.index >= gtable.cnt)
626 return 0;
627 group = SDSTR(gtable.by_name[gfind.index]->group);
628 if (gfind.len && wcsncmp(gfind.str,group,gfind.len))
629 return 0;
630 gfind.index++;
631 return group;
632 }
633
634 void
user_panel_data(void)635 user_panel_data(void)
636 {
637 int i, j;
638 size_t len;
639 const wchar_t *login, *gecos;
640 uid_t curs;
641
642 curs = VALID_CURSOR(panel_user.pd) ? panel_user.users[panel_user.pd->curs].uid : 0;
643 if (panel_user.pd->filtering)
644 match_substr_set(panel_user.pd->filter->line);
645
646 panel_user.maxlen = 0;
647 for (i = j = 0; i < utable.cnt; i++) {
648 if (curs == utable.by_uid[i]->uid)
649 panel_user.pd->curs = j;
650 login = SDSTR(utable.by_uid[i]->login);
651 gecos = SDSTR(utable.by_uid[i]->gecos);
652 if (panel_user.pd->filtering && !match_substr(login) && !match_substr(gecos))
653 continue;
654 panel_user.users[j].uid = utable.by_uid[i]->uid;
655 panel_user.users[j].login = login;
656 len = wcslen(login);
657 if (len > panel_user.maxlen)
658 panel_user.maxlen = len;
659 panel_user.users[j++].gecos = gecos;
660 }
661
662 panel_user.pd->cnt = j;
663 }
664
665 int
user_prepare(void)666 user_prepare(void)
667 {
668 if (utable.cnt > panel_user.usr_alloc) {
669 efree(panel_user.users);
670 panel_user.usr_alloc = utable.cnt;
671 panel_user.users = emalloc(panel_user.usr_alloc * sizeof(USER_ENTRY));
672 }
673
674 panel_user.pd->filtering = 0;
675 panel_user.pd->curs = -1;
676 user_panel_data();
677 panel_user.pd->top = panel_user.pd->min;
678 panel_user.pd->curs = 0;
679
680 panel = panel_user.pd;
681 textline = &line_cmd;
682
683 return 0;
684 }
685
686
687 void
cx_user_paste(void)688 cx_user_paste(void)
689 {
690 edit_nu_insertstr(panel_user.users[panel_user.pd->curs].login,QUOT_NORMAL);
691 edit_insertchar(' ');
692 if (panel->filtering == 1)
693 cx_filter();
694 }
695
696 void
cx_user_mouse(void)697 cx_user_mouse(void)
698 {
699 if (MI_PASTE)
700 cx_user_paste();
701 }
702
703 void
group_panel_data(void)704 group_panel_data(void)
705 {
706 int i, j;
707 const wchar_t *group;
708 gid_t curs;
709
710 curs = VALID_CURSOR(panel_group.pd) ? panel_group.groups[panel_group.pd->curs].gid : 0;
711 if (panel_group.pd->filtering)
712 match_substr_set(panel_group.pd->filter->line);
713
714 for (i = j = 0; i < gtable.cnt; i++) {
715 if (curs == gtable.by_gid[i]->gid)
716 panel_group.pd->curs = j;
717 group = SDSTR(gtable.by_gid[i]->group);
718 if (panel_group.pd->filtering && !match_substr(group))
719 continue;
720 panel_group.groups[j].gid = gtable.by_gid[i]->gid;
721 panel_group.groups[j++].group = group;
722 }
723
724 panel_group.pd->cnt = j;
725 }
726
727 int
group_prepare(void)728 group_prepare(void)
729 {
730 if (gtable.cnt > panel_group.grp_alloc) {
731 efree(panel_group.groups);
732 panel_group.grp_alloc = gtable.cnt;
733 panel_group.groups = emalloc(panel_group.grp_alloc * sizeof(GROUP_ENTRY));
734 }
735
736 panel_group.pd->filtering = 0;
737 panel_group.pd->curs = -1;
738 group_panel_data();
739 panel_group.pd->top = panel_group.pd->min;
740 panel_group.pd->curs = 0;
741 panel = panel_group.pd;
742 textline = &line_cmd;
743
744 return 0;
745 }
746
747 void
cx_group_paste(void)748 cx_group_paste(void)
749 {
750 edit_nu_insertstr(panel_group.groups[panel_group.pd->curs].group,QUOT_NORMAL);
751 edit_insertchar(' ');
752 if (panel->filtering == 1)
753 cx_filter();
754 }
755
756 void
cx_group_mouse(void)757 cx_group_mouse(void)
758 {
759 if (MI_PASTE)
760 cx_group_paste();
761 }
762