@@ -654,53 +654,22 @@ def Create(self, name, admin, crypted_password,
654654 #
655655 # Database and filesystem I/O
656656 #
657- def __save (self , data_dict ):
658- # Save the file as a binary pickle, and rotate the old version to a
659- # backup file. We must guarantee that config.pck is always valid so
660- # we never rotate unless the we've successfully written the temp file.
661- # We use pickle now because marshal is not guaranteed to be compatible
662- # between Python versions.
657+ def __save (self , dbfile , dict ):
658+ # Save the dictionary to the specified database file. We always save
659+ # using pickle, even if the file was originally a marshal file. This
660+ # is because pickle is guaranteed to be compatible across Python
661+ # versions, while marshal is not.
663662 #
664- # We use protocol 4 for Python 2/3 compatibility because:
665- # 1. It supports large objects (>4GB)
666- # 2. It's compatible between Python 2.7 and Python 3.x
667- # 3. It handles Unicode strings properly
668- # 4. It's the highest protocol version supported by both Python 2.7 and 3.x
669- fname = os .path .join (self .fullpath (), 'config.pck' )
670- fname_tmp = fname + '.tmp.%s.%d' % (socket .gethostname (), os .getpid ())
671- fname_last = fname + '.last'
672- fp = None
663+ # On success return None. On error, return the error object.
673664 try :
674- fp = open (fname_tmp , 'wb' )
675- # Use protocol 4 for Python 2/3 compatibility, with fix_imports for backward compatibility
676- pickle .dump (data_dict , fp , protocol = 4 , fix_imports = True )
677- fp .flush ()
678- if mm_cfg .SYNC_AFTER_WRITE :
679- os .fsync (fp .fileno ())
680- fp .close ()
681- except IOError as e :
682- syslog ('error' ,
683- 'Failed config.pck write, retaining old state.\n %s' , e )
684- if fp is not None :
685- os .unlink (fname_tmp )
686- raise
687- # Now do config.pck.tmp.xxx -> config.pck -> config.pck.last rotation
688- # as safely as possible.
689- try :
690- # Remove existing backup file if it exists
691- try :
692- os .unlink (fname_last )
693- except OSError as e :
694- if e .errno != errno .ENOENT :
695- raise
696- # Create new backup file
697- os .link (fname , fname_last )
698- except OSError as e :
699- if e .errno != errno .ENOENT :
700- raise
701- os .rename (fname_tmp , fname )
702- # Reset the timestamp
703- self .__timestamp = os .path .getmtime (fname )
665+ # Save using the utility function with protocol 4
666+ save_pickle_file (dbfile , dict )
667+ # Update the timestamp
668+ self .__timestamp = os .path .getmtime (dbfile )
669+ return None
670+ except Exception as e :
671+ syslog ('error' , 'Failed to save database file %s: %s' , dbfile , str (e ))
672+ return e
704673
705674 def Save (self ):
706675 """Save the mailing list's configuration to disk.
@@ -731,7 +700,7 @@ def Save(self):
731700 # list members' passwords (in clear text).
732701 omask = os .umask (0o007 )
733702 try :
734- self .__save (dict )
703+ self .__save (os . path . join ( self . fullpath (), 'config.pck' ), dict )
735704 finally :
736705 os .umask (omask )
737706 self .SaveRequestsDb ()
@@ -753,55 +722,16 @@ def __load(self, dbfile):
753722 elif dbfile .endswith ('.pck' ) or dbfile .endswith ('.pck.last' ):
754723 def loadfunc (fp ):
755724 try :
756- # Read the first byte to determine protocol version
757- protocol = ord (fp .read (1 ))
758- print (C_ ('List %(listname)s %(dbfile)s uses pickle protocol %(protocol)d' ) % {
759- 'listname' : self .internal_name (),
760- 'dbfile' : os .path .basename (dbfile ),
761- 'protocol' : protocol
762- })
763- # Reset file pointer to beginning
764- fp .seek (0 )
765-
766- # For protocol 2 files (Python 2.x), try loading with different encodings
767- if protocol == 2 :
768- try :
769- # First try with latin1 (most common for Python 2.x)
770- return pickle .load (fp , fix_imports = True , encoding = 'latin1' )
771- except (UnicodeDecodeError , pickle .UnpicklingError ) as e :
772- syslog ('error' , 'Failed to load with latin1: %s' , str (e ))
773- fp .seek (0 )
774- try :
775- # Then try with UTF-8
776- return pickle .load (fp , fix_imports = True , encoding = 'utf-8' )
777- except (UnicodeDecodeError , pickle .UnpicklingError ) as e :
778- syslog ('error' , 'Failed to load with UTF-8: %s' , str (e ))
779- fp .seek (0 )
780- # Finally try without encoding
781- return pickle .load (fp , fix_imports = True )
782- # For protocol 4 files (Python 3.x), try loading with different encodings
783- elif protocol == 4 :
784- try :
785- # First try with UTF-8
786- return pickle .load (fp , fix_imports = True , encoding = 'utf-8' )
787- except (UnicodeDecodeError , pickle .UnpicklingError ) as e :
788- syslog ('error' , 'Failed to load with UTF-8: %s' , str (e ))
789- fp .seek (0 )
790- try :
791- # Then try with latin1
792- return pickle .load (fp , fix_imports = True , encoding = 'latin1' )
793- except (UnicodeDecodeError , pickle .UnpicklingError ) as e :
794- syslog ('error' , 'Failed to load with latin1: %s' , str (e ))
795- fp .seek (0 )
796- # Finally try without encoding
797- return pickle .load (fp , fix_imports = True )
798- else :
799- # For other protocols, try without encoding first
800- try :
801- return pickle .load (fp , fix_imports = True )
802- except (UnicodeDecodeError , pickle .UnpicklingError ):
803- fp .seek (0 )
804- return pickle .load (fp , fix_imports = True , encoding = 'latin1' )
725+ # Get the protocol version
726+ protocol = get_pickle_protocol (fp .name )
727+ if protocol is not None :
728+ print (C_ ('List %(listname)s %(dbfile)s uses pickle protocol %(protocol)d' ) % {
729+ 'listname' : self .internal_name (),
730+ 'dbfile' : os .path .basename (dbfile ),
731+ 'protocol' : protocol
732+ })
733+ # Use the utility function to load the pickle
734+ return load_pickle_file (fp .name )
805735 except Exception as e :
806736 syslog ('error' , 'Failed to load pickle file %s: %s' , dbfile , str (e ))
807737 raise
0 commit comments