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-2020 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 (posix_mode_t) (POSIX_S_IRUSR | 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*) posix_malloc(strlen(aa) + (size_t) 1);
84  y = (char*) 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  posix_errno = 0;
94  res = strcoll(x, y);
95  if(posix_errno) { res = 0; }
96  }
97  posix_free((void*) x);
98  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, (posix_mode_t) 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  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) 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  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*) 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 = posix_getline(&line, &len, fs);
322  if(-1 == readlen)
323  {
324  if (POSIX_ENOMEM == 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  posix_free((void*) line);
412  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  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 = posix_getline(&line, &len, fs);
448  if(-1 == readlen)
449  {
450  posix_free((void*) line);
451  if (POSIX_ENOMEM == 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  posix_free((void*) line);
480  continue;
481  }
482  /* Extract group name */
483  if(strchr(line, (int) '!'))
484  {
485  /* Ignore unsubscribed groups */
486  posix_free(line);
487  continue;
488  }
489  c = strchr(line, (int) ':');
490  if(NULL == c)
491  {
492  /* Invalid format => Ignore line */
493  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  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  posix_free((void*) line);
506  break;
507  }
508  else
509  {
510  *grouplist = p;
511  line[readlen - (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  struct_posix_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 = posix_stat(newsrc, &state);
569  if(rv) { PRINT_ERROR("Cannot stat newsrc file"); }
570  else if(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, POSIX_O_RDWR, (posix_mode_t) 0);
577  if(!rv)
578  {
579  rv = fu_lock_file(fd);
580  if(!rv)
581  {
582  rv = fu_read_whole_file(fd, &data, &len);
583  }
584  fu_close_file(&fd, NULL);
585  if(!rv)
586  {
587  oldgrouppathname =
588  (char*) posix_malloc(strlen(grouppathname) + (size_t) 5);
589  if(NULL == oldgrouppathname)
590  {
591  PRINT_ERROR("Cannot allocate memory for pathname");
592  }
593  else
594  {
595  strcpy(oldgrouppathname, grouppathname);
596  strcat(oldgrouppathname, ".old");
597  rv = posix_rename(grouppathname, oldgrouppathname);
598  if(rv)
599  {
600  PRINT_ERROR("Renaming groupfile failed");
601  }
602  else
603  {
604  rv = fu_open_file(grouppathname, &fd,
605  POSIX_O_WRONLY | POSIX_O_CREAT,
606  GROUP_PERM);
607  if(!rv)
608  {
609  rv = fu_lock_file(fd);
610  if(!rv)
611  {
612  len = strlen(data);
613  rv = fu_write_to_filedesc(fd, data, len);
614  }
615  fu_close_file(&fd, NULL);
616  }
617  }
618  }
619  }
620  }
621  }
622  }
623  if(rv)
624  {
625  PRINT_ERROR("Importing newsrc failed, using local groupfile");
626  }
627  }
628 
629  /* Release memory */
630  posix_free((void*) data);
631  posix_free((void*) oldgrouppathname);
632  posix_free((void*) grouppathname);
633 }
634 
635 
636 /* ========================================================================== */
637 /*! \brief Shutdown group handling
638  *
639  * This function copy the groupfile back to the location configured by
640  * \c CONF_NEWSRC .
641  */
642 
643 void group_exit(void)
644 {
645  const char* newsrc = config[CONF_NEWSRC].val.s;
646  const char* grouppathname = NULL;
647  char* tmppathname = NULL;
648  int rv;
649  int fd = -1;
650  char* data = NULL;
651  size_t len;
652 
653  if(strlen(newsrc))
654  {
655  /* Read groupfile */
656  rv = group_get_pathname(&grouppathname, groupfile_name);
657  if(!rv)
658  {
659  rv = fu_open_file(grouppathname, &fd, POSIX_O_RDWR,
660  (posix_mode_t) 0);
661  if(!rv)
662  {
663  rv = fu_lock_file(fd);
664  if(!rv)
665  {
666  rv = fu_read_whole_file(fd, &data, &len);
667  }
668  fu_close_file(&fd, NULL);
669  if(!rv)
670  {
671  /* Write newsrc file */
672  if(main_debug)
673  {
674  printf("%s: %sExport to external newsrc: %s\n",
675  CFG_NAME, MAIN_ERR_PREFIX, newsrc);
676  }
677  tmppathname = (char*) posix_malloc(strlen(newsrc) + (size_t) 5);
678  if(NULL == tmppathname)
679  {
680  PRINT_ERROR("Cannot allocate memory for pathname");
681  }
682  else
683  {
684  strcpy(tmppathname, newsrc);
685  strcat(tmppathname, ".new");
686  rv = fu_open_file(tmppathname, &fd,
687  POSIX_O_WRONLY | POSIX_O_CREAT, GROUP_PERM);
688  if(!rv)
689  {
690  rv = fu_lock_file(fd);
691  if(!rv)
692  {
693  len = strlen(data);
694  rv = fu_write_to_filedesc(fd, data, len);
695  if(rv) { rv = fu_sync(fd, NULL); }
696  if(rv)
697  {
698  PRINT_ERROR("Writing data to newsrc file failed");
699  }
700  else
701  {
702  rv = posix_rename(tmppathname, newsrc);
703  if(rv)
704  {
705  PRINT_ERROR("Renaming new newsrc file failed");
706  }
707  }
708  }
709  fu_close_file(&fd, NULL);
710  }
711  }
712  }
713  }
714  }
715  if(rv) { PRINT_ERROR("Exporting groupfile data to newsrc failed"); }
716  }
717 
718  /* Release memory */
719  posix_free((void*) data);
720  posix_free((void*) tmppathname);
721  posix_free((void*) grouppathname);
722 }
723 
724 
725 /* ========================================================================== */
726 /*! \brief Article range constructor
727  *
728  * \param[out] range Pointer to article range structure
729  * \param[in] start First article in range
730  * \param[in] end Last article in range
731  * \param[in] next Pointer to next article range structure in linked list
732  *
733  * If success (zero) is returned, the caller is responsible for releasing the
734  * memory allocated for the article range object.
735  *
736  * \return
737  * - 0 on success
738  * - Negative value on error
739  */
740 
742  core_anum_t start, core_anum_t end,
743  struct core_range* next)
744 {
745  int res = 0;
746 
747  *range = (struct core_range*) posix_malloc(sizeof(struct core_range));
748  if (NULL == *range) { res = -1; }
749  else
750  {
751  (*range)->first = start;
752  (*range)->last = end;
753  (*range)->next = next;
754  }
755 
756  return(res);
757 }
758 
759 
760 /* ========================================================================== */
761 /*! \brief Destructor for linked list of article ranges
762  *
763  * \param[in] list Pointer to linked list of article ranges
764  */
765 
767 {
768  struct core_range* tmp_element;
769  struct core_range* current_element = NULL;
770 
771  /* Delete linked list */
772  current_element = *list;
773  while(NULL != current_element)
774  {
775  tmp_element = current_element->next;
776  posix_free((void*) current_element);
777  current_element = tmp_element;
778  }
779  *list = NULL;
780 }
781 
782 
783 /* ========================================================================== */
784 /*! \brief Destroy list of subscribed groups
785  *
786  * \param[in] num Pointer to number of groups in list
787  * \param[in] grouplist Pointer to array of group information structures
788  *
789  * If \e grouplist is \c NULL , this function do nothing.
790  * On success, \e num is zero and \e grouplist is \c NULL after return.
791  */
792 
793 void group_destroy_list(size_t* num, struct core_groupstate** grouplist)
794 {
795  if(NULL == grouplist) { return; }
796 
797  while((*num)--)
798  {
799  posix_free((void*) (*grouplist)[*num].name);
800  group_article_range_destructor(&(*grouplist)[*num].info);
801  }
802  /* Unsigned integer under/overflow is intended and has defined behaviour */
803  ++(*num);
804  posix_free((void*) *grouplist);
805  *grouplist = NULL;
806 }
807 
808 
809 /* ========================================================================== */
810 /*! \brief Get list with subscribed groups
811  *
812  * \param[out] num Pointer to number of groups in list
813  * \param[out] grouplist Pointer to array of group information structures
814  *
815  * If success (zero) is returned, the caller is responsible for releasing the
816  * memory allocated for the group list object.
817  * The destructor \e group_destroy_list() should be used for this purpose.
818  *
819  * \return
820  * - 0 on success
821  * - Negative value on error
822  */
823 
824 int group_get_list(size_t* num, struct core_groupstate** grouplist)
825 {
826  int res = -1;
827  int fd = -1;
828  FILE* fs = NULL;
829  const char* grouppathname = NULL;
830 
831  /* Open and lock group file */
832  res = group_get_pathname(&grouppathname, groupfile_name);
833  if(!res)
834  {
835  res = fu_open_file(grouppathname, &fd, POSIX_O_RDWR | POSIX_O_CREAT,
836  GROUP_PERM);
837  if(!res) { res = fu_lock_file(fd); }
838  if(!res) { res = fu_assign_stream(fd, &fs, "r"); }
839  }
840 
841  /* Import group list */
842  if(!res) { res = group_import_groups(num, grouplist, fs); }
843 
844  /* Close group file (this will also release the lock) */
845  fu_close_file(&fd, &fs);
846 
847  /* Release memory for file pathname */
848  posix_free((void*) grouppathname);
849 
850  return(res);
851 }
852 
853 
854 /* ========================================================================== */
855 /*! \brief Store list with subscribed groups
856  *
857  * \param[in] num Number of elements in group information array
858  * \param[in] grouplist Array of group information structures
859  *
860  * If \e num is zero, \e grouplist is ignored and the configuration is cleared.
861  * All group information elements in the array must be unique, that means it is
862  * not allowed to have multiple entries with the same group name.
863  *
864  * \return
865  * - 0 on success
866  * - Negative value on error
867  */
868 
869 int group_set_list(size_t num, struct core_groupstate* grouplist)
870 {
871  int res = -1;
872  int fd = -1;
873  FILE* fs = NULL;
874  int fd_tmp = -1;
875  FILE* fs_tmp = NULL;
876  const char* grouppathname = NULL;
877  const char* tmppathname = NULL;
878 
879  /* Get file pathnames */
880  res = group_get_pathname(&grouppathname, groupfile_name);
881  if(!res) { res = group_get_pathname(&tmppathname, tmpfile_name); }
882 
883  /* Open and lock group file */
884  if(!res)
885  {
886  res = fu_open_file(grouppathname, &fd, POSIX_O_RDWR | POSIX_O_CREAT,
887  GROUP_PERM);
888  if(!res) { res = fu_lock_file(fd); }
889  if(!res) { res = fu_assign_stream(fd, &fs, "r"); }
890  }
891 
892  /* Open temporary file for new group list */
893  if(!res)
894  {
895  res = fu_open_file(tmppathname, &fd_tmp,
896  POSIX_O_WRONLY | POSIX_O_CREAT | POSIX_O_TRUNC,
897  GROUP_PERM);
898  /*
899  * Because we have the lock for the group file, it is allowed to
900  * assume that no other instance of the program currently use the
901  * temporary filename.
902  */
903  if(!res) { res = fu_assign_stream(fd_tmp, &fs_tmp, "w"); }
904  }
905 
906  /* Export group list to temporary file */
907  if(!res && num) { res = group_export_groups(num, grouplist, fs, fs_tmp); }
908 
909  /* Flush stream of temporary file*/
910  if(!res) { res = fu_sync(fd_tmp, fs_tmp); }
911 
912  /* Rename temporary file to group file */
913  if(!res) { res = posix_rename(tmppathname, grouppathname); }
914 
915  /* Unlink temporary file after error */
916  if(res)
917  {
918  PRINT_ERROR("Failed to store group file");
919  if(tmppathname) { (void) fu_unlink_file(tmppathname); }
920  }
921 
922  /* The tmp file must be removed before releasing the group file lock! */
923  fu_close_file(&fd_tmp, &fs_tmp);
924 
925  /* Close group file (this will also release the lock) */
926  fu_close_file(&fd, &fs);
927 
928  /* Release memory for file pathnames */
929  posix_free((void*) tmppathname);
930  posix_free((void*) grouppathname);
931 
932  return(res);
933 }
934 
935 
936 /* ========================================================================== */
937 /*! \brief Alphabetically sort list with subscribed groups
938  *
939  * \return
940  * - 0 on success
941  * - Negative value on error
942  */
943 
945 {
946  int res = -1;
947  int fd = -1;
948  FILE* fs = NULL;
949  int fd_tmp = -1;
950  FILE* fs_tmp = NULL;
951  const char* grouppathname = NULL;
952  const char* tmppathname = NULL;
953  char* line;
954  size_t len = 0;
955  posix_ssize_t readlen;
956  const char** array = NULL;
957  const char** array_tmp;
958  size_t array_len = 0;
959  int rv;
960  size_t i;
961 
962  /* Get file pathnames */
963  res = group_get_pathname(&grouppathname, groupfile_name);
964  if(!res) { res = group_get_pathname(&tmppathname, tmpfile_name); }
965 
966  /* Open and lock group file */
967  if(!res)
968  {
969  res = fu_open_file(grouppathname, &fd, POSIX_O_RDWR | POSIX_O_CREAT,
970  GROUP_PERM);
971  if(!res) { res = fu_lock_file(fd); }
972  if(!res) { res = fu_assign_stream(fd, &fs, "r"); }
973  }
974 
975  /* Open temporary file for new group list */
976  if(!res)
977  {
978  /*
979  * Because we have the lock for the group file, it is allowed to
980  * assume that no other instance of the program currently use the
981  * temporary filename.
982  */
983  res = fu_open_file(tmppathname, &fd_tmp,
984  POSIX_O_WRONLY | POSIX_O_CREAT | POSIX_O_TRUNC,
985  GROUP_PERM);
986  if(!res) { res = fu_assign_stream(fd_tmp, &fs_tmp, "w"); }
987  }
988 
989  /* Read groupfile lines */
990  if(!res)
991  {
992  res = -1;
993  while(1)
994  {
995  line = NULL;
996  readlen = posix_getline(&line, &len, fs);
997  if(-1 == readlen)
998  {
999  posix_free((void*) line);
1000  if (POSIX_ENOMEM == posix_errno)
1001  {
1002  PRINT_ERROR("Cannot allocate memory for group data");
1003  break;
1004  }
1005  else
1006  {
1007  /* Check for error */
1008  if(ferror(fs))
1009  {
1010  PRINT_ERROR("Parse error in group file");
1011  break;
1012  }
1013  /* Check for EOF */
1014  if(feof(fs))
1015  {
1016  if(NULL != array) { res = 0; }
1017  break;
1018  }
1019  }
1020  }
1021  if(0 >= readlen) break;
1022  else
1023  {
1024  /* Create array of line pointers */
1025  array_tmp = (const char**)
1026  posix_realloc(array, (array_len + (size_t) 1)
1027  * (sizeof(const char*)));
1028  if(NULL == array_tmp) { posix_free((void*) line); break; }
1029  else
1030  {
1031  array = array_tmp;
1032  array[array_len++] = line;
1033  }
1034  }
1035  }
1036  }
1037 
1038  /* Sort lines */
1039  if(!res)
1040  {
1041  qsort((void*) array, array_len, sizeof(const char*), group_compar);
1042  for(i = 0; i < array_len; ++i)
1043  {
1044  /* Write modified line to new group file */
1045  rv = fprintf(fs_tmp, "%s", array[i]);
1046  if(0 > rv) { res = -1; break; }
1047  }
1048  }
1049 
1050  /* Release memory for array of lines */
1051  while(array_len) { posix_free((void*) array[--array_len]); }
1052  posix_free((void*) array);
1053 
1054  /* Flush stream of temporary file*/
1055  if(!res) { res = fu_sync(fd_tmp, fs_tmp); }
1056 
1057  /* Rename temporary file to group file */
1058  if(!res) { res = posix_rename(tmppathname, grouppathname); }
1059 
1060  /* Unlink temporary file after error */
1061  if(res)
1062  {
1063  PRINT_ERROR("Failed to store group file");
1064  if(tmppathname) { (void) fu_unlink_file(tmppathname); }
1065  }
1066 
1067  /* The tmp file must be removed before releasing the group file lock! */
1068  fu_close_file(&fd_tmp, &fs_tmp);
1069 
1070  /* Close group file (this will also release the lock) */
1071  fu_close_file(&fd, &fs);
1072 
1073  /* Release memory for file pathnames */
1074  posix_free((void*) tmppathname);
1075  posix_free((void*) grouppathname);
1076 
1077  return(res);
1078 }
1079 
1080 
1081 /* ========================================================================== */
1082 /*! \brief Add subscribed group
1083  *
1084  * \param[in] group Pointer to group information
1085  *
1086  * \return
1087  * - 0 on success
1088  * - Negative value on error
1089  */
1090 
1091 int group_add(struct core_groupstate* group)
1092 {
1093  int res = -1;
1094  int fd = -1;
1095  FILE* fs = NULL;
1096  int fd_tmp = -1;
1097  FILE* fs_tmp = NULL;
1098  const char* grouppathname = NULL;
1099  const char* tmppathname = NULL;
1100  char* line = NULL;
1101  size_t len = 0;
1102  posix_ssize_t readlen;
1103  int rv;
1104  size_t nlen;
1105  char* p;
1106  int found = 0;
1107 
1108  /* Get file pathnames */
1109  res = group_get_pathname(&grouppathname, groupfile_name);
1110  if(!res) { res = group_get_pathname(&tmppathname, tmpfile_name); }
1111 
1112  /* Open and lock group file */
1113  if(!res)
1114  {
1115  res = fu_open_file(grouppathname, &fd, POSIX_O_RDWR | POSIX_O_CREAT,
1116  GROUP_PERM);
1117  if(!res) { res = fu_lock_file(fd); }
1118  if(!res) { res = fu_assign_stream(fd, &fs, "r"); }
1119  }
1120 
1121  /* Open temporary file for new group list */
1122  if(!res)
1123  {
1124  res = fu_open_file(tmppathname, &fd_tmp,
1125  POSIX_O_WRONLY | POSIX_O_CREAT | POSIX_O_TRUNC,
1126  GROUP_PERM);
1127  /*
1128  * Because we have the lock for the group file, it is allowed to
1129  * assume that no other instance of the program currently use the
1130  * temporary filename.
1131  */
1132  if(!res) { res = fu_assign_stream(fd_tmp, &fs_tmp, "w"); }
1133  }
1134 
1135  /* Copy current data to temporary file and append new group if not present */
1136  if(!res)
1137  {
1138  res = -1;
1139  while(1)
1140  {
1141  /* Read line */
1142  readlen = posix_getline(&line, &len, fs);
1143  if(-1 == readlen)
1144  {
1145  if (POSIX_ENOMEM == posix_errno)
1146  {
1147  PRINT_ERROR("Cannot assign memory to read group file");
1148  }
1149  else
1150  {
1151  /* Check for error */
1152  if(ferror(fs))
1153  {
1154  PRINT_ERROR("Reading from group file failed");
1155  break;
1156  }
1157  /* Check for EOF */
1158  if(feof(fs))
1159  {
1160  res = 0;
1161  break;
1162  }
1163  }
1164  }
1165  if(0 >= readlen) break;
1166  else
1167  {
1168  /* Compare with new group */
1169  nlen = strlen(group->name);
1170  if(!strncmp(line, group->name, nlen))
1171  {
1172  /* Verify that group name not only matches partly at beginning */
1173  if(':' == line[nlen] || '!' == line[nlen])
1174  {
1175  found = 1;
1176  /* Check whether group is unsubscribed */
1177  p = strchr(line, (int) '!');
1178  if(NULL != p)
1179  {
1180  /* Yes => Subscribe to group again and preserve state */
1181  *p = ':';
1182  }
1183  }
1184  }
1185  /* Write line */
1186  rv = fprintf(fs_tmp, "%s", line);
1187  if(0 > rv) break;
1188  }
1189  }
1190  /* Append new group if it was not found */
1191  if(!res && !found) { res = group_print_entry(fs_tmp, group); }
1192  }
1193 
1194  /* Flush stream of temporary file*/
1195  if(!res) { res = fu_sync(fd_tmp, fs_tmp); }
1196 
1197  /* Rename temporary file to group file */
1198  if(!res) { res = posix_rename(tmppathname, grouppathname); }
1199 
1200  /* Unlink temporary file after error */
1201  if(res)
1202  {
1203  PRINT_ERROR("Failed to store group file");
1204  if(tmppathname) { (void) fu_unlink_file(tmppathname); }
1205  }
1206 
1207  /* The tmp file must be removed before releasing the group file lock! */
1208  fu_close_file(&fd_tmp, &fs_tmp);
1209 
1210  /* Close group file (this will also release the lock) */
1211  fu_close_file(&fd, &fs);
1212 
1213  /* Release memory for file pathnames */
1214  posix_free((void*) line);
1215  posix_free((void*) tmppathname);
1216  posix_free((void*) grouppathname);
1217 
1218  return(res);
1219 }
1220 
1221 
1222 /* ========================================================================== */
1223 /*! \brief Delete group states
1224  *
1225  * This is necessary after changing the server because every server use its
1226  * own article numbers (on which our group states are based).
1227  *
1228  * \return
1229  * - 0 on success
1230  * - Negative value on error
1231  */
1233 {
1234  int res = -1;
1235  size_t num;
1236  struct core_groupstate* gs;
1237  size_t i;
1238 
1239  /* Get group list with states */
1240  res = group_get_list(&num, &gs);
1241  if(!res)
1242  {
1243  /* Strip all states leaving only the group names */
1244  for(i = 0; i < num; ++i) { group_article_range_destructor(&gs[i].info); }
1245  group_set_list(0, NULL);
1246  res = group_set_list(num, gs);
1247  group_destroy_list(&num, &gs);
1248  }
1249 
1250  return(res);
1251 }
1252 
1253 
1254 /*! @} */
1255 
1256 /* 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:542
group_reset_states
int group_reset_states(void)
Delete group states.
Definition: group.c:1232
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:944
fu_assign_stream
int fu_assign_stream(int filedesc, FILE **stream, const char *mode)
Assign I/O stream to open file.
Definition: fileutils.c:373
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:328
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:1091
core_groupstate
Group description.
Definition: core.h:82
enc_ascii_check
int enc_ascii_check(const char *s)
Verify ASCII encoding.
Definition: encoding.c:4944
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:4604
conf_entry_val::s
char * s
Definition: conf.h:103
group_set_list
int group_set_list(size_t num, struct core_groupstate *grouplist)
Store list with subscribed groups.
Definition: group.c:869
fu_create_path
int fu_create_path(const char *path, posix_mode_t perm)
Create path.
Definition: fileutils.c:119
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:766
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:741
group_get_list
int group_get_list(size_t *num, struct core_groupstate **grouplist)
Get list with subscribed groups.
Definition: group.c:824
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:4995
fu_unlink_file
int fu_unlink_file(const char *pathname)
Unlink file.
Definition: fileutils.c:355
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:643
xdg_get_confdir
const char * xdg_get_confdir(const char *)
Get configuration directory.
Definition: xdg.c:115
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:402
fu_close_file
void fu_close_file(int *filedesc, FILE **stream)
Close file (and potentially associated I/O stream)
Definition: fileutils.c:290
conf::val
union conf_entry_val val
Definition: conf.h:111
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:793
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:445
fu_open_file
int fu_open_file(const char *pathname, int *filedesc, int mode, posix_mode_t perm)
Open file.
Definition: fileutils.c:243

Generated at 2024-04-27 using  doxygen