group.c
Go to the documentation of this file.
1 /* ========================================================================== */
2 /*! \file
3  * \brief Handling of subscribed groups and already viewed articles
4  *
5  * Copyright (c) 2012-2024 by the developers. See the LICENSE file for details.
6  *
7  * If nothing else is specified, functions return zero to indicate success
8  * and a negative value to indicate an error.
9  */
10 
11 
12 /* ========================================================================== */
13 /* Include headers */
14 
15 #include "posix.h" /* Include this first because of feature test macros */
16 
17 #include <stdio.h>
18 #include <string.h>
19 
20 #include "conf.h"
21 #include "config.h"
22 #include "core.h"
23 #include "encoding.h"
24 #include "fileutils.h"
25 #include "group.h"
26 #include "main.h"
27 #include "xdg.h"
28 
29 
30 /* ========================================================================== */
31 /*! \defgroup GROUPHANDLING GROUP: Usenet group handling
32  *
33  * Location of group information file: $XDG_CONFIG_HOME/$CFG_NAME/groupfile
34  *
35  * The content of the groupfile should be compatible with the de-facto standard
36  * of the '~/.newsrc' file used by other newsreaders so that the contained data
37  * can be shared with them if desired.
38  */
39 /*! @{ */
40 
41 
42 /* ========================================================================== */
43 /* Constants */
44 
45 /*! \brief Message prefix for GROUPHANDLING module */
46 #define MAIN_ERR_PREFIX "GROUP: "
47 
48 
49 /*! \brief Permissions for group file */
50 #define GROUP_PERM (api_posix_mode_t) (API_POSIX_S_IRUSR | API_POSIX_S_IWUSR)
51 
52 
53 /* ========================================================================== */
54 /* Variables */
55 
56 static const char groupfile_name[] = "groupfile";
57 static const char tmpfile_name[] = "groupfile.new";
58 
59 
60 /* ========================================================================== */
61 /* Compare groups
62  *
63  * Only the group names up to ":" or "!" respectively are compared.
64  *
65  * \param[in] a Line from groupfile
66  * \param[in] b Line from groupfile
67  *
68  * \return
69  * - Negative value if \e a is less than \e b (\e a should be listed first)
70  * - 0 if both group names are considered equal
71  * - Negative value if \e a is greater than \e b (\e b should be listed first)
72  */
73 
74 static int group_compar(const void* a, const void* b)
75 {
76  int res = 0;
77  const char* aa = *(const char**) a;
78  const char* bb = *(const char**) b;
79  char* x;
80  char* y;
81  char* p;
82 
83  x = (char*) api_posix_malloc(strlen(aa) + (size_t) 1);
84  y = (char*) api_posix_malloc(strlen(bb) + (size_t) 1);
85  if(NULL != x && NULL != y)
86  {
87  strcpy(x, aa);
88  strcpy(y, bb);
89  p = strchr(x, (int) ':'); if(NULL != p) { *p = 0; }
90  p = strchr(x, (int) '!'); if(NULL != p) { *p = 0; }
91  p = strchr(y, (int) ':'); if(NULL != p) { *p = 0; }
92  p = strchr(y, (int) '!'); if(NULL != p) { *p = 0; }
93  api_posix_errno = 0;
94  res = strcoll(x, y);
95  if(api_posix_errno) { res = 0; }
96  }
97  api_posix_free((void*) x);
98  api_posix_free((void*) y);
99 
100  return(res);
101 }
102 
103 
104 /* ========================================================================== */
105 /* Get groupfile pathname
106  *
107  * This function must be thread safe.
108  * The caller is responsible to free the memory for the buffer on success.
109  */
110 
111 static int group_get_pathname(const char** pathname, const char* filename)
112 {
113  int res = -1;
114  int rv;
115 
116  /* This requires the UI mutex */
117  *pathname = xdg_get_confdir(CFG_NAME);
118  if(NULL != *pathname)
119  {
120  rv = fu_create_path(*pathname, (api_posix_mode_t) API_POSIX_S_IRWXU);
121  if(0 == rv)
122  {
123  /* Store scorefile pathname */
124  rv = xdg_append_to_path(pathname, filename);
125  if(0 == rv)
126  {
127  res = 0;
128  }
129  }
130  }
131 
132  /* Free memory on error */
133  if(0 != res)
134  {
135  PRINT_ERROR("Cannot create group file pathname");
136  api_posix_free((void*) *pathname);
137  *pathname = NULL;
138  }
139 
140  return(res);
141 }
142 
143 
144 /* ========================================================================== */
145 /* Extract ranges of already read articles from string
146  *
147  * \param[out] list Pointer to linked list containing ranges
148  * \param[in] s Pointer to string containing ranges
149  *
150  * This function extracts article number ranges from string \e s and create a
151  * linked list from the extracted data. On success a pointer to this list is
152  * written to the location pointed to by \e rp .
153  *
154  * Format: 1-134,136,138-2500
155  * White space is allowed to surround the numbers.
156  * Consecutive and overlapping ranges are merged together.
157  */
158 
159 static int group_extract_ranges(struct core_range** list, const char* s)
160 {
161  int res = 0;
162  int rv;
163  const char* p;
164  size_t len = 0;
165  int range_start = 1;
166  core_anum_t tmp;
167  core_anum_t start = 0;
168  core_anum_t end = 0;
169  struct core_range* list_start = NULL;
170  struct core_range* list_element;
171  struct core_range* current_element = NULL;
172 
173  while(*s)
174  {
175  /* Skip potential white space */
176  while(' ' == *s || (char) 0x09 == *s ) { ++s; }
177  /* Accept comma as field separator */
178  while(',' == *s) { ++s; }
179  /* Skip potential white space */
180  while(' ' == *s || (char) 0x09 == *s ) { ++s; }
181  /* Check for end of string */
182  if(!*s) { break; }
183  /* Digit(s) must follow now */
184  p = s;
185  if(enc_ascii_check_digit(s)) { res = -1; break; }
186  while(!enc_ascii_check_digit(++s));
187  len = (size_t) (s - p);
188  if((size_t) API_POSIX_INT_MAX < len) { res = -1; break; }
189  rv = enc_convert_ascii_to_anum(&tmp, p, (int) len);
190  if(rv) { res = -1; break; }
191  /* Skip potential white space */
192  while(' ' == *s || (char) 0x09 == *s ) { ++s; }
193  if(range_start)
194  {
195  start = tmp; range_start = 0;
196  /* Check for single article range */
197  if('-' != *s) { end = start; range_start = 1; } else { ++s; }
198  }
199  else
200  {
201  end = tmp;
202  range_start = 1;
203  }
204  /* Store article range */
205  if(range_start)
206  {
207  /* printf("Range: %lu-%lu\n", start, end); */
208  res = group_article_range_constructor(&list_element, start, end, NULL);
209  if(0 > res) { break; }
210  if(NULL == current_element) { list_start = list_element; }
211  else { current_element->next = list_element; }
212  current_element = list_element;
213  }
214  }
215  /* Check for error */
216  if(res)
217  {
218  PRINT_ERROR("Parsing article ranges in group file failed");
219  group_article_range_destructor(&list_start);
220  }
221  else
222  {
223  /* Return linked list */
224  *list = list_start;
225  }
226 
227  return(res);
228 }
229 
230 
231 /* ========================================================================== */
232 /* Print one line to groupfile (using "~/.newsrc" style data format)
233  *
234  * \param[in] fs File stream of groupfile to write
235  * \param[in] group Pointer to group state
236  */
237 
238 static int group_print_entry(FILE* fs, struct core_groupstate* group)
239 {
240  int res = 0;
241  int rv;
242  struct core_range* cr;
243  unsigned long int a;
244 
245  /* Print group name */
246  rv = fprintf(fs, "%s: ", group->name);
247  if(0 > rv) { res = -1; }
248 
249  /* Print list of already read articles */
250  if(!res)
251  {
252  if(CORE_ANUM_T_MAX > ULONG_MAX)
253  {
254  PRINT_ERROR("Value of CORE_ANUM_T_MAX is too large");
255  res = -1;
256  }
257  else
258  {
259  cr = group->info;
260  a = 0;
261  while(NULL != cr)
262  {
263  if(a) { rv = fprintf(fs, ", "); }
264  if(0 > rv) { res = -1; break; }
265  a = (unsigned long int) cr->first;
266  rv = fprintf(fs, "%lu", a);
267  if(0 > rv) { res = -1; break; }
268  if(cr->last != cr->first)
269  {
270  a = (unsigned long int) cr->last;
271  rv = fprintf(fs, "-%lu", a);
272  if(0 > rv) { res = -1; break; }
273  }
274  cr = cr->next;
275  }
276  }
277  }
278 
279  /* Terminate line */
280  fprintf(fs, "\n");
281 
282  return(res);
283 }
284 
285 
286 /* ========================================================================== */
287 /* Export all groups to file (using "~/.newsrc" style data format)
288  *
289  * \param[in] num Number of elements in group state array
290  * \param[in] grouplist Array of group states to merge
291  * \param[in] fs File stream of current groupfile
292  * \param[in] fs_tmp File stream of (temporary) output groupfile
293  */
294 
295 static int group_export_groups(size_t num, struct core_groupstate* grouplist,
296  FILE* fs, FILE* fs_tmp)
297 {
298  int res = -1;
299 
300  char* line = NULL;
301  size_t len = 0;
302  api_posix_ssize_t readlen;
303  int rv;
304  char* p;
305  size_t* inserted = NULL;
306  size_t i;
307  int error;
308  int modified;
309  size_t nlen = 0;
310 
311  inserted = (size_t*) api_posix_malloc(sizeof(size_t) * num);
312  if(NULL != inserted)
313  {
314  /* Init insertion flags */
315  for(i = 0; i < num; ++i) { inserted[i] = 0; }
316 
317  /* Process lines of current groupfile */
318  while(1)
319  {
320  /* Read line */
321  readlen = api_posix_getline(&line, &len, fs);
322  if(-1 == readlen)
323  {
324  if (API_POSIX_ENOMEM == api_posix_errno)
325  {
326  PRINT_ERROR("Cannot assign memory for group file parser");
327  break;
328  }
329  else
330  {
331  /* Check for error */
332  if(ferror(fs))
333  {
334  PRINT_ERROR("Parse error in group file");
335  break;
336  }
337  /* Check for EOF */
338  if(feof(fs))
339  {
340  res = 0;
341  break;
342  }
343  }
344  }
345  if(0 >= readlen) break;
346  else
347  {
348  /* Replace potential CRs with SPACEes */
349  for(i = 0; i < (size_t) readlen; ++i)
350  {
351  if(0x0D == (int) line[i]) { line[i] = ' '; }
352  }
353  /* Check whether one of the groups in array matches */
354  error = 0;
355  modified = 0;
356  for(i = 0; i < num; ++i)
357  {
358  nlen = strlen(grouplist[i].name);
359  if(!inserted[i] && !strncmp(line, grouplist[i].name, nlen))
360  {
361  /* Verify that group name not only matches partly at beginning */
362  if(':' != line[nlen] && '!' != line[nlen]) { continue; }
363  /* Yes => Check whether group is unsubscribed */
364  p = strchr(line, (int) '!');
365  if(NULL != p)
366  {
367  /* Yes => Subscribe to group again and preserve state */
368  *p = ':';
369  /* Write modified line to new group file */
370  rv = fprintf(fs_tmp, "%s", line);
371  if(0 > rv) { error = 1; break; }
372  }
373  else
374  {
375  /* No => Replace line with current state */
376  res = group_print_entry(fs_tmp, &grouplist[i]);
377  if(res) { error = 1; break; }
378  }
379  inserted[i] = 1;
380  modified = 1;
381  break;
382  }
383  }
384  if(error) { break; }
385  if(!modified)
386  {
387  /* Unsubscribe from group */
388  p = strchr(line, (int) ':');
389  if(NULL != p) { *p = '!'; }
390  rv = fprintf(fs_tmp, "%s", line);
391  if(0 > rv) break;
392  }
393  }
394  }
395 
396  /* Append lines for unprocessed new groups */
397  if(!res)
398  {
399  for(i = 0; i < num; ++i)
400  {
401  if(!inserted[i])
402  {
403  res = group_print_entry(fs_tmp, &grouplist[i]);
404  if(res) { break; }
405  }
406  }
407  }
408  }
409 
410  /* Release memory */
411  api_posix_free((void*) line);
412  api_posix_free((void*) inserted);
413 
414  return(res);
415 }
416 
417 
418 /* ========================================================================== */
419 /* Import subscribed groups from file (containing "~/.newsrc" style data)
420  *
421  * \param[out] num Pointer to number of groups in list
422  * \param[out] grouplist Pointer to array of group name strings
423  *
424  * On success, the number of groups found in file \e fs is written to the
425  * location pointed to by \e num .
426  */
427 
428 static int group_import_groups(size_t* num,
429  struct core_groupstate** grouplist,
430  FILE* fs)
431 {
432  int res = -1;
433  char* line;
434  size_t len = 0;
435  api_posix_ssize_t readlen;
436  struct core_groupstate* p;
437  char* c;
438  struct core_range* rp = NULL;
439 
440  *grouplist = NULL;
441  *num = 0;
442 
443  while(1)
444  {
445  /* Read line */
446  line = NULL;
447  readlen = api_posix_getline(&line, &len, fs);
448  if(-1 == readlen)
449  {
450  api_posix_free((void*) line);
451  if (API_POSIX_ENOMEM == api_posix_errno)
452  {
453  PRINT_ERROR("Cannot allocate memory for group file parser");
454  break;
455  }
456  else
457  {
458  /* Check for error */
459  if(ferror(fs))
460  {
461  PRINT_ERROR("Parse error in group file");
462  break;
463  }
464  /* Check for EOF */
465  if(feof(fs))
466  {
467  res = 0;
468  break;
469  }
470  }
471  }
472  if(0 >= readlen) break;
473  else
474  {
475  /* Verify line */
476  if(enc_ascii_check(line))
477  {
478  /* Invalid encoding => Ignore line */
479  api_posix_free((void*) line);
480  continue;
481  }
482  /* Extract group name */
483  if(strchr(line, (int) '!'))
484  {
485  /* Ignore unsubscribed groups */
486  api_posix_free(line);
487  continue;
488  }
489  c = strchr(line, (int) ':');
490  if(NULL == c)
491  {
492  /* Invalid format => Ignore line */
493  api_posix_free((void*) line);
494  continue;
495  }
496  /* Terminate group name by replacing ':' with NUL */
497  *c = 0;
498  /* Add group name to list */
499  p = (struct core_groupstate*)
500  api_posix_realloc((void*) *grouplist,
501  (*num + (size_t) 1) * sizeof(**grouplist));
502  if(NULL == p)
503  {
504  PRINT_ERROR("Cannot allocate memory for group list");
505  api_posix_free((void*) line);
506  break;
507  }
508  else
509  {
510  *grouplist = p;
511  line[readlen - (api_posix_ssize_t) 1] = 0;
512  (*grouplist)[(*num)].name = line;
513  (*grouplist)[(*num)].info = NULL;
514  (*grouplist)[(*num)].last_viewed = 0;
515  }
516  /* Get numbers of already read articles (c still points to ':') */
517  if(!group_extract_ranges(&rp, ++c))
518  {
519  /* Store range list */
520  (*grouplist)[(*num)].info = rp;
521  }
522  /* Increment number of groups found */
523  ++*num;
524  }
525  }
526 
527  /* Release memory on error */
528  if(res)
529  {
530  /* Destroy group list */
531  group_destroy_list(num, grouplist);
532  }
533 
534  return(res);
535 }
536 
537 
538 /* ========================================================================== */
539 /*! \brief Initialize group handling
540  *
541  * This function prepares the groupfile using the following algorithm:
542  * - Check whether \c CONF_NEWSRC is configured
543  * - If not configured abort and return, otherwise continue
544  * - Check whether pathname configured with \c CONF_NEWSRC is valid
545  * - If not valid abort and return
546  * - Rename current \c groupfile to \c groupfile.old
547  * - Copy pathname configured with \c CONF_NEWSRC to \c groupfile
548  */
549 
550 void group_init(void)
551 {
552  const char* newsrc = config[CONF_NEWSRC].val.s;
553  const char* grouppathname = NULL;
554  char* oldgrouppathname = NULL;
555  int rv;
556  api_posix_struct_stat state;
557  int fd = -1;
558  char* data = NULL;
559  size_t len;
560 
561  if(strlen(newsrc))
562  {
563  if(main_debug)
564  {
565  printf("%s: %sImport external newsrc: %s\n",
566  CFG_NAME, MAIN_ERR_PREFIX, newsrc);
567  }
568  rv = api_posix_stat(newsrc, &state);
569  if(rv) { PRINT_ERROR("Cannot stat newsrc file"); }
570  else if(API_POSIX_S_ISREG(state.st_mode))
571  {
572  rv = group_get_pathname(&grouppathname, groupfile_name);
573  if(!rv)
574  {
575  /* Read newsrc file */
576  rv = fu_open_file(newsrc, &fd, API_POSIX_O_RDWR,
577  (api_posix_mode_t) 0);
578  if(!rv)
579  {
580  rv = fu_lock_file(fd);
581  if(!rv)
582  {
583  rv = fu_read_whole_file(fd, &data, &len);
584  }
585  fu_close_file(&fd, NULL);
586  if(!rv)
587  {
588  oldgrouppathname =
589  (char*) api_posix_malloc(strlen(grouppathname)
590  + (size_t) 5);
591  if(NULL == oldgrouppathname)
592  {
593  PRINT_ERROR("Cannot allocate memory for pathname");
594  }
595  else
596  {
597  strcpy(oldgrouppathname, grouppathname);
598  strcat(oldgrouppathname, ".old");
599  rv = api_posix_rename(grouppathname, oldgrouppathname);
600  if(rv)
601  {
602  PRINT_ERROR("Renaming groupfile failed");
603  }
604  else
605  {
606  rv = fu_open_file(grouppathname, &fd,
607  API_POSIX_O_WRONLY
608  | API_POSIX_O_CREAT,
609  GROUP_PERM);
610  if(!rv)
611  {
612  rv = fu_lock_file(fd);
613  if(!rv)
614  {
615  len = strlen(data);
616  rv = fu_write_to_filedesc(fd, data, len);
617  }
618  fu_close_file(&fd, NULL);
619  }
620  }
621  }
622  }
623  }
624  }
625  }
626  if(rv)
627  {
628  PRINT_ERROR("Importing newsrc failed, using local groupfile");
629  }
630  }
631 
632  /* Release memory */
633  api_posix_free((void*) data);
634  api_posix_free((void*) oldgrouppathname);
635  api_posix_free((void*) grouppathname);
636 }
637 
638 
639 /* ========================================================================== */
640 /*! \brief Shutdown group handling
641  *
642  * This function copy the groupfile back to the location configured by
643  * \c CONF_NEWSRC .
644  */
645 
646 void group_exit(void)
647 {
648  const char* newsrc = config[CONF_NEWSRC].val.s;
649  const char* grouppathname = NULL;
650  char* tmppathname = NULL;
651  int rv;
652  int fd = -1;
653  char* data = NULL;
654  size_t len;
655 
656  if(strlen(newsrc))
657  {
658  /* Read groupfile */
659  rv = group_get_pathname(&grouppathname, groupfile_name);
660  if(!rv)
661  {
662  rv = fu_open_file(grouppathname, &fd, API_POSIX_O_RDWR,
663  (api_posix_mode_t) 0);
664  if(!rv)
665  {
666  rv = fu_lock_file(fd);
667  if(!rv)
668  {
669  rv = fu_read_whole_file(fd, &data, &len);
670  }
671  fu_close_file(&fd, NULL);
672  if(!rv)
673  {
674  /* Write newsrc file */
675  if(main_debug)
676  {
677  printf("%s: %sExport to external newsrc: %s\n",
678  CFG_NAME, MAIN_ERR_PREFIX, newsrc);
679  }
680  tmppathname = (char*) api_posix_malloc(strlen(newsrc)
681  + (size_t) 5);
682  if(NULL == tmppathname)
683  {
684  PRINT_ERROR("Cannot allocate memory for pathname");
685  }
686  else
687  {
688  strcpy(tmppathname, newsrc);
689  strcat(tmppathname, ".new");
690  rv = fu_open_file(tmppathname, &fd,
691  API_POSIX_O_WRONLY | API_POSIX_O_CREAT,
692  GROUP_PERM);
693  if(!rv)
694  {
695  rv = fu_lock_file(fd);
696  if(!rv)
697  {
698  len = strlen(data);
699  rv = fu_write_to_filedesc(fd, data, len);
700  if(rv) { rv = fu_sync(fd, NULL); }
701  if(rv)
702  {
703  PRINT_ERROR("Writing data to newsrc file failed");
704  }
705  else
706  {
707  rv = api_posix_rename(tmppathname, newsrc);
708  if(rv)
709  {
710  PRINT_ERROR("Renaming new newsrc file failed");
711  }
712  }
713  }
714  fu_close_file(&fd, NULL);
715  }
716  }
717  }
718  }
719  }
720  if(rv) { PRINT_ERROR("Exporting groupfile data to newsrc failed"); }
721  }
722 
723  /* Release memory */
724  api_posix_free((void*) data);
725  api_posix_free((void*) tmppathname);
726  api_posix_free((void*) grouppathname);
727 }
728 
729 
730 /* ========================================================================== */
731 /*! \brief Article range constructor
732  *
733  * \param[out] range Pointer to article range structure
734  * \param[in] start First article in range
735  * \param[in] end Last article in range
736  * \param[in] next Pointer to next article range structure in linked list
737  *
738  * If success (zero) is returned, the caller is responsible for releasing the
739  * memory allocated for the article range object.
740  *
741  * \return
742  * - 0 on success
743  * - Negative value on error
744  */
745 
747  core_anum_t start, core_anum_t end,
748  struct core_range* next)
749 {
750  int res = 0;
751 
752  *range = (struct core_range*) api_posix_malloc(sizeof(struct core_range));
753  if (NULL == *range) { res = -1; }
754  else
755  {
756  (*range)->first = start;
757  (*range)->last = end;
758  (*range)->next = next;
759  }
760 
761  return(res);
762 }
763 
764 
765 /* ========================================================================== */
766 /*! \brief Destructor for linked list of article ranges
767  *
768  * \param[in] list Pointer to linked list of article ranges
769  */
770 
772 {
773  struct core_range* tmp_element;
774  struct core_range* current_element = NULL;
775 
776  /* Delete linked list */
777  current_element = *list;
778  while(NULL != current_element)
779  {
780  tmp_element = current_element->next;
781  api_posix_free((void*) current_element);
782  current_element = tmp_element;
783  }
784  *list = NULL;
785 }
786 
787 
788 /* ========================================================================== */
789 /*! \brief Destroy list of subscribed groups
790  *
791  * \param[in] num Pointer to number of groups in list
792  * \param[in] grouplist Pointer to array of group information structures
793  *
794  * If \e grouplist is \c NULL , this function do nothing.
795  * On success, \e num is zero and \e grouplist is \c NULL after return.
796  */
797 
798 void group_destroy_list(size_t* num, struct core_groupstate** grouplist)
799 {
800  if(NULL == grouplist) { return; }
801 
802  while((*num)--)
803  {
804  api_posix_free((void*) (*grouplist)[*num].name);
805  group_article_range_destructor(&(*grouplist)[*num].info);
806  }
807  /* Unsigned integer under/overflow is intended and has defined behaviour */
808  ++(*num);
809  api_posix_free((void*) *grouplist);
810  *grouplist = NULL;
811 }
812 
813 
814 /* ========================================================================== */
815 /*! \brief Get list with subscribed groups
816  *
817  * \param[out] num Pointer to number of groups in list
818  * \param[out] grouplist Pointer to array of group information structures
819  *
820  * If success (zero) is returned, the caller is responsible for releasing the
821  * memory allocated for the group list object.
822  * The destructor \e group_destroy_list() should be used for this purpose.
823  *
824  * \return
825  * - 0 on success
826  * - Negative value on error
827  */
828 
829 int group_get_list(size_t* num, struct core_groupstate** grouplist)
830 {
831  int res = -1;
832  int fd = -1;
833  FILE* fs = NULL;
834  const char* grouppathname = NULL;
835 
836  /* Open and lock group file */
837  res = group_get_pathname(&grouppathname, groupfile_name);
838  if(!res)
839  {
840  res = fu_open_file(grouppathname, &fd,
841  API_POSIX_O_RDWR | API_POSIX_O_CREAT,
842  GROUP_PERM);
843  if(!res) { res = fu_lock_file(fd); }
844  if(!res) { res = fu_assign_stream(fd, &fs, "r"); }
845  }
846 
847  /* Import group list */
848  if(!res) { res = group_import_groups(num, grouplist, fs); }
849 
850  /* Close group file (this will also release the lock) */
851  fu_close_file(&fd, &fs);
852 
853  /* Release memory for file pathname */
854  api_posix_free((void*) grouppathname);
855 
856  return(res);
857 }
858 
859 
860 /* ========================================================================== */
861 /*! \brief Store list with subscribed groups
862  *
863  * \param[in] num Number of elements in group information array
864  * \param[in] grouplist Array of group information structures
865  *
866  * If \e num is zero, \e grouplist is ignored and the configuration is cleared.
867  * All group information elements in the array must be unique, that means it is
868  * not allowed to have multiple entries with the same group name.
869  *
870  * \return
871  * - 0 on success
872  * - Negative value on error
873  */
874 
875 int group_set_list(size_t num, struct core_groupstate* grouplist)
876 {
877  int res = -1;
878  int fd = -1;
879  FILE* fs = NULL;
880  int fd_tmp = -1;
881  FILE* fs_tmp = NULL;
882  const char* grouppathname = NULL;
883  const char* tmppathname = NULL;
884 
885  /* Get file pathnames */
886  res = group_get_pathname(&grouppathname, groupfile_name);
887  if(!res) { res = group_get_pathname(&tmppathname, tmpfile_name); }
888 
889  /* Open and lock group file */
890  if(!res)
891  {
892  res = fu_open_file(grouppathname, &fd,
893  API_POSIX_O_RDWR | API_POSIX_O_CREAT,
894  GROUP_PERM);
895  if(!res) { res = fu_lock_file(fd); }
896  if(!res) { res = fu_assign_stream(fd, &fs, "r"); }
897  }
898 
899  /* Open temporary file for new group list */
900  if(!res)
901  {
902  res = fu_open_file(tmppathname, &fd_tmp,
903  API_POSIX_O_WRONLY | API_POSIX_O_CREAT
904  | API_POSIX_O_TRUNC,
905  GROUP_PERM);
906  /*
907  * Because we have the lock for the group file, it is allowed to
908  * assume that no other instance of the program currently use the
909  * temporary filename.
910  */
911  if(!res) { res = fu_assign_stream(fd_tmp, &fs_tmp, "w"); }
912  }
913 
914  /* Export group list to temporary file */
915  if(!res && num) { res = group_export_groups(num, grouplist, fs, fs_tmp); }
916 
917  /* Flush stream of temporary file*/
918  if(!res) { res = fu_sync(fd_tmp, fs_tmp); }
919 
920  /* Rename temporary file to group file */
921  if(!res) { res = api_posix_rename(tmppathname, grouppathname); }
922 
923  /* Unlink temporary file after error */
924  if(res)
925  {
926  PRINT_ERROR("Failed to store group file");
927  if(tmppathname) { (void) fu_unlink_file(tmppathname); }
928  }
929 
930  /* The tmp file must be removed before releasing the group file lock! */
931  fu_close_file(&fd_tmp, &fs_tmp);
932 
933  /* Close group file (this will also release the lock) */
934  fu_close_file(&fd, &fs);
935 
936  /* Release memory for file pathnames */
937  api_posix_free((void*) tmppathname);
938  api_posix_free((void*) grouppathname);
939 
940  return(res);
941 }
942 
943 
944 /* ========================================================================== */
945 /*! \brief Alphabetically sort list with subscribed groups
946  *
947  * \return
948  * - 0 on success
949  * - Negative value on error
950  */
951 
953 {
954  int res = -1;
955  int fd = -1;
956  FILE* fs = NULL;
957  int fd_tmp = -1;
958  FILE* fs_tmp = NULL;
959  const char* grouppathname = NULL;
960  const char* tmppathname = NULL;
961  char* line;
962  size_t len = 0;
963  api_posix_ssize_t readlen;
964  const char** array = NULL;
965  const char** array_tmp;
966  size_t array_len = 0;
967  int rv;
968  size_t i;
969 
970  /* Get file pathnames */
971  res = group_get_pathname(&grouppathname, groupfile_name);
972  if(!res) { res = group_get_pathname(&tmppathname, tmpfile_name); }
973 
974  /* Open and lock group file */
975  if(!res)
976  {
977  res = fu_open_file(grouppathname, &fd,
978  API_POSIX_O_RDWR | API_POSIX_O_CREAT,
979  GROUP_PERM);
980  if(!res) { res = fu_lock_file(fd); }
981  if(!res) { res = fu_assign_stream(fd, &fs, "r"); }
982  }
983 
984  /* Open temporary file for new group list */
985  if(!res)
986  {
987  /*
988  * Because we have the lock for the group file, it is allowed to
989  * assume that no other instance of the program currently use the
990  * temporary filename.
991  */
992  res = fu_open_file(tmppathname, &fd_tmp,
993  API_POSIX_O_WRONLY | API_POSIX_O_CREAT
994  | API_POSIX_O_TRUNC,
995  GROUP_PERM);
996  if(!res) { res = fu_assign_stream(fd_tmp, &fs_tmp, "w"); }
997  }
998 
999  /* Read groupfile lines */
1000  if(!res)
1001  {
1002  res = -1;
1003  while(1)
1004  {
1005  line = NULL;
1006  readlen = api_posix_getline(&line, &len, fs);
1007  if(-1 == readlen)
1008  {
1009  api_posix_free((void*) line);
1010  if (API_POSIX_ENOMEM == api_posix_errno)
1011  {
1012  PRINT_ERROR("Cannot allocate memory for group data");
1013  break;
1014  }
1015  else
1016  {
1017  /* Check for error */
1018  if(ferror(fs))
1019  {
1020  PRINT_ERROR("Parse error in group file");
1021  break;
1022  }
1023  /* Check for EOF */
1024  if(feof(fs))
1025  {
1026  if(NULL != array) { res = 0; }
1027  break;
1028  }
1029  }
1030  }
1031  if(0 >= readlen) break;
1032  else
1033  {
1034  /* Create array of line pointers */
1035  array_tmp = (const char**)
1036  api_posix_realloc(array, (array_len + (size_t) 1)
1037  * (sizeof(const char*)));
1038  if(NULL == array_tmp) { api_posix_free((void*) line); break; }
1039  else
1040  {
1041  array = array_tmp;
1042  array[array_len++] = line;
1043  }
1044  }
1045  }
1046  }
1047 
1048  /* Sort lines */
1049  if(!res)
1050  {
1051  qsort((void*) array, array_len, sizeof(const char*), group_compar);
1052  for(i = 0; i < array_len; ++i)
1053  {
1054  /* Write modified line to new group file */
1055  rv = fprintf(fs_tmp, "%s", array[i]);
1056  if(0 > rv) { res = -1; break; }
1057  }
1058  }
1059 
1060  /* Release memory for array of lines */
1061  while(array_len) { api_posix_free((void*) array[--array_len]); }
1062  api_posix_free((void*) array);
1063 
1064  /* Flush stream of temporary file*/
1065  if(!res) { res = fu_sync(fd_tmp, fs_tmp); }
1066 
1067  /* Rename temporary file to group file */
1068  if(!res) { res = api_posix_rename(tmppathname, grouppathname); }
1069 
1070  /* Unlink temporary file after error */
1071  if(res)
1072  {
1073  PRINT_ERROR("Failed to store group file");
1074  if(tmppathname) { (void) fu_unlink_file(tmppathname); }
1075  }
1076 
1077  /* The tmp file must be removed before releasing the group file lock! */
1078  fu_close_file(&fd_tmp, &fs_tmp);
1079 
1080  /* Close group file (this will also release the lock) */
1081  fu_close_file(&fd, &fs);
1082 
1083  /* Release memory for file pathnames */
1084  api_posix_free((void*) tmppathname);
1085  api_posix_free((void*) grouppathname);
1086 
1087  return(res);
1088 }
1089 
1090 
1091 /* ========================================================================== */
1092 /*! \brief Add subscribed group
1093  *
1094  * \param[in] group Pointer to group information
1095  *
1096  * \return
1097  * - 0 on success
1098  * - Negative value on error
1099  */
1100 
1101 int group_add(struct core_groupstate* group)
1102 {
1103  int res = -1;
1104  int fd = -1;
1105  FILE* fs = NULL;
1106  int fd_tmp = -1;
1107  FILE* fs_tmp = NULL;
1108  const char* grouppathname = NULL;
1109  const char* tmppathname = NULL;
1110  char* line = NULL;
1111  size_t len = 0;
1112  api_posix_ssize_t readlen;
1113  int rv;
1114  size_t nlen;
1115  char* p;
1116  int found = 0;
1117 
1118  /* Get file pathnames */
1119  res = group_get_pathname(&grouppathname, groupfile_name);
1120  if(!res) { res = group_get_pathname(&tmppathname, tmpfile_name); }
1121 
1122  /* Open and lock group file */
1123  if(!res)
1124  {
1125  res = fu_open_file(grouppathname, &fd,
1126  API_POSIX_O_RDWR | API_POSIX_O_CREAT,
1127  GROUP_PERM);
1128  if(!res) { res = fu_lock_file(fd); }
1129  if(!res) { res = fu_assign_stream(fd, &fs, "r"); }
1130  }
1131 
1132  /* Open temporary file for new group list */
1133  if(!res)
1134  {
1135  res = fu_open_file(tmppathname, &fd_tmp,
1136  API_POSIX_O_WRONLY | API_POSIX_O_CREAT
1137  | API_POSIX_O_TRUNC,
1138  GROUP_PERM);
1139  /*
1140  * Because we have the lock for the group file, it is allowed to
1141  * assume that no other instance of the program currently use the
1142  * temporary filename.
1143  */
1144  if(!res) { res = fu_assign_stream(fd_tmp, &fs_tmp, "w"); }
1145  }
1146 
1147  /* Copy current data to temporary file and append new group if not present */
1148  if(!res)
1149  {
1150  res = -1;
1151  while(1)
1152  {
1153  /* Read line */
1154  readlen = api_posix_getline(&line, &len, fs);
1155  if(-1 == readlen)
1156  {
1157  if (API_POSIX_ENOMEM == api_posix_errno)
1158  {
1159  PRINT_ERROR("Cannot assign memory to read group file");
1160  }
1161  else
1162  {
1163  /* Check for error */
1164  if(ferror(fs))
1165  {
1166  PRINT_ERROR("Reading from group file failed");
1167  break;
1168  }
1169  /* Check for EOF */
1170  if(feof(fs))
1171  {
1172  res = 0;
1173  break;
1174  }
1175  }
1176  }
1177  if(0 >= readlen) break;
1178  else
1179  {
1180  /* Compare with new group */
1181  nlen = strlen(group->name);
1182  if(!strncmp(line, group->name, nlen))
1183  {
1184  /* Verify that group name not only matches partly at beginning */
1185  if(':' == line[nlen] || '!' == line[nlen])
1186  {
1187  found = 1;
1188  /* Check whether group is unsubscribed */
1189  p = strchr(line, (int) '!');
1190  if(NULL != p)
1191  {
1192  /* Yes => Subscribe to group again and preserve state */
1193  *p = ':';
1194  }
1195  }
1196  }
1197  /* Write line */
1198  rv = fprintf(fs_tmp, "%s", line);
1199  if(0 > rv) break;
1200  }
1201  }
1202  /* Append new group if it was not found */
1203  if(!res && !found) { res = group_print_entry(fs_tmp, group); }
1204  }
1205 
1206  /* Flush stream of temporary file*/
1207  if(!res) { res = fu_sync(fd_tmp, fs_tmp); }
1208 
1209  /* Rename temporary file to group file */
1210  if(!res) { res = api_posix_rename(tmppathname, grouppathname); }
1211 
1212  /* Unlink temporary file after error */
1213  if(res)
1214  {
1215  PRINT_ERROR("Failed to store group file");
1216  if(tmppathname) { (void) fu_unlink_file(tmppathname); }
1217  }
1218 
1219  /* The tmp file must be removed before releasing the group file lock! */
1220  fu_close_file(&fd_tmp, &fs_tmp);
1221 
1222  /* Close group file (this will also release the lock) */
1223  fu_close_file(&fd, &fs);
1224 
1225  /* Release memory for file pathnames */
1226  api_posix_free((void*) line);
1227  api_posix_free((void*) tmppathname);
1228  api_posix_free((void*) grouppathname);
1229 
1230  return(res);
1231 }
1232 
1233 
1234 /* ========================================================================== */
1235 /*! \brief Delete group states
1236  *
1237  * This is necessary after changing the server because every server use its
1238  * own article numbers (on which our group states are based).
1239  *
1240  * \return
1241  * - 0 on success
1242  * - Negative value on error
1243  */
1245 {
1246  int res = -1;
1247  size_t num;
1248  struct core_groupstate* gs;
1249  size_t i;
1250 
1251  /* Get group list with states */
1252  res = group_get_list(&num, &gs);
1253  if(!res)
1254  {
1255  /* Strip all states leaving only the group names */
1256  for(i = 0; i < num; ++i) { group_article_range_destructor(&gs[i].info); }
1257  group_set_list(0, NULL);
1258  res = group_set_list(num, gs);
1259  group_destroy_list(&num, &gs);
1260  }
1261 
1262  return(res);
1263 }
1264 
1265 
1266 /*! @} */
1267 
1268 /* EOF */
fu_write_to_filedesc
int fu_write_to_filedesc(int filedesc, const char *buffer, size_t len)
Write data block to filedescriptor.
Definition: fileutils.c:552
group_reset_states
int group_reset_states(void)
Delete group states.
Definition: group.c:1244
core_range::next
struct core_range * next
Definition: core.h:78
group_sort_list
int group_sort_list(void)
Alphabetically sort list with subscribed groups.
Definition: group.c:952
fu_assign_stream
int fu_assign_stream(int filedesc, FILE **stream, const char *mode)
Assign I/O stream to open file.
Definition: fileutils.c:380
core_anum_t
#define core_anum_t
Article number data type (value zero is always reserved)
Definition: core.h:24
fu_lock_file
int fu_lock_file(int filedesc)
Lock file for writing.
Definition: fileutils.c:335
core_range
Article range linked list element.
Definition: core.h:74
group_add
int group_add(struct core_groupstate *group)
Add subscribed group.
Definition: group.c:1101
core_groupstate
Group description.
Definition: core.h:82
enc_ascii_check
int enc_ascii_check(const char *s)
Verify ASCII encoding.
Definition: encoding.c:3922
config
struct conf config[CONF_NUM]
Global configuration.
Definition: conf.c:63
enc_convert_ascii_to_anum
int enc_convert_ascii_to_anum(core_anum_t *result, const char *wm, int len)
Convert number from ASCII to numerical format.
Definition: encoding.c:3621
conf_entry_val::s
char * s
Definition: conf.h:105
group_set_list
int group_set_list(size_t num, struct core_groupstate *grouplist)
Store list with subscribed groups.
Definition: group.c:875
core_range::first
core_anum_t first
Definition: core.h:76
main_debug
int main_debug
Enable additional debug output if nonzero.
Definition: main.cxx:64
group_article_range_destructor
void group_article_range_destructor(struct core_range **list)
Destructor for linked list of article ranges.
Definition: group.c:771
group_article_range_constructor
int group_article_range_constructor(struct core_range **range, core_anum_t start, core_anum_t end, struct core_range *next)
Article range constructor.
Definition: group.c:746
group_get_list
int group_get_list(size_t *num, struct core_groupstate **grouplist)
Get list with subscribed groups.
Definition: group.c:829
fu_create_path
int fu_create_path(const char *path, api_posix_mode_t perm)
Create path.
Definition: fileutils.c:122
MAIN_ERR_PREFIX
#define MAIN_ERR_PREFIX
Message prefix for GROUPHANDLING module.
Definition: group.c:46
enc_ascii_check_digit
int enc_ascii_check_digit(const char *s)
Check for ASCII digit characters.
Definition: encoding.c:3973
fu_unlink_file
int fu_unlink_file(const char *pathname)
Unlink file.
Definition: fileutils.c:362
core_groupstate::name
const char * name
Definition: core.h:84
PRINT_ERROR
#define PRINT_ERROR(s)
Prepend module prefix and print error message.
Definition: main.h:19
data
struct core_data data
Global data object (shared by all threads)
Definition: core.c:242
GROUP_PERM
#define GROUP_PERM
Permissions for group file.
Definition: group.c:50
CONF_NEWSRC
Definition: conf.h:49
group_exit
void group_exit(void)
Shutdown group handling.
Definition: group.c:646
xdg_get_confdir
const char * xdg_get_confdir(const char *)
Get configuration directory.
Definition: xdg.c:116
xdg_append_to_path
int xdg_append_to_path(const char **, const char *)
Append path component to buffer.
Definition: xdg.c:55
group_init
void group_init(void)
Initialize group handling.
Definition: group.c:550
core_range::last
core_anum_t last
Definition: core.h:77
fu_sync
int fu_sync(int filedesc, FILE *stream)
Flush buffers of file.
Definition: fileutils.c:409
fu_close_file
void fu_close_file(int *filedesc, FILE **stream)
Close file (and potentially associated I/O stream)
Definition: fileutils.c:297
conf::val
union conf_entry_val val
Definition: conf.h:113
core_groupstate::info
struct core_range * info
Definition: core.h:85
group_destroy_list
void group_destroy_list(size_t *num, struct core_groupstate **grouplist)
Destroy list of subscribed groups.
Definition: group.c:798
CORE_ANUM_T_MAX
#define CORE_ANUM_T_MAX
Article number limit.
Definition: core.h:180
fu_read_whole_file
int fu_read_whole_file(int filedesc, char **buffer, size_t *len)
Read text file content and store it into memory buffer.
Definition: fileutils.c:452
fu_open_file
int fu_open_file(const char *pathname, int *filedesc, int mode, api_posix_mode_t perm)
Open file.
Definition: fileutils.c:246

Generated at 2026-01-27 using  doxygen