- changed the umask handling. We now set the umask to 0 and explicitly
[kai/samba.git] / source3 / locking / shmem.c
1 /* 
2    Unix SMB/Netbios implementation.
3    Version 1.9.
4    Shared memory functions
5    Copyright (C) Erik Devriendt 1996
6    
7    This program is free software; you can redistribute it and/or modify
8    it under the terms of the GNU General Public License as published by
9    the Free Software Foundation; either version 2 of the License, or
10    (at your option) any later version.
11    
12    This program is distributed in the hope that it will be useful,
13    but WITHOUT ANY WARRANTY; without even the implied warranty of
14    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15    GNU General Public License for more details.
16    
17    You should have received a copy of the GNU General Public License
18    along with this program; if not, write to the Free Software
19    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
20
21 */
22
23 #include "includes.h"
24
25
26 #if FAST_SHARE_MODES
27
28
29 extern int DEBUGLEVEL;
30
31
32 #define SHM_MAGIC 0x53484100
33 /* = "SHM" in hex */
34
35 #define SHM_VERSION 1
36
37 /* WARNING : offsets are used because mmap() does not guarantee that all processes have the 
38    shared memory mapped to the same address */
39
40 struct ShmHeader
41 {
42    int shm_magic;
43    int shm_version;
44    int total_size;      /* in bytes */
45    BOOL consistent;
46    shm_offset_t first_free_off;
47    shm_offset_t userdef_off;    /* a userdefined offset. can be used to store root of tree or list */
48    struct {             /* a cell is a range of bytes of sizeof(struct ShmBlockDesc) size */
49       int cells_free;
50       int cells_used;
51       int cells_system; /* number of cells used as allocated block descriptors */
52    } statistics;
53 };
54
55 #define SHM_NOT_FREE_OFF (-1)
56 struct ShmBlockDesc
57 {
58    shm_offset_t next;   /* offset of next block in the free list or SHM_NOT_FREE_OFF when block in use  */
59    int          size;   /* user size in BlockDescSize units */
60 };
61
62 #define EOList_Addr     (struct ShmBlockDesc *)( 0 )
63 #define EOList_Off      (NULL_OFFSET)
64
65 #define CellSize        sizeof(struct ShmBlockDesc)
66
67 /* HeaderSize aligned on 8 byte boundary */
68 #define AlignedHeaderSize       ((sizeof(struct ShmHeader)+7) & ~7)
69
70 static int  shm_fd = -1;
71 static pstring shm_processreg_name = "";
72
73 static struct ShmHeader *shm_header_p = (struct ShmHeader *)0;
74 static int shm_times_locked = 0;
75
76 static BOOL shm_register_process(char *processreg_file, pid_t pid, BOOL *other_processes)
77 {
78    int shm_processes_fd = -1;
79    int nb_read;
80    pid_t other_pid;
81    int free_slot = -1;
82    int erased_slot;   
83    
84    shm_processes_fd = open(processreg_file, O_RDWR | O_CREAT, 0666);
85    if ( shm_processes_fd < 0 )
86    {
87       DEBUG(0,("ERROR shm_register_process : processreg_file open failed with code %d\n",errno));
88       return False;
89    }
90    
91    *other_processes = False;
92    
93    while ((nb_read = read(shm_processes_fd, &other_pid, sizeof(other_pid))) > 0)
94    {
95       if(other_pid)
96       {
97          if(process_exists(other_pid))
98             *other_processes = True;
99          else
100          {
101             /* erase old pid */
102             DEBUG(2,("shm_register_process : erasing stale record for pid %d\n",other_pid));
103             other_pid = (pid_t)0;
104             erased_slot = lseek(shm_processes_fd, -sizeof(other_pid), SEEK_CUR);
105             write(shm_processes_fd, &other_pid, sizeof(other_pid));
106             if(free_slot < 0)
107                free_slot = erased_slot;
108          }
109       }
110       else 
111          if(free_slot < 0)
112             free_slot = lseek(shm_processes_fd, -sizeof(other_pid), SEEK_CUR);
113    }
114    if (nb_read < 0)
115    {
116       DEBUG(0,("ERROR shm_register_process : processreg_file read failed with code %d\n",errno));
117       close(shm_processes_fd);
118       return False;
119    }
120    
121    if(free_slot < 0)
122       free_slot = lseek(shm_processes_fd, 0, SEEK_END);
123
124    DEBUG(2,("shm_register_process : writing record for pid %d at offset %d\n",pid,free_slot));
125    lseek(shm_processes_fd, free_slot, SEEK_SET);
126    if(write(shm_processes_fd, &pid, sizeof(pid)) < 0)
127    {
128       DEBUG(0,("ERROR shm_register_process : processreg_file write failed with code %d\n",errno));
129       close(shm_processes_fd);
130       return False;
131    }
132
133    close(shm_processes_fd);
134
135    return True;
136 }
137
138 static BOOL shm_unregister_process(char *processreg_file, pid_t pid)
139 {
140    int old_umask;
141    int shm_processes_fd = -1;
142    int nb_read;
143    pid_t other_pid;
144    int erased_slot;
145    BOOL found = False;
146    
147    
148    old_umask = umask(0);
149    shm_processes_fd = open(processreg_file, O_RDWR);
150    umask(old_umask);
151    if ( shm_processes_fd < 0 )
152    {
153       DEBUG(0,("ERROR shm_unregister_process : processreg_file open failed with code %d\n",errno));
154       return False;
155    }
156    
157    while ((nb_read = read(shm_processes_fd, &other_pid, sizeof(other_pid))) > 0)
158    {
159       if(other_pid == pid)
160       {
161          /* erase pid */
162          DEBUG(2,("shm_unregister_process : erasing record for pid %d\n",other_pid));
163          other_pid = (pid_t)0;
164          erased_slot = lseek(shm_processes_fd, -sizeof(other_pid), SEEK_CUR);
165          if(write(shm_processes_fd, &other_pid, sizeof(other_pid)) < 0)
166          {
167             DEBUG(0,("ERROR shm_unregister_process : processreg_file write failed with code %d\n",errno));
168             close(shm_processes_fd);
169             return False;
170          }
171          
172          found = True;
173          break;
174       }
175    }
176    if (nb_read < 0)
177    {
178       DEBUG(0,("ERROR shm_unregister_process : processreg_file read failed with code %d\n",errno));
179       close(shm_processes_fd);
180       return False;
181    }
182    
183    if(!found)
184    {
185       DEBUG(0,("ERROR shm_unregister_process : couldn't find pid %d in file %s\n",pid,processreg_file));
186       close(shm_processes_fd);
187       return False;
188    }
189       
190    
191    close(shm_processes_fd);
192
193    return True;
194 }
195
196
197 static BOOL shm_validate_header(int size)
198 {
199    if( !shm_header_p )
200    {
201       /* not mapped yet */
202       DEBUG(0,("ERROR shm_validate_header : shmem not mapped\n"));
203       return False;
204    }
205    
206    if(shm_header_p->shm_magic != SHM_MAGIC)
207    {
208       DEBUG(0,("ERROR shm_validate_header : bad magic\n"));
209       return False;
210    }
211    if(shm_header_p->shm_version != SHM_VERSION)
212    {
213       DEBUG(0,("ERROR shm_validate_header : bad version %X\n",shm_header_p->shm_version));
214       return False;
215    }
216    
217    if(shm_header_p->total_size != size)
218    {
219       DEBUG(0,("ERROR shm_validate_header : shmem size mismatch (old = %d, new = %d)\n",shm_header_p->total_size,size));
220       return False;
221    }
222
223    if(!shm_header_p->consistent)
224    {
225       DEBUG(0,("ERROR shm_validate_header : shmem not consistent\n"));
226       return False;
227    }
228    return True;
229 }
230
231 static BOOL shm_initialize(int size)
232 {
233    struct ShmBlockDesc * first_free_block_p;
234    
235    DEBUG(2,("shm_initialize : initializing shmem file of size %d\n",size));
236    
237    if( !shm_header_p )
238    {
239       /* not mapped yet */
240       DEBUG(0,("ERROR shm_initialize : shmem not mapped\n"));
241       return False;
242    }
243    
244    shm_header_p->shm_magic = SHM_MAGIC;
245    shm_header_p->shm_version = SHM_VERSION;
246    shm_header_p->total_size = size;
247    shm_header_p->first_free_off = AlignedHeaderSize;
248    shm_header_p->userdef_off = NULL_OFFSET;
249    
250    first_free_block_p = (struct ShmBlockDesc *)shm_offset2addr(shm_header_p->first_free_off);
251    first_free_block_p->next = EOList_Off;
252    first_free_block_p->size = ( size - AlignedHeaderSize - CellSize ) / CellSize ;
253    
254    shm_header_p->statistics.cells_free = first_free_block_p->size;
255    shm_header_p->statistics.cells_used = 0;
256    shm_header_p->statistics.cells_system = 1;
257    
258    shm_header_p->consistent = True;
259    
260    return True;
261 }
262    
263 static void shm_solve_neighbors(struct ShmBlockDesc *head_p )
264 {
265    struct ShmBlockDesc *next_p;
266    
267    /* Check if head_p and head_p->next are neighbors and if so join them */
268    if ( head_p == EOList_Addr ) return ;
269    if ( head_p->next == EOList_Off ) return ;
270    
271    next_p = (struct ShmBlockDesc *)shm_offset2addr(head_p->next);
272    if ( ( head_p + head_p->size + 1 ) == next_p)
273    {
274       head_p->size += next_p->size +1 ; /* adapt size */
275       head_p->next = next_p->next         ; /* link out */
276       
277       shm_header_p->statistics.cells_free += 1;
278       shm_header_p->statistics.cells_system -= 1;
279    }
280 }
281
282
283
284 BOOL shm_open( char *file_name, int size)
285 {
286    int filesize;
287    BOOL created_new = False;
288    BOOL other_processes = True;
289    int old_umask;
290    
291    DEBUG(2,("shm_open : using shmem file %s to be of size %d\n",file_name,size));
292
293    old_umask = umask(0);
294    shm_fd = open(file_name, O_RDWR | O_CREAT, 0666);
295    umask(old_umask);
296    if ( shm_fd < 0 )
297    {
298       DEBUG(0,("ERROR shm_open : open failed with code %d\n",errno));
299       return False;
300    }
301    
302    if (!shm_lock())
303    {
304       DEBUG(0,("ERROR shm_open : can't do shm_lock\n"));
305       return False;
306    }
307    
308    if( (filesize = lseek(shm_fd, 0, SEEK_END)) < 0)
309    {
310       DEBUG(0,("ERROR shm_open : lseek failed with code %d\n",errno));
311       shm_unlock();
312       close(shm_fd);
313       return False;
314    }
315
316    /* return the file offset to 0 to save on later seeks */
317    lseek(shm_fd,0,SEEK_SET);
318
319    if (filesize == 0)
320    {
321       /* we just created a new one */
322       created_new = True;
323    }
324    
325    /* to find out if some other process is already mapping the file,
326       we use a registration file containing the processids of the file mapping processes
327       */
328
329    /* construct processreg file name */
330    strcpy(shm_processreg_name, file_name);
331    strcat(shm_processreg_name, ".processes");
332
333    if (! shm_register_process(shm_processreg_name, getpid(), &other_processes))
334    {
335       shm_unlock();
336       close(shm_fd);
337       return False;
338    }
339
340    if (created_new || !other_processes)
341    {
342       /* we just created a new one, or are the first opener, lets set it size */
343       if( ftruncate(shm_fd, size) <0)
344       {
345          DEBUG(0,("ERROR shm_open : ftruncate failed with code %d\n",errno));
346          shm_unregister_process(shm_processreg_name, getpid());
347          shm_unlock();
348          close(shm_fd);
349          return False;
350       }
351
352       /* paranoia */
353       lseek(shm_fd,0,SEEK_SET);
354
355       filesize = size;
356    }
357    
358    if (size != filesize )
359    {
360       /* the existing file has a different size and we are not the first opener.
361          Since another process is still using it, we will use the file size */
362       DEBUG(0,("WARNING shm_open : filesize (%d) != expected size (%d), using filesize\n",filesize,size));
363       size = filesize;
364    }
365    
366    shm_header_p = (struct ShmHeader *)mmap( NULL, size, PROT_READ | PROT_WRITE, MAP_FILE | MAP_SHARED, shm_fd, 0);
367    /* WARNING, shm_header_p can be different for different processes mapping the same file ! */
368    if (shm_header_p  == (struct ShmHeader *)(-1))
369    {
370       DEBUG(0,("ERROR shm_open : mmap failed with code %d\n",errno));
371       shm_unregister_process(shm_processreg_name, getpid());
372       shm_unlock();
373       close(shm_fd);
374       return False;
375    }      
376    
377       
378    if (created_new || !other_processes)
379    {
380       shm_initialize(size);
381    }
382    else if (!shm_validate_header(size) )
383    {
384       /* existing file is corrupt, samba admin should remove it by hand */
385       DEBUG(0,("ERROR shm_open : corrupt shared mem file, remove it manually\n"));
386       munmap((caddr_t)shm_header_p, size);
387       shm_unregister_process(shm_processreg_name, getpid());
388       shm_unlock();
389       close(shm_fd);
390       return False;
391    }
392    
393    shm_unlock();
394    return True;
395       
396 }
397
398
399 BOOL shm_close( void )
400 {
401    
402    DEBUG(2,("shm_close\n"));
403    if(shm_times_locked > 0)
404       DEBUG(0,("WARNING shm_close : shmem was still locked %d times\n",shm_times_locked));;
405    if ( munmap((caddr_t)shm_header_p, shm_header_p->total_size) < 0)
406    {
407       DEBUG(0,("ERROR shm_close : munmap failed with code %d\n",errno));
408    }
409
410    shm_lock();
411    shm_unregister_process(shm_processreg_name, getpid());
412    shm_unlock();
413    
414    close(shm_fd);
415    
416    shm_fd = -1;
417    shm_processreg_name[0] = '\0';
418
419    shm_header_p = (struct ShmHeader *)0;
420    shm_times_locked = 0;
421    
422    return True;
423 }
424
425 shm_offset_t shm_alloc(int size)
426 {
427    unsigned num_cells ;
428    struct ShmBlockDesc *scanner_p;
429    struct ShmBlockDesc *prev_p;
430    struct ShmBlockDesc *new_p;
431    shm_offset_t result_offset;
432    
433    
434    if( !shm_header_p )
435    {
436       /* not mapped yet */
437       DEBUG(0,("ERROR shm_alloc : shmem not mapped\n"));
438       return NULL_OFFSET;
439    }
440    
441    if( !shm_header_p->consistent)
442    {
443       DEBUG(0,("ERROR shm_alloc : shmem not consistent\n"));
444       return NULL_OFFSET;
445    }
446    
447    
448    /* calculate the number of cells */
449    num_cells = (size + CellSize -1) / CellSize;
450
451    /* set start of scan */
452    prev_p = (struct ShmBlockDesc *)shm_offset2addr(shm_header_p->first_free_off);
453    scanner_p =  prev_p ;
454    
455    /* scan the free list to find a matching free space */
456    while ( ( scanner_p != EOList_Addr ) && ( scanner_p->size < num_cells ) )
457    {
458       prev_p = scanner_p;
459       scanner_p = (struct ShmBlockDesc *)shm_offset2addr(scanner_p->next);
460    }
461    
462    /* at this point scanner point to a block header or to the end of the list */
463    if ( scanner_p == EOList_Addr )      
464    {
465       DEBUG(0,("ERROR shm_alloc : alloc of %d bytes failed, no free space found\n",size));
466       return (NULL_OFFSET);
467    }
468    
469    /* going to modify shared mem */
470    shm_header_p->consistent = False;
471    
472    /* if we found a good one : scanner == the good one */
473    if ( scanner_p->size <= num_cells + 2 )
474    {
475       /* there is no use in making a new one, it will be too small anyway 
476       *  we will link out scanner
477       */
478       if ( prev_p == scanner_p )
479       {
480          shm_header_p->first_free_off = scanner_p->next ;
481       }
482       else
483       {
484          prev_p->next = scanner_p->next ;
485       }
486       shm_header_p->statistics.cells_free -= scanner_p->size;
487       shm_header_p->statistics.cells_used += scanner_p->size;
488    }
489    else
490    {
491       /* Make a new one */
492       new_p = scanner_p + 1 + num_cells;
493       new_p->size = scanner_p->size - num_cells - 1;
494       new_p->next = scanner_p->next;
495       scanner_p->size = num_cells;
496       scanner_p->next = shm_addr2offset(new_p);
497       
498       if ( prev_p       != scanner_p )
499       {
500          prev_p->next      = shm_addr2offset(new_p)  ;
501       }
502       else
503       {
504          shm_header_p->first_free_off = shm_addr2offset(new_p)  ;
505       }
506       shm_header_p->statistics.cells_free -= num_cells+1;
507       shm_header_p->statistics.cells_used += num_cells;
508       shm_header_p->statistics.cells_system += 1;
509    }
510
511    result_offset = shm_addr2offset( &(scanner_p[1]) );
512    scanner_p->next =    SHM_NOT_FREE_OFF ;
513
514    /* end modification of shared mem */
515    shm_header_p->consistent = True;
516
517    DEBUG(2,("shm_alloc : request for %d bytes, allocated %d bytes at offset %d\n",size,scanner_p->size*CellSize,result_offset ));
518
519    return ( result_offset );
520 }   
521
522
523
524 BOOL shm_free(shm_offset_t offset)
525 {
526    struct ShmBlockDesc *header_p  ; /*  pointer to header of block to free */
527    struct ShmBlockDesc *scanner_p ; /*  used to scan the list                      */
528    struct ShmBlockDesc *prev_p     ; /* holds previous in the list                 */
529    
530    if( !shm_header_p )
531    {
532       /* not mapped yet */
533       DEBUG(0,("ERROR shm_free : shmem not mapped\n"));
534       return False;
535    }
536    
537    if( !shm_header_p->consistent)
538    {
539       DEBUG(0,("ERROR shm_free : shmem not consistent\n"));
540       return False;
541    }
542    
543    header_p = ( (struct ShmBlockDesc *)shm_offset2addr(offset) - 1); /* make pointer to header of block */
544
545    if (header_p->next != SHM_NOT_FREE_OFF)
546    {
547       DEBUG(0,("ERROR shm_free : bad offset (%d)\n",offset));
548       return False;
549    }
550    
551    /* find a place in the free_list to put the header in */
552    
553    /* set scanner and previous pointer to start of list */
554    prev_p = (struct ShmBlockDesc *)shm_offset2addr(shm_header_p->first_free_off);
555    scanner_p = prev_p ;
556    
557    while ( ( scanner_p != EOList_Addr) && (scanner_p < header_p) ) /* while we didn't scan past its position */
558    {
559       prev_p = scanner_p ;
560       scanner_p = (struct ShmBlockDesc *)shm_offset2addr(scanner_p->next);
561    }
562    
563    shm_header_p->consistent = False;
564    
565    DEBUG(2,("shm_free : freeing %d bytes at offset %d\n",header_p->size*CellSize,offset));
566
567    if ( scanner_p == prev_p )
568    {
569       shm_header_p->statistics.cells_free += header_p->size;
570       shm_header_p->statistics.cells_used -= header_p->size;
571
572       /* we must free it at the beginning of the list */
573       shm_header_p->first_free_off = shm_addr2offset(header_p);                                          /*     set     the free_list_pointer to this block_header */
574
575       /* scanner is the one that was first in the list */
576       header_p->next = shm_addr2offset(scanner_p);
577       shm_solve_neighbors( header_p ); /* if neighbors then link them */
578       
579       shm_header_p->consistent = True;
580       return True;
581    } 
582    else
583    {
584       shm_header_p->statistics.cells_free += header_p->size;
585       shm_header_p->statistics.cells_used -= header_p->size;
586
587       prev_p->next = shm_addr2offset(header_p);
588       header_p->next = shm_addr2offset(scanner_p);
589       shm_solve_neighbors(header_p) ;
590       shm_solve_neighbors(prev_p) ;
591
592       shm_header_p->consistent = True;
593       return True;
594    }
595 }
596
597 shm_offset_t shm_get_userdef_off(void)
598 {
599    if (!shm_header_p)
600       return NULL_OFFSET;
601    else
602       return shm_header_p->userdef_off;
603 }
604
605 BOOL shm_set_userdef_off(shm_offset_t userdef_off)
606 {
607    if (!shm_header_p)
608       return False;
609    else
610       shm_header_p->userdef_off = userdef_off;
611    return True;
612 }
613
614 void * shm_offset2addr(shm_offset_t offset)
615 {
616    if (offset == NULL_OFFSET )
617       return (void *)(0);
618    
619    if (!shm_header_p)
620       return (void *)(0);
621    
622    return (void *)((char *)shm_header_p + offset );
623 }
624
625 shm_offset_t shm_addr2offset(void *addr)
626 {
627    if (!addr)
628       return NULL_OFFSET;
629    
630    if (!shm_header_p)
631       return NULL_OFFSET;
632    
633    return (shm_offset_t)((char *)addr - (char *)shm_header_p);
634 }
635
636 BOOL shm_lock(void)
637 {
638    if (shm_fd < 0)
639    {
640       DEBUG(0,("ERROR shm_lock : bad shm_fd (%d)\n",shm_fd));
641       return False;
642    }
643    
644    shm_times_locked++;
645    
646    if(shm_times_locked > 1)
647    {
648       DEBUG(2,("shm_lock : locked %d times\n",shm_times_locked));
649       return True;
650    }
651    
652    if (lockf(shm_fd, F_LOCK, 0) < 0)
653    {
654       DEBUG(0,("ERROR shm_lock : lockf failed with code %d\n",errno));
655       shm_times_locked--;
656       return False;
657    }
658    
659    return True;
660    
661 }
662
663
664
665 BOOL shm_unlock(void)
666 {
667    if (shm_fd < 0)
668    {
669       DEBUG(0,("ERROR shm_unlock : bad shm_fd (%d)\n",shm_fd));
670       return False;
671    }
672    
673    if(shm_times_locked == 0)
674    {
675       DEBUG(0,("ERROR shm_unlock : shmem not locked\n",shm_fd));
676       return False;
677    }
678    
679    shm_times_locked--;
680    
681    if(shm_times_locked > 0)
682    {
683       DEBUG(2,("shm_unlock : still locked %d times\n",shm_times_locked));
684       return True;
685    }
686    
687    if (lockf(shm_fd, F_ULOCK, 0) < 0)
688    {
689       DEBUG(0,("ERROR shm_unlock : lockf failed with code %d\n",errno));
690       shm_times_locked++;
691       return False;
692    }
693    
694    return True;
695    
696 }
697
698
699 BOOL shm_get_usage(int *bytes_free,
700                    int *bytes_used,
701                    int *bytes_overhead)
702 {
703    if( !shm_header_p )
704    {
705       /* not mapped yet */
706       DEBUG(0,("ERROR shm_free : shmem not mapped\n"));
707       return False;
708    }
709    *bytes_free = shm_header_p->statistics.cells_free * CellSize;
710    *bytes_used = shm_header_p->statistics.cells_used * CellSize;
711    *bytes_overhead = shm_header_p->statistics.cells_system * CellSize + AlignedHeaderSize;
712    
713    return True;
714 }
715
716 #else /* FAST_SHARE_MODES */
717  int shmem_dummy_procedure(void)
718 {return 0;}
719 #endif