Skip to content

Instantly share code, notes, and snippets.

@epu
Last active December 28, 2015 20:48
Show Gist options
  • Save epu/7559669 to your computer and use it in GitHub Desktop.
Save epu/7559669 to your computer and use it in GitHub Desktop.
facepalm. on osx cpython 2.7.6, cannot get a zipfile info entry's extended attr to save as 0120755 for a real file, reverts to 0100755.
#ZIPFILE_CREATE_SYSTEM_FLAG can be 3 (posix/unix) or 19 (modern pk zip archives)
import zipfile
import os
import stat
import time
""" The following was use ok to write symlink directory entries into a .zipfile just fine.
But things got challenging when retrieving symlinks to files.
When iterating over file entries:
>>for info in zip_file.filelist:
file_ = zip_file.extract(info, target_dir)
perms = int(info.external_attr >> 16L) # oct(perms) of a symlink with 0755 == 0120775
# oct(perms) becomes 0100775
"""
def __write_symlink(self, zip_file, fullpath, member_name, destpath):
""" Write a symlink to a real file or dir into a .zip archive.
http://unix.stackexchange.com/questions/14705/the-zip-formats-external-file-attribute
explains that we probably want to literally use the stat for 'is a symlink'.
#define S_IFLNK 0120000 /* symbolic link */
See also the horrors of:
http://stackoverflow.com/questions/434641/how-do-i-set-permissions-attributes-on-a-file-in-a-zip-file-using-pythons-zip
Parts ripped from the zipfile implementation of write()
Also, this is a horrible hack workaround.
According to the pkzip spec, the extra field on unix types includes either block device info,
or symlink file info. Not sure if it works in practice, or how to write it and tsize.
4.5.7 -UNIX Extra Field (0x000d):
The following is the layout of the UNIX "extra" block.
Note: all fields are stored in Intel low-byte/high-byte
order.
Value Size Description
----- ---- -----------
(UNIX) 0x000d 2 bytes Tag for this "extra" block type
TSize 2 bytes Size for the following data block
Atime 4 bytes File last access time
Mtime 4 bytes File last modification time
Uid 2 bytes File user ID
Gid 2 bytes File group ID
(var) variable Variable length data field
The variable length data field will contain file type
specific data. Currently the only values allowed are
the original "linked to" file names for hard or symbolic
links, and the major and minor device node numbers for
character and block device nodes. Since device nodes
cannot be either symbolic or hard links, only one set of
variable length data is stored. Link files will have the
name of the original file stored. This name is NOT NULL
terminated. Its size can be determined by checking TSize -
12. Device entries will have eight bytes stored as two 4
byte entries (in little endian format). The first entry
will be the major device number, and the second the minor
device number.
"""
# If you don't explicitly use lstat,
# files that are symbolic links will fail stat.S_ISLNK(mode),
# and mask will come back as static file (example 0100755).
# But having it correct doesn't mean you can write it and get it back.
st = os.lstat(fullpath)
mtime = time.localtime(st.st_mtime)
date_time = mtime[0:6]
info = zipfile.ZipInfo(fullpath, date_time)
# Slightly better documented with CONSTANT.
info.filename = member_name # This might be os.path.basename in the write() impl -epu
info.create_system = ZIPFILE_CREATE_SYSTEM_FLAG
info.compress_type = zipfile.ZIP_DEFLATED # The example of saving the link from SO uses ZIP_STORED.
# But, everytime I create a file that was a symlink, it comes out as regular file mask.
# Instead of blindly using only st_mode or setting to constant of 0600,
# use the file permissions (S_IMODE of stat result) and the link stat entry.
perms = stat.S_IMODE(st.st_mode) | stat.S_IFLNK # oct(perms) when mode is 0775: 0120775
# This might be wrong, since git reports perms of 0120000 internally, no?
info.external_attr = perms << 16L
#zip_file.writestr(info, member_name)
zip_file.writestr(info, destpath)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment