diff --git a/db/sqlite/migrations/metadata/1637447083.sql b/db/sqlite/migrations/metadata/1637447083.sql index 71fe30a..a69b603 100644 --- a/db/sqlite/migrations/metadata/1637447083.sql +++ b/db/sqlite/migrations/metadata/1637447083.sql @@ -15,9 +15,9 @@ create table headers ( -- depending on the presence of a trailing slash in Name. typeflag integer not null, -- Name of file entry - name text not null primary key, + name text not null, -- Target name of link (valid for TypeLink or TypeSymlink) - -- linkname text not null, -- FIXME: Handle linkname + linkname text not null, -- Logical file size in bytes size integer not null, -- Permission and mode bits @@ -65,7 +65,9 @@ create table headers ( -- If the format is unspecified when Writer.WriteHeader is called, -- then it uses the first format (in the order of USTAR, PAX, GNU) -- capable of encoding this Header (see Format). - format integer not null + format integer not null, + + primary key (name, linkname) ); -- +migrate Down drop table headers; \ No newline at end of file diff --git a/internal/converters/header.go b/internal/converters/header.go index 7714d7c..3f72aef 100644 --- a/internal/converters/header.go +++ b/internal/converters/header.go @@ -16,21 +16,21 @@ func ConfigHeaderToDBHeader(confighdr *config.Header) *models.Header { Lastknownblock: confighdr.Lastknownblock, Typeflag: confighdr.Typeflag, Name: confighdr.Name, - // Linkname: confighdr.Linkname, // FIXME: Handle linkname - Size: confighdr.Size, - Mode: confighdr.Mode, - UID: confighdr.UID, - Gid: confighdr.Gid, - Uname: confighdr.Uname, - Gname: confighdr.Gname, - Modtime: confighdr.Modtime, - Accesstime: confighdr.Accesstime, - Changetime: confighdr.Changetime, - Devmajor: confighdr.Devmajor, - Devminor: confighdr.Devminor, - Paxrecords: confighdr.Paxrecords, - Format: confighdr.Format, - Deleted: confighdr.Deleted, + Linkname: confighdr.Linkname, + Size: confighdr.Size, + Mode: confighdr.Mode, + UID: confighdr.UID, + Gid: confighdr.Gid, + Uname: confighdr.Uname, + Gname: confighdr.Gname, + Modtime: confighdr.Modtime, + Accesstime: confighdr.Accesstime, + Changetime: confighdr.Changetime, + Devmajor: confighdr.Devmajor, + Devminor: confighdr.Devminor, + Paxrecords: confighdr.Paxrecords, + Format: confighdr.Format, + Deleted: confighdr.Deleted, } } @@ -42,21 +42,21 @@ func DBHeaderToConfigHeader(dbhdr *models.Header) *config.Header { Lastknownblock: dbhdr.Lastknownblock, Typeflag: dbhdr.Typeflag, Name: dbhdr.Name, - // Linkname: dbhdr.Linkname, // FIXME: Handle linkname - Size: dbhdr.Size, - Mode: dbhdr.Mode, - UID: dbhdr.UID, - Gid: dbhdr.Gid, - Uname: dbhdr.Uname, - Gname: dbhdr.Gname, - Modtime: dbhdr.Modtime, - Accesstime: dbhdr.Accesstime, - Changetime: dbhdr.Changetime, - Devmajor: dbhdr.Devmajor, - Devminor: dbhdr.Devminor, - Paxrecords: dbhdr.Paxrecords, - Format: dbhdr.Format, - Deleted: dbhdr.Deleted, + Linkname: dbhdr.Linkname, + Size: dbhdr.Size, + Mode: dbhdr.Mode, + UID: dbhdr.UID, + Gid: dbhdr.Gid, + Uname: dbhdr.Uname, + Gname: dbhdr.Gname, + Modtime: dbhdr.Modtime, + Accesstime: dbhdr.Accesstime, + Changetime: dbhdr.Changetime, + Devmajor: dbhdr.Devmajor, + Devminor: dbhdr.Devminor, + Paxrecords: dbhdr.Paxrecords, + Format: dbhdr.Format, + Deleted: dbhdr.Deleted, } } @@ -70,9 +70,9 @@ func DBHeaderToTarHeader(dbhdr *models.Header) (*tar.Header, error) { } hdr := &tar.Header{ - Typeflag: byte(dbhdr.Typeflag), - Name: dbhdr.Name, - // Linkname: dbhdr.Linkname, // FIXME: Handle linkname + Typeflag: byte(dbhdr.Typeflag), + Name: dbhdr.Name, + Linkname: dbhdr.Linkname, Size: dbhdr.Size, Mode: dbhdr.Mode, Uid: int(dbhdr.UID), @@ -104,20 +104,20 @@ func TarHeaderToDBHeader(record, lastKnownRecord, block, lastKnownBlock int64, t Lastknownblock: lastKnownBlock, Typeflag: int64(tarhdr.Typeflag), Name: tarhdr.Name, - // Linkname: tarhdr.Linkname, // FIXME: Handle linkname - Size: tarhdr.Size, - Mode: tarhdr.Mode, - UID: int64(tarhdr.Uid), - Gid: int64(tarhdr.Gid), - Uname: tarhdr.Uname, - Gname: tarhdr.Gname, - Modtime: tarhdr.ModTime, - Accesstime: tarhdr.AccessTime, - Changetime: tarhdr.ChangeTime, - Devmajor: tarhdr.Devmajor, - Devminor: tarhdr.Devminor, - Paxrecords: string(paxRecords), - Format: int64(tarhdr.Format), + Linkname: tarhdr.Linkname, + Size: tarhdr.Size, + Mode: tarhdr.Mode, + UID: int64(tarhdr.Uid), + Gid: int64(tarhdr.Gid), + Uname: tarhdr.Uname, + Gname: tarhdr.Gname, + Modtime: tarhdr.ModTime, + Accesstime: tarhdr.AccessTime, + Changetime: tarhdr.ChangeTime, + Devmajor: tarhdr.Devmajor, + Devminor: tarhdr.Devminor, + Paxrecords: string(paxRecords), + Format: int64(tarhdr.Format), } return &hdr, nil diff --git a/internal/db/sqlite/migrations/metadata/migrations.go b/internal/db/sqlite/migrations/metadata/migrations.go index b746ebe..1ff146e 100644 --- a/internal/db/sqlite/migrations/metadata/migrations.go +++ b/internal/db/sqlite/migrations/metadata/migrations.go @@ -25,7 +25,7 @@ func bindata_read(data []byte, name string) ([]byte, error) { return buf.Bytes(), nil } -var _db_sqlite_migrations_metadata_1637447083_sql = []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\xa4\x56\x4d\x73\xdb\x36\x13\x3e\xbf\xfe\x15\x3b\xb9\xc4\x9e\x57\xd4\xb9\x93\x4c\x0f\x6e\x9c\x38\x9e\x89\x9d\x8c\x62\x35\x39\x06\x24\x96\x24\x2a\x10\x8b\x2e\x40\xc9\xcc\xaf\xef\x2c\x40\xca\xb2\x64\xc9\xe9\xf4\x24\x0a\xd8\xe7\xd9\xef\x5d\x14\x05\xfc\xbf\x33\x0d\xab\x88\xb0\xf4\x67\x15\xa3\x7c\x45\x55\x5a\x84\x16\x95\x46\x0e\x70\x7e\x06\x00\x50\x14\xb0\xc0\x8a\x58\x03\xd5\x10\x5b\x13\xc6\x7b\x20\x07\xb1\x15\x8c\xc7\x24\xc8\x59\xca\xb8\x88\x0d\x32\x38\x8a\xe0\x7a\x6b\x67\xcf\xb1\x20\x58\x15\x22\xf4\x5e\x8b\xda\x89\xf0\x34\xbf\x20\x56\x8e\x36\xee\x25\x45\x7f\x58\xaa\x56\xfb\x6c\x26\xb3\x65\x6c\x92\x2c\x93\xd8\xaf\xb0\xfc\x8a\xb5\x87\xfc\x5b\x7b\x5f\x50\x74\x53\x43\xc0\x38\x4b\xf0\x91\xac\x55\x01\x4a\x44\x07\x1a\x2d\x46\xd4\x29\x16\xca\xe3\x0c\xca\x3e\xc2\x8f\xbd\x48\xfc\x00\xe5\xf4\xce\x69\xd2\xf7\x03\x14\x23\x84\x68\xac\x15\x53\x19\x2d\xae\x95\xab\x72\x28\x27\xda\xa3\x36\xdd\x0f\x1e\x6b\xab\x1a\x30\x21\x27\x61\xf0\x28\x34\xa3\x7d\xe8\x22\x0f\xf3\xad\x70\x8b\xf0\x13\x99\x60\xad\x6c\x8f\x02\x51\x7d\xa4\x4e\x45\x53\x29\x6b\x07\xf0\x4c\x1d\x89\xba\x48\x80\x26\xb6\xc8\x89\x7f\x81\x0d\x50\xfe\xbc\x32\x3c\x91\x69\xf4\xe8\xb4\x71\xcd\x94\x7f\xcf\x18\xd0\x55\x49\xbd\x82\xc8\xca\x58\xb9\x0d\x56\x85\x56\xa2\x7e\xa7\x3a\xcc\xa6\xc4\xad\xd1\xc7\xdc\x12\x59\xe1\xa9\x8d\xc5\xec\x44\xba\x71\x72\x1c\xf1\x21\x6e\x11\xe0\xd9\x74\x8a\x07\x58\xe1\xf0\x18\x14\xc5\x0d\xc6\x2c\x4d\x35\x58\xe3\x56\x70\xbe\x56\xd6\x68\xa8\x47\x47\x3e\xc9\xd9\xf8\xfd\x75\xe8\x44\xe4\x62\x82\xcb\x9f\x43\x4d\x33\xb9\xfa\x70\xf3\xfd\xf6\xfd\x1b\xf8\xa8\x9c\xb6\xb8\x15\x9c\x80\x9f\xa8\x91\x48\x66\xab\x83\xf9\x89\xe2\x76\x39\x44\x0c\x49\x62\x3c\x39\xe2\xf2\x17\xe4\xce\x84\x60\xc8\xa5\x2a\xe9\x48\x23\x94\x26\x66\x68\xfa\x77\x14\xba\x0c\xc8\x70\x73\x25\xbe\xd2\xc6\x61\x4e\x51\x6f\x4e\x94\xcd\x35\x53\xef\x0f\x20\xcd\x29\x48\x52\x32\x85\x74\x47\xcd\x33\x91\x7a\xaa\xe5\x10\xd3\x9c\xc0\xdc\xe4\x3e\xfe\x40\xdc\xa9\x28\x25\xda\xbb\xe0\xb1\x32\xb5\x41\x9d\x5a\xcf\xc1\x37\x36\x11\x79\x9e\x7e\x3e\xe6\x3a\x67\xea\x9d\x0e\x70\x4b\xfa\xde\x3c\xe6\x23\x52\xe2\x72\xa8\x18\x43\x84\x80\x15\x39\x9d\xa2\x6b\x1a\x47\x8c\xb9\x65\x2e\xab\x0a\x43\x10\x5c\xba\x7a\xd7\x2a\xd7\x60\xfa\x5b\x1b\xb4\x3a\x4c\xed\xb3\xad\x2e\x82\x3e\x3c\x81\x11\xef\xa0\x66\x90\xed\x1d\x76\xfd\x50\x01\xbe\x5c\x7e\x17\xc1\xeb\xbb\xe5\x7c\x8f\x29\xf4\x65\x31\xda\xc6\x18\xc8\xf6\xd1\x90\x3b\x41\xb3\xc5\xdf\x92\x36\xb5\xa9\x94\xc8\x43\x9c\x1c\xef\x48\xcb\x37\xa4\x19\x78\x10\xdf\x6c\x76\x92\x86\x73\xc6\xbf\x7b\x23\x71\x18\xbb\xfd\xd1\x46\x08\xbd\xf7\xc4\x31\x37\x85\x4a\xa0\x13\xac\xd9\xfd\x7f\xc9\x5a\x25\xd0\x09\xd6\x5b\xf5\x17\x31\x68\x5c\x9b\x0a\xc1\xf5\x5d\x89\xbc\xdf\xc7\xef\x5a\xc5\x53\x1f\xa7\x45\x70\x31\xce\xcd\x75\x97\xc0\x47\xcb\xf9\xd6\xb8\xff\xc0\x9d\xc0\xc7\x5b\xf9\xf2\x7b\xde\xa1\x21\xcd\x58\xe8\x94\x97\x06\x90\x38\xe0\x43\x44\xa7\x51\x4f\x03\x3a\x2f\x86\x83\x1a\x93\x66\x2b\x34\xd6\xc6\xa1\x9e\x64\x20\xb4\xd4\x5b\x0d\xad\x5a\xa3\x8c\xbb\x30\xed\xbc\x9a\xac\xa5\x8d\xcc\xda\x9a\xb8\x7b\x33\x72\xfc\xef\xcf\xf7\x77\x57\x9f\x17\xf3\x15\x0e\x9b\x69\xd3\x15\x05\x7c\x6b\x91\x11\xf2\x9d\x58\x17\xa8\xc3\xd4\xa2\xc1\xab\x2a\x0d\x2c\x65\x2d\xf4\xde\x23\x57\x2a\xe0\x2c\xf5\xc4\xc8\x01\x9d\x1a\x26\x1e\x71\xba\x22\x17\xd5\xb8\x4f\x5f\xff\xfe\x5a\x12\xca\xaa\x8a\x12\x49\x9c\x37\xf3\x19\xbc\xba\xfe\xfc\xe9\xf2\xee\x7a\xee\x57\xcd\x7c\x8d\x2c\xc3\xed\xd5\xc5\x93\x75\xb4\xc2\x21\x69\xc8\x1b\x69\xf4\xb0\x94\x5a\x70\x05\x76\x3e\x0e\xb0\xbc\xff\x50\xfc\x06\x21\xb2\x71\xcd\x41\x9c\xbe\x1d\x99\x07\x26\x80\xac\x34\x19\x19\x12\xf5\x29\x82\x1a\xd9\xac\x51\x43\xcd\xd4\x89\xd5\x13\x0d\xa5\x3a\xcd\xed\x2e\x21\x18\x59\xa2\x5a\xa5\xb5\x56\xa1\xce\x8b\x6d\x9d\xab\x79\xb1\x9b\x34\xaf\x1e\x26\xfa\xe7\x27\xda\xd8\xbb\xd3\x14\x0b\x63\xce\xd2\xe1\x98\xc1\xa8\x78\x2c\x88\x83\x61\x23\x2f\x17\x49\x13\x46\x28\x07\x58\x64\xa1\x3b\x51\xa4\xa4\xb4\x4a\x0c\xb1\xc0\xba\x26\x8e\xd0\xf4\xd2\xd9\x2a\xee\x28\xd8\xc6\xfa\xab\x11\x07\xe4\x22\x53\x80\x35\x25\x72\xda\xfa\x8c\x4a\x4f\x75\x40\xae\xa8\xa8\xf3\xd6\x28\x17\xd3\x1a\x0b\x5b\x37\x4c\x9a\xc6\x9e\x42\x30\xf2\xf6\x94\x26\x49\xcf\xaa\x48\x92\xaf\xec\xe4\xd2\xa5\x77\xcd\xbe\x13\x37\xf5\xae\xcf\x4f\x67\x3a\x6c\x5e\x4c\xe1\x76\x9c\x8b\xa4\x89\x32\x33\xc7\x20\x1a\x0e\x71\xa2\x3d\x1f\x0b\x91\x78\x7c\xf4\x2d\xbf\xde\x5f\x2e\x52\xfe\x67\x32\x7b\xb6\xeb\xbd\x52\x3e\xbd\x9e\xa9\x06\x74\x15\xa5\x27\x4c\xf2\x64\x54\x7c\x1e\x70\xf2\x67\xac\xd5\xc9\xf0\xbd\x7e\x3f\xbb\x78\x7b\xb6\xfb\x3c\xbf\xa2\x8d\x3b\xd3\x4c\xfe\xe9\xf3\xfc\xed\x3f\x01\x00\x00\xff\xff\xf3\xc7\xc5\x5e\xc3\x0b\x00\x00") +var _db_sqlite_migrations_metadata_1637447083_sql = []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\xa4\x56\x4d\x73\xdb\x36\x13\x3e\xbf\xfe\x15\x3b\xb9\x44\x9a\x57\xd4\xb9\xd3\x4c\x0f\x6e\xdc\xb8\x9e\x89\x9d\x8c\x62\x35\x39\x1a\x24\x96\x24\x2a\x10\x8b\x2e\x40\xc9\xcc\xaf\xef\x2c\x40\xaa\xb2\x65\xc9\xe9\xf4\x24\x0a\xd8\x7d\x76\xf7\xd9\x2f\x14\x05\xfc\xbf\x33\x0d\xab\x88\xb0\xf6\x17\x15\xa3\x7c\x45\x55\x5a\x84\x16\x95\x46\x0e\x30\xbb\x00\x00\x28\x0a\x58\x61\x45\xac\x81\x6a\x88\xad\x09\xe3\x3d\x90\x83\xd8\x8a\x8e\xc7\x24\xc8\x59\xca\xb8\x88\x0d\x32\x38\x8a\xe0\x7a\x6b\x17\x2f\xa1\x20\x58\x15\x22\xf4\x5e\x8b\xd9\x09\xf0\x3c\xbe\x68\x6c\x1c\xed\xdc\x6b\x86\x7e\xb5\x54\x6d\x9e\xa3\x99\x8c\x96\x75\x93\x64\x99\xc4\x7e\x04\xe5\x47\xbc\x3d\xc6\xdf\xfb\xfb\x8a\xa1\x9b\x1a\x02\xc6\x45\x52\x1f\xc1\x5a\x15\xa0\x44\x74\xa0\xd1\x62\x44\x9d\xb8\x50\x1e\x17\x50\xf6\x11\x1e\x9e\x31\xf1\x00\xca\xe9\x83\xd3\x64\xef\x01\x14\x23\x84\x68\xac\x15\x57\x19\x2d\x6e\x95\xab\x32\x95\x13\xec\x49\x9f\xee\x07\x8f\xb5\x55\x0d\x98\x90\x93\x30\x78\x14\x98\xd1\x3f\x74\x91\x87\xe5\x5e\xb8\x45\xf8\x8e\x4c\xb0\x55\xb6\x47\x51\x51\x7d\xa4\x4e\x45\x53\x29\x6b\x07\xf0\x4c\x1d\x89\xb9\x48\x80\x26\xb6\xc8\x09\x7f\x85\x0d\x50\xfe\xbc\x32\x3c\x81\x69\xf4\xe8\xb4\x71\xcd\x94\x7f\xcf\x18\xd0\x55\xc9\xbc\x82\xc8\xca\x58\xb9\x0d\x56\x85\x56\x58\xbf\x53\x1d\x66\x57\xe2\xde\xe9\x53\x61\x89\xac\xe0\xd4\xc6\x62\x0e\x22\xdd\x38\x39\x8e\xf8\x18\x5f\x20\x42\x71\x83\x31\x4b\x50\x0d\xd6\xb8\x0d\xcc\xb6\xca\x1a\x0d\xf5\xe8\xfc\x47\x39\x1b\xbf\xbf\x0c\x9d\x88\xcc\x73\x01\x18\xb7\x39\x03\xfd\x91\x1a\x21\x28\x3b\x13\xcc\x77\x94\x68\xca\x21\x62\x48\x12\xe3\xc9\x89\x48\x3e\x23\x77\x26\x04\x43\x2e\x25\xbf\x23\x8d\x50\x9a\x98\x55\xd3\xbf\x93\xaa\xeb\x80\x0c\x37\x57\x12\x0e\xed\x1c\x66\xe6\x7b\x73\xa6\x1a\xae\x99\x7a\x7f\xa4\xd2\x9c\x53\x49\x46\x26\xd6\x0e\xcc\x9c\xe1\x23\x5b\x39\xd6\x69\xce\xe8\xdc\xe4\xf6\xfc\x40\xdc\xa9\x28\x95\xd7\xbb\xe0\xb1\x32\xb5\x41\x9d\x3a\xca\xc1\x57\x36\x11\x79\x99\x7e\x7e\xcf\xe5\xcb\xd4\x3b\x1d\xe0\x96\xf4\xbd\xe9\x70\xc2\x8a\x94\xb0\x1c\x2a\xc6\x10\x21\x60\x45\x4e\x27\x76\x4d\xe3\x88\x31\x77\xc2\x65\x55\x61\x08\xa2\x97\xae\xde\xb7\xca\x35\x98\xfe\xd6\x06\xad\x0e\x53\x57\xec\x0b\x88\xa0\x0f\x4f\xd4\x88\x0f\xb4\x16\x90\xfd\x1d\x0e\xe3\x50\x01\x3e\x5f\x7e\x13\xc1\xeb\xbb\xf5\xf2\x19\x52\xe8\xcb\x62\xf4\x8d\x31\x90\xed\xa3\x21\x77\x06\x66\xaf\x7f\x4b\xda\xd4\xa6\x52\x22\x0f\x71\x0a\xbc\x23\x2d\xdf\x90\x46\xdb\x11\xbf\xd9\xed\x24\x0d\x33\xc6\xbf\x7a\x23\x3c\x8c\x4d\xfc\x8f\x8f\x10\x7a\xef\x89\x63\xae\x7b\x95\x94\xce\xa0\xe6\xf0\xff\x25\x6a\x95\x94\xce\xa0\xde\xaa\x3f\x89\x41\xe3\xd6\x54\x08\xae\xef\x4a\xe4\xe7\xad\xfa\xbe\x55\x3c\xb5\x6a\x9a\xef\xf3\x71\x1c\x6e\xbb\xa4\x7c\xb2\x9c\x6f\x8d\xfb\x0f\xd8\x49\xf9\x74\x2b\x5f\x7e\xcb\xab\x31\xa4\xd1\x09\x9d\xf2\xd2\x00\xc2\x03\x3e\x46\x74\x1a\xf5\x34\x77\xf3\xbc\x3f\xaa\x31\x69\xb6\x42\x63\x6d\x1c\xea\x49\x06\x42\x4b\xbd\xd5\xd0\xaa\x2d\xc2\x06\x87\x30\xad\xb2\x9a\xac\xa5\x9d\x8c\xd0\x9a\xb8\xfb\x79\xc4\xf8\xdf\x1f\xbf\xdd\x5d\x7d\x5a\x2d\x37\x38\xec\xa6\x05\x56\x14\xf0\xb5\x45\x46\xc8\x77\xe2\x5d\xa0\x0e\x53\x8b\x06\xaf\xaa\x34\xb0\x94\xb5\xd0\x7b\x8f\x5c\xa9\x80\x8b\xd4\x13\x23\x06\x74\x6a\x98\x70\x24\xe8\x8a\x5c\x54\xe3\x9a\x7c\xfb\xcb\x5b\x49\x28\xab\x2a\x0a\x93\xb8\x6c\x96\x0b\x78\x73\xfd\xe9\xe3\xe5\xdd\xf5\xd2\x6f\x9a\xe5\x16\x59\x86\xdb\x9b\xf9\x93\x2d\xb3\xc1\x21\x59\xc8\x8b\x66\x8c\xb0\x94\x5a\x70\x05\x76\x3e\x0e\xb0\xbe\xff\x50\xfc\x04\x21\xb2\x71\xcd\x11\x4f\x5f\x4f\xcc\x03\x13\x40\x36\x95\x8c\x0c\x61\x7d\x62\x50\x23\x9b\x2d\x6a\xa8\x99\x3a\xf1\x7a\x82\xa1\x54\xa7\xb9\xdd\x85\x82\x11\x25\xaa\x4d\xda\x56\x15\xea\xbc\xaf\xb6\xb9\x9a\x57\x87\x49\xf3\xea\x71\x82\x7f\x79\xa2\x8d\xbd\x3b\x4d\xb1\x30\xe6\x2c\x1d\x8e\x19\x8c\x8a\xc7\x82\x38\x1a\x36\xf2\x20\x91\x34\x61\x84\x72\x80\x55\x16\xba\x13\x43\x4a\x4a\xab\xc4\x10\x0b\xac\x6b\xe2\x08\x4d\x2f\x9d\xad\xe2\x81\x81\x3d\xd7\x5f\x8c\x04\x20\x17\x19\x02\xac\x29\x91\xd3\x32\x67\x54\x7a\xaa\x03\x72\x45\x45\x9d\xb7\x46\xb9\x98\xd6\x58\xd8\x87\x61\xd2\x34\xf6\x14\x82\x91\x27\xa5\x34\x49\x7a\x2d\x45\x92\x7c\xe5\x20\xd7\x2e\x3d\x57\x9e\x07\x71\x53\x1f\xc6\xfc\x74\xa6\xc3\xee\xd5\x14\xee\xc7\xb9\x48\x9a\x28\x33\x73\x24\xd1\x70\x88\x13\xec\x6c\x2c\x44\xe2\xf1\x2d\xb7\xfe\x72\x7f\xb9\x4a\xf9\x5f\xc8\xec\x99\x4f\x30\x95\xf2\xe9\x51\x4c\x35\xa0\xab\x28\xbd\x4c\x52\x24\xa3\xe1\x59\xc0\x29\x9e\xb1\x56\x27\xc7\x8f\xfa\x3d\x17\x00\x9b\x4e\xf1\x90\x4a\x79\x26\x9d\xb4\xd8\xbf\x13\xe6\x17\xf3\x77\x17\x87\x0f\xf3\x2b\xda\xb9\x0b\xcd\xe4\x9f\x3e\xcc\xdf\xfd\x1d\x00\x00\xff\xff\x7e\x7f\x3e\xa6\xbd\x0b\x00\x00") func db_sqlite_migrations_metadata_1637447083_sql() ([]byte, error) { return bindata_read( diff --git a/internal/db/sqlite/models/metadata/headers.go b/internal/db/sqlite/models/metadata/headers.go index e7e7036..e3d5cc1 100644 --- a/internal/db/sqlite/models/metadata/headers.go +++ b/internal/db/sqlite/models/metadata/headers.go @@ -30,6 +30,7 @@ type Header struct { Deleted int64 `boil:"deleted" json:"deleted" toml:"deleted" yaml:"deleted"` Typeflag int64 `boil:"typeflag" json:"typeflag" toml:"typeflag" yaml:"typeflag"` Name string `boil:"name" json:"name" toml:"name" yaml:"name"` + Linkname string `boil:"linkname" json:"linkname" toml:"linkname" yaml:"linkname"` Size int64 `boil:"size" json:"size" toml:"size" yaml:"size"` Mode int64 `boil:"mode" json:"mode" toml:"mode" yaml:"mode"` UID int64 `boil:"uid" json:"uid" toml:"uid" yaml:"uid"` @@ -56,6 +57,7 @@ var HeaderColumns = struct { Deleted string Typeflag string Name string + Linkname string Size string Mode string UID string @@ -77,6 +79,7 @@ var HeaderColumns = struct { Deleted: "deleted", Typeflag: "typeflag", Name: "name", + Linkname: "linkname", Size: "size", Mode: "mode", UID: "uid", @@ -100,6 +103,7 @@ var HeaderTableColumns = struct { Deleted string Typeflag string Name string + Linkname string Size string Mode string UID string @@ -121,6 +125,7 @@ var HeaderTableColumns = struct { Deleted: "headers.deleted", Typeflag: "headers.typeflag", Name: "headers.name", + Linkname: "headers.linkname", Size: "headers.size", Mode: "headers.mode", UID: "headers.uid", @@ -190,6 +195,7 @@ var HeaderWhere = struct { Deleted whereHelperint64 Typeflag whereHelperint64 Name whereHelperstring + Linkname whereHelperstring Size whereHelperint64 Mode whereHelperint64 UID whereHelperint64 @@ -211,6 +217,7 @@ var HeaderWhere = struct { Deleted: whereHelperint64{field: "\"headers\".\"deleted\""}, Typeflag: whereHelperint64{field: "\"headers\".\"typeflag\""}, Name: whereHelperstring{field: "\"headers\".\"name\""}, + Linkname: whereHelperstring{field: "\"headers\".\"linkname\""}, Size: whereHelperint64{field: "\"headers\".\"size\""}, Mode: whereHelperint64{field: "\"headers\".\"mode\""}, UID: whereHelperint64{field: "\"headers\".\"uid\""}, @@ -243,10 +250,10 @@ func (*headerR) NewStruct() *headerR { type headerL struct{} var ( - headerAllColumns = []string{"record", "lastknownrecord", "block", "lastknownblock", "deleted", "typeflag", "name", "size", "mode", "uid", "gid", "uname", "gname", "modtime", "accesstime", "changetime", "devmajor", "devminor", "paxrecords", "format"} - headerColumnsWithoutDefault = []string{"record", "lastknownrecord", "block", "lastknownblock", "deleted", "typeflag", "name", "size", "mode", "uid", "gid", "uname", "gname", "modtime", "accesstime", "changetime", "devmajor", "devminor", "paxrecords", "format"} + headerAllColumns = []string{"record", "lastknownrecord", "block", "lastknownblock", "deleted", "typeflag", "name", "linkname", "size", "mode", "uid", "gid", "uname", "gname", "modtime", "accesstime", "changetime", "devmajor", "devminor", "paxrecords", "format"} + headerColumnsWithoutDefault = []string{"record", "lastknownrecord", "block", "lastknownblock", "deleted", "typeflag", "name", "linkname", "size", "mode", "uid", "gid", "uname", "gname", "modtime", "accesstime", "changetime", "devmajor", "devminor", "paxrecords", "format"} headerColumnsWithDefault = []string{} - headerPrimaryKeyColumns = []string{"name"} + headerPrimaryKeyColumns = []string{"name", "linkname"} ) type ( @@ -532,7 +539,7 @@ func Headers(mods ...qm.QueryMod) headerQuery { // FindHeader retrieves a single record by ID with an executor. // If selectCols is empty Find will return all columns. -func FindHeader(ctx context.Context, exec boil.ContextExecutor, name string, selectCols ...string) (*Header, error) { +func FindHeader(ctx context.Context, exec boil.ContextExecutor, name string, linkname string, selectCols ...string) (*Header, error) { headerObj := &Header{} sel := "*" @@ -540,10 +547,10 @@ func FindHeader(ctx context.Context, exec boil.ContextExecutor, name string, sel sel = strings.Join(strmangle.IdentQuoteSlice(dialect.LQ, dialect.RQ, selectCols), ",") } query := fmt.Sprintf( - "select %s from \"headers\" where \"name\"=?", sel, + "select %s from \"headers\" where \"name\"=? AND \"linkname\"=?", sel, ) - q := queries.Raw(query, name) + q := queries.Raw(query, name, linkname) err := q.Bind(ctx, exec, headerObj) if err != nil { @@ -779,7 +786,7 @@ func (o *Header) Delete(ctx context.Context, exec boil.ContextExecutor) (int64, } args := queries.ValuesFromMapping(reflect.Indirect(reflect.ValueOf(o)), headerPrimaryKeyMapping) - sql := "DELETE FROM \"headers\" WHERE \"name\"=?" + sql := "DELETE FROM \"headers\" WHERE \"name\"=? AND \"linkname\"=?" if boil.IsDebug(ctx) { writer := boil.DebugWriterFrom(ctx) @@ -876,7 +883,7 @@ func (o HeaderSlice) DeleteAll(ctx context.Context, exec boil.ContextExecutor) ( // Reload refetches the object from the database // using the primary keys with an executor. func (o *Header) Reload(ctx context.Context, exec boil.ContextExecutor) error { - ret, err := FindHeader(ctx, exec, o.Name) + ret, err := FindHeader(ctx, exec, o.Name, o.Linkname) if err != nil { return err } @@ -915,16 +922,16 @@ func (o *HeaderSlice) ReloadAll(ctx context.Context, exec boil.ContextExecutor) } // HeaderExists checks if the Header row exists. -func HeaderExists(ctx context.Context, exec boil.ContextExecutor, name string) (bool, error) { +func HeaderExists(ctx context.Context, exec boil.ContextExecutor, name string, linkname string) (bool, error) { var exists bool - sql := "select exists(select 1 from \"headers\" where \"name\"=? limit 1)" + sql := "select exists(select 1 from \"headers\" where \"name\"=? AND \"linkname\"=? limit 1)" if boil.IsDebug(ctx) { writer := boil.DebugWriterFrom(ctx) fmt.Fprintln(writer, sql) - fmt.Fprintln(writer, name) + fmt.Fprintln(writer, name, linkname) } - row := exec.QueryRowContext(ctx, sql, name) + row := exec.QueryRowContext(ctx, sql, name, linkname) err := row.Scan(&exists) if err != nil { diff --git a/pkg/fs/filesystem_test.go b/pkg/fs/filesystem_test.go index 989d22d..19f44e8 100644 --- a/pkg/fs/filesystem_test.go +++ b/pkg/fs/filesystem_test.go @@ -11,6 +11,7 @@ import ( "path" "path/filepath" "reflect" + "strings" "sync" "testing" "time" @@ -2914,729 +2915,783 @@ func TestSTFS_Chtimes(t *testing.T) { } } -// FIXME: Handle linkname -// type lstatArgs struct { -// name string -// } - -// var lstatTests = []struct { -// name string -// args lstatArgs -// wantErr bool -// prepare func(*STFS) error -// check func(os.FileInfo) error -// withCache bool -// withOsFs bool -// }{ -// { -// "Can not lstat /", -// lstatArgs{"/"}, -// true, -// func(f *STFS) error { return nil }, -// func(f os.FileInfo) error { return nil }, -// true, -// true, -// }, -// { -// "Can not lstat /test.txt without creating it", -// lstatArgs{"/test.txt"}, -// true, -// func(f *STFS) error { return nil }, -// func(f os.FileInfo) error { return nil }, -// true, -// true, -// }, -// { -// "Can lstat /test2.txt after creating /test.txt and symlinking it", -// lstatArgs{"/test2.txt"}, -// false, -// func(f *STFS) error { -// if _, err := f.Create("/test.txt"); err != nil { -// return err -// } - -// if err := f.SymlinkIfPossible("/test.txt", "/test2.txt"); err != nil { -// return err -// } - -// return nil -// }, -// func(f os.FileInfo) error { -// want := "test2.txt" -// got := f.Name() - -// if want != got { -// return fmt.Errorf("invalid name, got %v, want %v", got, want) -// } - -// return nil -// }, -// true, -// true, -// }, -// { -// "Can not lstat /mydir/test.txt without creating it", -// lstatArgs{"/mydir/test.txt"}, -// true, -// func(f *STFS) error { return nil }, -// func(f os.FileInfo) error { return nil }, -// true, -// true, -// }, -// { -// "Can lstat /mydir/test2.txt after creating /mydir/test.txt and symlinking it", -// lstatArgs{"/mydir/test2.txt"}, -// false, -// func(f *STFS) error { -// if err := f.Mkdir("/mydir", os.ModePerm); err != nil { -// return err -// } - -// if _, err := f.Create("/mydir/test.txt"); err != nil { -// return err -// } - -// if err := f.SymlinkIfPossible("/mydir/test.txt", "/mydir/test2.txt"); err != nil { -// return err -// } - -// return nil -// }, -// func(f os.FileInfo) error { -// want := "test2.txt" -// got := f.Name() - -// if want != got { -// return fmt.Errorf("invalid name, got %v, want %v", got, want) -// } - -// return nil -// }, -// true, -// true, -// }, -// { -// "Result of lstat /test2.txt after creating /test.txt and symlinking it matches provided values", -// lstatArgs{"/test2.txt"}, -// false, -// func(f *STFS) error { -// file, err := f.OpenFile("/test.txt", os.O_CREATE, os.ModePerm) -// if err != nil { -// return err -// } -// if err := file.Close(); err != nil { -// return err -// } - -// if err := f.SymlinkIfPossible("/test.txt", "/test2.txt"); err != nil { -// return err -// } - -// return nil -// }, -// func(f os.FileInfo) error { -// wantName := "test2.txt" -// gotName := f.Name() - -// if wantName != gotName { -// return fmt.Errorf("invalid name, got %v, want %v", gotName, wantName) -// } - -// wantPerm := os.ModePerm -// gotPerm := f.Mode().Perm() - -// if wantPerm != gotPerm { -// return fmt.Errorf("invalid perm, got %v, want %v", gotPerm, wantPerm) -// } - -// return nil -// }, -// true, -// true, -// }, -// } - -// func TestSTFS_Lstat(t *testing.T) { -// for _, tt := range lstatTests { -// tt := tt - -// runTestForAllFss(t, tt.name, true, tt.withCache, tt.withOsFs, func(t *testing.T, fs fsConfig) { -// stfs, ok := fs.fs.(*STFS) -// if !ok { -// return -// } - -// if err := tt.prepare(stfs); err != nil { -// t.Errorf("%v prepare() error = %v", stfs.Name(), err) - -// return -// } - -// got, possible, err := stfs.LstatIfPossible(tt.args.name) -// if !possible { -// t.Errorf("%v.LstatIfPossible() possible = %v, want %v", stfs.Name(), possible, true) -// } - -// if (err != nil) != tt.wantErr { -// t.Errorf("%v.LstatIfPossible() error = %v, wantErr %v", stfs.Name(), err, tt.wantErr) - -// return -// } - -// if err := tt.check(got); err != nil { -// t.Errorf("%v check() error = %v", stfs.Name(), err) - -// return -// } -// }) -// } -// } - -// type symlinkArgs struct { -// oldname string -// newname string -// } - -// var symlinkTests = []struct { -// name string -// args symlinkArgs -// wantErr bool -// prepare func(symFs) error -// check func(symFs) error -// checkAfterError bool -// withCache bool -// withOsFs bool -// }{ -// { -// "Can symlink / to /mydir", -// symlinkArgs{"/", "/mydir"}, -// false, -// func(f symFs) error { return nil }, -// func(f symFs) error { -// info, _, err := f.LstatIfPossible("/mydir") -// if err != nil { -// return err -// } - -// want := "mydir" -// got := info.Name() - -// if want != got { -// return fmt.Errorf("symlinked file has wrong name, got %v, want %v", got, want) -// } - -// return nil -// }, -// false, -// true, -// true, -// }, -// { -// "Can not symlink / to /", -// symlinkArgs{"/", "/"}, -// true, -// func(f symFs) error { return nil }, -// func(f symFs) error { return nil }, -// false, -// true, -// true, -// }, -// { -// "Can not symlink '' to ''", -// symlinkArgs{"", ""}, -// true, -// func(f symFs) error { return nil }, -// func(f symFs) error { return nil }, -// false, -// true, -// true, -// }, -// { -// "Can symlink ' ' to ' '", -// symlinkArgs{" ", " "}, -// false, -// func(f symFs) error { return nil }, -// func(f symFs) error { -// if _, _, err := f.LstatIfPossible(" "); !errors.Is(err, os.ErrNotExist) { -// return err -// } - -// return nil -// }, -// false, -// true, -// true, -// }, -// { -// "Can symlink /test.txt to /new.txt if does exist", -// symlinkArgs{"/test.txt", "/new.txt"}, -// false, -// func(f symFs) error { -// if _, err := f.Create("/test.txt"); err != nil { -// return err -// } - -// return nil -// }, -// func(f symFs) error { -// info, _, err := f.LstatIfPossible("/new.txt") -// if err != nil { -// return err -// } - -// want := "new.txt" -// got := info.Name() - -// if want != got { -// return fmt.Errorf("symlinked file has wrong name, got %v, want %v", got, want) -// } - -// return nil -// }, -// false, -// true, -// true, -// }, -// { -// "Can symlink /test.txt to /new.txt if does not exist", -// symlinkArgs{"/test.txt", "/new.txt"}, -// false, -// func(f symFs) error { return nil }, -// func(f symFs) error { return nil }, -// false, -// true, -// true, -// }, -// { -// "Can symlink directory /myolddir to /mydir", -// symlinkArgs{"/myolddir", "/mydir"}, -// false, -// func(f symFs) error { -// if err := f.Mkdir("/myolddir", os.ModePerm); err != nil { -// return err -// } - -// return nil -// }, -// func(f symFs) error { -// info, _, err := f.LstatIfPossible("/mydir") -// if err != nil { -// return err -// } - -// want := "mydir" -// got := info.Name() - -// if want != got { -// return fmt.Errorf("symlinked directory has wrong name, got %v, want %v", got, want) -// } - -// return nil -// }, -// false, -// false, -// true, -// }, -// { -// "Can symlink non-empty directory /myolddir to /mydir", -// symlinkArgs{"/myolddir", "/mydir"}, -// false, -// func(f symFs) error { -// if err := f.Mkdir("/myolddir", os.ModePerm); err != nil { -// return err -// } - -// if _, err := f.Create("/myolddir/test.txt"); err != nil { -// return err -// } - -// return nil -// }, -// func(f symFs) error { -// info, _, err := f.LstatIfPossible("/mydir") -// if err != nil { -// return err -// } - -// want := "mydir" -// got := info.Name() - -// if want != got { -// return fmt.Errorf("symlinked directory has wrong name, got %v, want %v", got, want) -// } - -// return nil -// }, -// false, -// false, -// true, -// }, -// { -// "Can not symlink /test.txt to /mydir/new.txt if new parent drectory does not exist", -// symlinkArgs{"/test.txt", "/mydir/new.txt"}, -// true, -// func(f symFs) error { -// if _, err := f.Create("/test.txt"); err != nil { -// return err -// } - -// return nil -// }, -// func(f symFs) error { -// if _, _, err := f.LstatIfPossible("/mydir/new.txt"); !errors.Is(err, os.ErrNotExist) { -// return err -// } - -// return nil -// }, -// false, -// true, -// true, -// }, -// { -// "Can symlink /test.txt to /mydir/new.txt if new parent drectory does exist", -// symlinkArgs{"/test.txt", "/mydir/new.txt"}, -// false, -// func(f symFs) error { -// if _, err := f.Create("/test.txt"); err != nil { -// return err -// } - -// if err := f.Mkdir("/mydir", os.ModePerm); err != nil { -// return err -// } - -// return nil -// }, -// func(f symFs) error { -// info, _, err := f.LstatIfPossible("/mydir/new.txt") -// if err != nil { -// return err -// } - -// want := "new.txt" -// got := info.Name() - -// if want != got { -// return fmt.Errorf("invalid name, got %v, want %v", got, want) -// } - -// return nil -// }, -// false, -// true, -// true, -// }, -// { -// "Can not symlink /test.txt to /test.txt if does exist", -// symlinkArgs{"/test.txt", "/test.txt"}, -// true, -// func(f symFs) error { -// if _, err := f.Create("/test.txt"); err != nil { -// return err -// } - -// return nil -// }, -// func(f symFs) error { return nil }, -// false, -// true, -// true, -// }, -// { -// "Can symlink /test.txt to /test.txt if does not exist", -// symlinkArgs{"/test.txt", "/test.txt"}, -// false, -// func(f symFs) error { return nil }, -// func(f symFs) error { -// info, _, err := f.LstatIfPossible("/test.txt") -// if err != nil { -// return err -// } - -// want := "test.txt" -// got := info.Name() - -// if want != got { -// return fmt.Errorf("invalid name, got %v, want %v", got, want) -// } - -// return nil -// }, -// false, -// true, -// true, -// }, -// { -// "Can not symlink /test.txt to /existing.txt if source and target both exist", -// symlinkArgs{"/test.txt", "/existing.txt"}, -// true, -// func(f symFs) error { -// if _, err := f.Create("/test.txt"); err != nil { -// return err -// } - -// if _, err := f.Create("/existing.txt"); err != nil { -// return err -// } - -// return nil -// }, -// func(f symFs) error { return nil }, -// false, -// true, -// true, -// }, -// { -// "Can not symlink /test.txt to /mydir if source is file and target is directory", -// symlinkArgs{"/test.txt", "/mydir"}, -// true, -// func(f symFs) error { -// if _, err := f.Create("/test.txt"); err != nil { -// return err -// } - -// if err := f.Mkdir("/mydir", os.ModePerm); err != nil { -// return err -// } - -// return nil -// }, -// func(f symFs) error { return nil }, -// false, -// true, -// true, -// }, -// { -// "Can not symlink /mydir to /test.txt if source is directory and target is file", -// symlinkArgs{"/mydir", "/test.txt"}, -// true, -// func(f symFs) error { -// if _, err := f.Create("/test.txt"); err != nil { -// return err -// } - -// if err := f.Mkdir("/mydir", os.ModePerm); err != nil { -// return err -// } - -// return nil -// }, -// func(f symFs) error { return nil }, -// false, -// true, -// true, -// }, -// { -// "Can not symlink /test.txt to /test.txt/", -// symlinkArgs{"/test.txt", "/test.txt/"}, -// true, -// func(f symFs) error { -// if _, err := f.Create("/test.txt"); err != nil { -// return err -// } - -// return nil -// }, -// func(f symFs) error { return nil }, -// false, -// true, -// true, -// }, -// } - -// func TestSTFS_Symlink(t *testing.T) { -// for _, tt := range symlinkTests { -// tt := tt - -// runTestForAllFss(t, tt.name, true, tt.withCache, tt.withOsFs, func(t *testing.T, fs fsConfig) { -// symFs, ok := fs.fs.(symFs) -// if !ok { -// return -// } - -// if err := tt.prepare(symFs); err != nil { -// t.Errorf("%v prepare() error = %v", symFs.Name(), err) - -// return -// } - -// if err := symFs.SymlinkIfPossible(tt.args.oldname, tt.args.newname); (err != nil) != tt.wantErr { -// if !tt.checkAfterError { -// t.Errorf("%v.SymlinkIfPossible() error = %v, wantErr %v", symFs.Name(), err, tt.wantErr) - -// return -// } -// } - -// if err := tt.check(symFs); err != nil { -// t.Errorf("%v check() error = %v", symFs.Name(), err) - -// return -// } -// }) -// } -// } - -// type readlinkArgs struct { -// name string -// } - -// var readlinkTests = []struct { -// name string -// args readlinkArgs -// wantErr bool -// prepare func(symFs) error -// check func(string) error -// withCache bool -// withOsFs bool -// }{ -// { -// "Can not readlink /", -// readlinkArgs{"/"}, -// true, -// func(f symFs) error { return nil }, -// func(got string) error { return nil }, -// true, -// true, -// }, -// { -// "Can not readlink /test.txt without creating it", -// readlinkArgs{"/test.txt"}, -// true, -// func(f symFs) error { return nil }, -// func(got string) error { return nil }, -// true, -// true, -// }, -// { -// "Can readlink /test2.txt after creating /test.txt and symlinking it", -// readlinkArgs{"/test2.txt"}, -// false, -// func(f symFs) error { -// if _, err := f.Create("/test.txt"); err != nil { -// return err -// } - -// if err := f.SymlinkIfPossible("/test.txt", "/test2.txt"); err != nil { -// return err -// } - -// return nil -// }, -// func(got string) error { -// want := "test.txt" - -// if !strings.HasSuffix(got, want) { -// return fmt.Errorf("invalid name, got %v, want %v", got, want) -// } - -// return nil -// }, -// true, -// true, -// }, -// { -// "Can not readlink /mydir/test.txt without creating it", -// readlinkArgs{"/mydir/test.txt"}, -// true, -// func(f symFs) error { return nil }, -// func(got string) error { return nil }, -// true, -// true, -// }, -// { -// "Can readlink /mydir/test2.txt after creating /mydir/test.txt and symlinking it", -// readlinkArgs{"/mydir/test2.txt"}, -// false, -// func(f symFs) error { -// if err := f.Mkdir("/mydir", os.ModePerm); err != nil { -// return err -// } - -// if _, err := f.Create("/mydir/test.txt"); err != nil { -// return err -// } - -// if err := f.SymlinkIfPossible("/mydir/test.txt", "/mydir/test2.txt"); err != nil { -// return err -// } - -// return nil -// }, -// func(got string) error { -// want := "test.txt" - -// if !strings.HasSuffix(got, want) { -// return fmt.Errorf("invalid name, got %v, want %v", got, want) -// } - -// return nil -// }, -// true, -// true, -// }, -// { -// "Result of readlink /test2.txt after creating /test.txt and symlinking it matches provided values", -// readlinkArgs{"/test2.txt"}, -// false, -// func(f symFs) error { -// file, err := f.OpenFile("/test.txt", os.O_CREATE, os.ModePerm) -// if err != nil { -// return err -// } -// if err := file.Close(); err != nil { -// return err -// } - -// if err := f.SymlinkIfPossible("/test.txt", "/test2.txt"); err != nil { -// return err -// } - -// return nil -// }, -// func(got string) error { -// want := "test.txt" - -// if !strings.HasSuffix(got, want) { -// return fmt.Errorf("invalid name, got %v, want %v", got, want) -// } - -// return nil -// }, -// true, -// true, -// }, -// } - -// func TestSTFS_Readlink(t *testing.T) { -// for _, tt := range readlinkTests { -// tt := tt - -// runTestForAllFss(t, tt.name, true, tt.withCache, tt.withOsFs, func(t *testing.T, fs fsConfig) { -// symFs, ok := fs.fs.(symFs) -// if !ok { -// return -// } - -// if err := tt.prepare(symFs); err != nil { -// t.Errorf("%v prepare() error = %v", symFs.Name(), err) - -// return -// } - -// got, err := symFs.ReadlinkIfPossible(tt.args.name) -// if (err != nil) != tt.wantErr { -// t.Errorf("%v.ReadlinkIfPossible() error = %v, wantErr %v", symFs.Name(), err, tt.wantErr) - -// return -// } - -// if err := tt.check(got); err != nil { -// t.Errorf("%v check() error = %v", symFs.Name(), err) - -// return -// } -// }) -// } -// } +type lstatArgs struct { + name []string +} + +var lstatTests = []struct { + name string + args lstatArgs + wantErr bool + prepare func(*STFS) error + check func(os.FileInfo, int) error + withCache bool + withOsFs bool +}{ + { + "Can not lstat /", + lstatArgs{[]string{"/"}}, + true, + func(f *STFS) error { return nil }, + func(f os.FileInfo, i int) error { return nil }, + true, + true, + }, + { + "Can not lstat /test.txt without creating it", + lstatArgs{[]string{"/test.txt"}}, + true, + func(f *STFS) error { return nil }, + func(f os.FileInfo, i int) error { return nil }, + true, + true, + }, + { + "Can lstat /test2.txt after creating /test.txt and symlinking it", + lstatArgs{[]string{"/test2.txt"}}, + false, + func(f *STFS) error { + if _, err := f.Create("/test.txt"); err != nil { + return err + } + + if err := f.SymlinkIfPossible("/test.txt", "/test2.txt"); err != nil { + return err + } + + return nil + }, + func(f os.FileInfo, i int) error { + want := "test2.txt" + got := f.Name() + + if want != got { + return fmt.Errorf("invalid name, got %v, want %v", got, want) + } + + return nil + }, + true, + true, + }, + { + "Can not lstat /mydir/test.txt without creating it", + lstatArgs{[]string{"/mydir/test.txt"}}, + true, + func(f *STFS) error { return nil }, + func(f os.FileInfo, i int) error { return nil }, + true, + true, + }, + { + "Can lstat /mydir/test2.txt after creating /mydir/test.txt and symlinking it", + lstatArgs{[]string{"/mydir/test2.txt"}}, + false, + func(f *STFS) error { + if err := f.Mkdir("/mydir", os.ModePerm); err != nil { + return err + } + + if _, err := f.Create("/mydir/test.txt"); err != nil { + return err + } + + if err := f.SymlinkIfPossible("/mydir/test.txt", "/mydir/test2.txt"); err != nil { + return err + } + + return nil + }, + func(f os.FileInfo, i int) error { + want := "test2.txt" + got := f.Name() + + if want != got { + return fmt.Errorf("invalid name, got %v, want %v", got, want) + } + + return nil + }, + true, + true, + }, + { + "Result of lstat /test2.txt after creating /test.txt and symlinking it matches provided values", + lstatArgs{[]string{"/test2.txt"}}, + false, + func(f *STFS) error { + file, err := f.OpenFile("/test.txt", os.O_CREATE, os.ModePerm) + if err != nil { + return err + } + if err := file.Close(); err != nil { + return err + } + + if err := f.SymlinkIfPossible("/test.txt", "/test2.txt"); err != nil { + return err + } + + return nil + }, + func(f os.FileInfo, i int) error { + wantName := "test2.txt" + gotName := f.Name() + + if wantName != gotName { + return fmt.Errorf("invalid name, got %v, want %v", gotName, wantName) + } + + wantPerm := os.ModePerm + gotPerm := f.Mode().Perm() + + if wantPerm != gotPerm { + return fmt.Errorf("invalid perm, got %v, want %v", gotPerm, wantPerm) + } + + return nil + }, + true, + true, + }, + { + "Result of lstat /test2.txt, /test3.txt and /test4.txt after creating /test.txt and symlinking it matches provided values", + lstatArgs{[]string{"/test2.txt", "/test3.txt", "/test4.txt"}}, + false, + func(f *STFS) error { + file, err := f.OpenFile("/test.txt", os.O_CREATE, os.ModePerm) + if err != nil { + return err + } + if err := file.Close(); err != nil { + return err + } + + if err := f.SymlinkIfPossible("/test.txt", "/test2.txt"); err != nil { + return err + } + + if err := f.SymlinkIfPossible("/test.txt", "/test3.txt"); err != nil { + return err + } + + if err := f.SymlinkIfPossible("/test.txt", "/test4.txt"); err != nil { + return err + } + + return nil + }, + func(f os.FileInfo, i int) error { + wantName := "test2.txt" + if i == 1 { + wantName = "test3.txt" + } else if i == 2 { + wantName = "test4.txt" + } + + gotName := f.Name() + + if wantName != gotName { + return fmt.Errorf("invalid name, got %v, want %v", gotName, wantName) + } + + wantPerm := os.ModePerm + gotPerm := f.Mode().Perm() + + if wantPerm != gotPerm { + return fmt.Errorf("invalid perm, got %v, want %v", gotPerm, wantPerm) + } + + return nil + }, + true, + true, + }, +} + +func TestSTFS_Lstat(t *testing.T) { + for _, tt := range lstatTests { + tt := tt + + runTestForAllFss(t, tt.name, true, tt.withCache, tt.withOsFs, func(t *testing.T, fs fsConfig) { + stfs, ok := fs.fs.(*STFS) + if !ok { + return + } + + if err := tt.prepare(stfs); err != nil { + t.Errorf("%v prepare() error = %v", stfs.Name(), err) + + return + } + + for i, arg := range tt.args.name { + got, possible, err := stfs.LstatIfPossible(arg) + if !possible { + t.Errorf("%v.LstatIfPossible() possible = %v, want %v", stfs.Name(), possible, true) + } + + if (err != nil) != tt.wantErr { + t.Errorf("%v.LstatIfPossible() error = %v, wantErr %v", stfs.Name(), err, tt.wantErr) + + return + } + + if err := tt.check(got, i); err != nil { + t.Errorf("%v check() error = %v", stfs.Name(), err) + + return + } + } + }) + } +} + +type symlinkArgs struct { + oldname string + newname string +} + +var symlinkTests = []struct { + name string + args symlinkArgs + wantErr bool + prepare func(symFs) error + check func(symFs) error + checkAfterError bool + withCache bool + withOsFs bool +}{ + { + "Can symlink / to /mydir", + symlinkArgs{"/", "/mydir"}, + false, + func(f symFs) error { return nil }, + func(f symFs) error { + info, _, err := f.LstatIfPossible("/mydir") + if err != nil { + return err + } + + want := "mydir" + got := info.Name() + + if want != got { + return fmt.Errorf("symlinked file has wrong name, got %v, want %v", got, want) + } + + return nil + }, + false, + true, + true, + }, + { + "Can not symlink / to /", + symlinkArgs{"/", "/"}, + true, + func(f symFs) error { return nil }, + func(f symFs) error { return nil }, + false, + true, + true, + }, + { + "Can not symlink '' to ''", + symlinkArgs{"", ""}, + true, + func(f symFs) error { return nil }, + func(f symFs) error { return nil }, + false, + true, + true, + }, + { + "Can symlink ' ' to ' '", + symlinkArgs{" ", " "}, + false, + func(f symFs) error { return nil }, + func(f symFs) error { + if _, _, err := f.LstatIfPossible(" "); !errors.Is(err, os.ErrNotExist) { + return err + } + + return nil + }, + false, + true, + true, + }, + { + "Can symlink /test.txt to /new.txt if does exist", + symlinkArgs{"/test.txt", "/new.txt"}, + false, + func(f symFs) error { + if _, err := f.Create("/test.txt"); err != nil { + return err + } + + return nil + }, + func(f symFs) error { + info, _, err := f.LstatIfPossible("/new.txt") + if err != nil { + return err + } + + want := "new.txt" + got := info.Name() + + if want != got { + return fmt.Errorf("symlinked file has wrong name, got %v, want %v", got, want) + } + + return nil + }, + false, + true, + true, + }, + { + "Can symlink /test.txt to /new.txt if does not exist", + symlinkArgs{"/test.txt", "/new.txt"}, + false, + func(f symFs) error { return nil }, + func(f symFs) error { return nil }, + false, + true, + true, + }, + { + "Can symlink directory /myolddir to /mydir", + symlinkArgs{"/myolddir", "/mydir"}, + false, + func(f symFs) error { + if err := f.Mkdir("/myolddir", os.ModePerm); err != nil { + return err + } + + return nil + }, + func(f symFs) error { + info, _, err := f.LstatIfPossible("/mydir") + if err != nil { + return err + } + + want := "mydir" + got := info.Name() + + if want != got { + return fmt.Errorf("symlinked directory has wrong name, got %v, want %v", got, want) + } + + return nil + }, + false, + false, + true, + }, + { + "Can symlink non-empty directory /myolddir to /mydir", + symlinkArgs{"/myolddir", "/mydir"}, + false, + func(f symFs) error { + if err := f.Mkdir("/myolddir", os.ModePerm); err != nil { + return err + } + + if _, err := f.Create("/myolddir/test.txt"); err != nil { + return err + } + + return nil + }, + func(f symFs) error { + info, _, err := f.LstatIfPossible("/mydir") + if err != nil { + return err + } + + want := "mydir" + got := info.Name() + + if want != got { + return fmt.Errorf("symlinked directory has wrong name, got %v, want %v", got, want) + } + + return nil + }, + false, + false, + true, + }, + { + "Can not symlink /test.txt to /mydir/new.txt if new parent drectory does not exist", + symlinkArgs{"/test.txt", "/mydir/new.txt"}, + true, + func(f symFs) error { + if _, err := f.Create("/test.txt"); err != nil { + return err + } + + return nil + }, + func(f symFs) error { + if _, _, err := f.LstatIfPossible("/mydir/new.txt"); !errors.Is(err, os.ErrNotExist) { + return err + } + + return nil + }, + false, + true, + true, + }, + { + "Can symlink /test.txt to /mydir/new.txt if new parent drectory does exist", + symlinkArgs{"/test.txt", "/mydir/new.txt"}, + false, + func(f symFs) error { + if _, err := f.Create("/test.txt"); err != nil { + return err + } + + if err := f.Mkdir("/mydir", os.ModePerm); err != nil { + return err + } + + return nil + }, + func(f symFs) error { + info, _, err := f.LstatIfPossible("/mydir/new.txt") + if err != nil { + return err + } + + want := "new.txt" + got := info.Name() + + if want != got { + return fmt.Errorf("invalid name, got %v, want %v", got, want) + } + + return nil + }, + false, + true, + true, + }, + { + "Can not symlink /test.txt to /test.txt if does exist", + symlinkArgs{"/test.txt", "/test.txt"}, + true, + func(f symFs) error { + if _, err := f.Create("/test.txt"); err != nil { + return err + } + + return nil + }, + func(f symFs) error { return nil }, + false, + true, + true, + }, + { + "Can symlink /test.txt to /test.txt if does not exist", + symlinkArgs{"/test.txt", "/test.txt"}, + false, + func(f symFs) error { return nil }, + func(f symFs) error { + info, _, err := f.LstatIfPossible("/test.txt") + if err != nil { + return err + } + + want := "test.txt" + got := info.Name() + + if want != got { + return fmt.Errorf("invalid name, got %v, want %v", got, want) + } + + return nil + }, + false, + true, + true, + }, + { + "Can not symlink /test.txt to /existing.txt if source and target both exist", + symlinkArgs{"/test.txt", "/existing.txt"}, + true, + func(f symFs) error { + if _, err := f.Create("/test.txt"); err != nil { + return err + } + + if _, err := f.Create("/existing.txt"); err != nil { + return err + } + + return nil + }, + func(f symFs) error { return nil }, + false, + true, + true, + }, + { + "Can not symlink /test.txt to /mydir if source is file and target is directory", + symlinkArgs{"/test.txt", "/mydir"}, + true, + func(f symFs) error { + if _, err := f.Create("/test.txt"); err != nil { + return err + } + + if err := f.Mkdir("/mydir", os.ModePerm); err != nil { + return err + } + + return nil + }, + func(f symFs) error { return nil }, + false, + true, + true, + }, + { + "Can not symlink /mydir to /test.txt if source is directory and target is file", + symlinkArgs{"/mydir", "/test.txt"}, + true, + func(f symFs) error { + if _, err := f.Create("/test.txt"); err != nil { + return err + } + + if err := f.Mkdir("/mydir", os.ModePerm); err != nil { + return err + } + + return nil + }, + func(f symFs) error { return nil }, + false, + true, + true, + }, + { + "Can not symlink /test.txt to /test.txt/", + symlinkArgs{"/test.txt", "/test.txt/"}, + true, + func(f symFs) error { + if _, err := f.Create("/test.txt"); err != nil { + return err + } + + return nil + }, + func(f symFs) error { return nil }, + false, + true, + true, + }, +} + +func TestSTFS_Symlink(t *testing.T) { + for _, tt := range symlinkTests { + tt := tt + + runTestForAllFss(t, tt.name, true, tt.withCache, tt.withOsFs, func(t *testing.T, fs fsConfig) { + symFs, ok := fs.fs.(symFs) + if !ok { + return + } + + if err := tt.prepare(symFs); err != nil { + t.Errorf("%v prepare() error = %v", symFs.Name(), err) + + return + } + + if err := symFs.SymlinkIfPossible(tt.args.oldname, tt.args.newname); (err != nil) != tt.wantErr { + if !tt.checkAfterError { + t.Errorf("%v.SymlinkIfPossible() error = %v, wantErr %v", symFs.Name(), err, tt.wantErr) + + return + } + } + + if err := tt.check(symFs); err != nil { + t.Errorf("%v check() error = %v", symFs.Name(), err) + + return + } + }) + } +} + +type readlinkArgs struct { + name string +} + +var readlinkTests = []struct { + name string + args readlinkArgs + wantErr bool + prepare func(symFs) error + check func(string) error + withCache bool + withOsFs bool +}{ + { + "Can not readlink /", + readlinkArgs{"/"}, + true, + func(f symFs) error { return nil }, + func(got string) error { return nil }, + true, + true, + }, + { + "Can not readlink /test.txt without creating it", + readlinkArgs{"/test.txt"}, + true, + func(f symFs) error { return nil }, + func(got string) error { return nil }, + true, + true, + }, + { + "Can readlink /test2.txt after creating /test.txt and symlinking it", + readlinkArgs{"/test2.txt"}, + false, + func(f symFs) error { + if _, err := f.Create("/test.txt"); err != nil { + return err + } + + if err := f.SymlinkIfPossible("/test.txt", "/test2.txt"); err != nil { + return err + } + + return nil + }, + func(got string) error { + want := "test.txt" + + if !strings.HasSuffix(got, want) { + return fmt.Errorf("invalid name, got %v, want %v", got, want) + } + + return nil + }, + true, + true, + }, + { + "Can not readlink /mydir/test.txt without creating it", + readlinkArgs{"/mydir/test.txt"}, + true, + func(f symFs) error { return nil }, + func(got string) error { return nil }, + true, + true, + }, + { + "Can readlink /mydir/test2.txt after creating /mydir/test.txt and symlinking it", + readlinkArgs{"/mydir/test2.txt"}, + false, + func(f symFs) error { + if err := f.Mkdir("/mydir", os.ModePerm); err != nil { + return err + } + + if _, err := f.Create("/mydir/test.txt"); err != nil { + return err + } + + if err := f.SymlinkIfPossible("/mydir/test.txt", "/mydir/test2.txt"); err != nil { + return err + } + + return nil + }, + func(got string) error { + want := "test.txt" + + if !strings.HasSuffix(got, want) { + return fmt.Errorf("invalid name, got %v, want %v", got, want) + } + + return nil + }, + true, + true, + }, + { + "Result of readlink /test2.txt after creating /test.txt and symlinking it matches provided values", + readlinkArgs{"/test2.txt"}, + false, + func(f symFs) error { + file, err := f.OpenFile("/test.txt", os.O_CREATE, os.ModePerm) + if err != nil { + return err + } + if err := file.Close(); err != nil { + return err + } + + if err := f.SymlinkIfPossible("/test.txt", "/test2.txt"); err != nil { + return err + } + + return nil + }, + func(got string) error { + want := "test.txt" + + if !strings.HasSuffix(got, want) { + return fmt.Errorf("invalid name, got %v, want %v", got, want) + } + + return nil + }, + true, + true, + }, +} + +func TestSTFS_Readlink(t *testing.T) { + for _, tt := range readlinkTests { + tt := tt + + runTestForAllFss(t, tt.name, true, tt.withCache, tt.withOsFs, func(t *testing.T, fs fsConfig) { + symFs, ok := fs.fs.(symFs) + if !ok { + return + } + + if err := tt.prepare(symFs); err != nil { + t.Errorf("%v prepare() error = %v", symFs.Name(), err) + + return + } + + got, err := symFs.ReadlinkIfPossible(tt.args.name) + if (err != nil) != tt.wantErr { + t.Errorf("%v.ReadlinkIfPossible() error = %v, wantErr %v", symFs.Name(), err, tt.wantErr) + + return + } + + if err := tt.check(got); err != nil { + t.Errorf("%v check() error = %v", symFs.Name(), err) + + return + } + }) + } +} diff --git a/pkg/persisters/metadata.go b/pkg/persisters/metadata.go index 6e5ba0e..62e7a2f 100644 --- a/pkg/persisters/metadata.go +++ b/pkg/persisters/metadata.go @@ -6,7 +6,6 @@ package persisters import ( "context" "database/sql" - "errors" "fmt" "path" "path/filepath" @@ -108,7 +107,11 @@ func (p *MetadataPersister) UpsertHeader(ctx context.Context, dbhdr *config.Head hdr.Name = p.getSanitizedPath(ctx, idbhdr.Name) } - if _, err := models.FindHeader(ctx, p.sqlite.DB, hdr.Name, models.HeaderColumns.Name); err != nil { + if _, err := models.Headers( + qm.Where(models.HeaderColumns.Name+" = ?", hdr.Name), + qm.Where(models.HeaderColumns.Linkname+" = ?", hdr.Linkname), + qm.Where(models.HeaderColumns.Deleted+" != 1"), + ).One(ctx, p.sqlite.DB); err != nil { if err == sql.ErrNoRows { if err := hdr.Insert(ctx, p.sqlite.DB, boil.Infer()); err != nil { return err @@ -219,21 +222,17 @@ func (p *MetadataPersister) GetHeader(ctx context.Context, name string) (*config } func (p *MetadataPersister) GetHeaderByLinkname(ctx context.Context, linkname string) (*config.Header, error) { - return nil, errors.New("not implemented") + linkname = p.getSanitizedPath(ctx, linkname) - // FIXME: Handle linkname + hdr, err := models.Headers( + qm.Where(models.HeaderColumns.Linkname+" = ?", linkname), + qm.Where(models.HeaderColumns.Deleted+" != 1"), + ).One(ctx, p.sqlite.DB) + if err != nil { + return nil, err + } - // linkname = p.getSanitizedPath(ctx, linkname) - - // hdr, err := models.Headers( - // qm.Where(models.HeaderColumns.Linkname+" = ?", linkname), - // qm.Where(models.HeaderColumns.Deleted+" != 1"), - // ).One(ctx, p.sqlite.DB) - // if err != nil { - // return nil, err - // } - - // return converters.DBHeaderToConfigHeader(hdr), nil + return converters.DBHeaderToConfigHeader(hdr), nil } func (p *MetadataPersister) GetHeaderChildren(ctx context.Context, name string) ([]*config.Header, error) { @@ -295,7 +294,7 @@ func (p *MetadataPersister) GetHeaderDirectChildren(ctx context.Context, name st getHeaders := func(prefix string) ([]*config.Header, error) { query := fmt.Sprintf( - `select %v, %v, %v, %v, %v, %v, %v, %v, %v, %v, %v, %v, %v, %v, %v, %v, %v, %v, %v, %v, + `select %v, %v, %v, %v, %v, %v, %v, %v, %v, %v, %v, %v, %v, %v, %v, %v, %v, %v, %v, %v, %v, length(replace(%v, ?, '')) - length(replace(replace(%v, ?, ''), '/', '')) as depth from %v where %v like ? @@ -315,7 +314,7 @@ where %v like ? models.HeaderColumns.Deleted, models.HeaderColumns.Typeflag, models.HeaderColumns.Name, - // models.HeaderColumns.Linkname, // FIXME: Handle linkname + models.HeaderColumns.Linkname, models.HeaderColumns.Size, models.HeaderColumns.Mode, models.HeaderColumns.UID, @@ -397,7 +396,10 @@ where %v like ? func (p *MetadataPersister) DeleteHeader(ctx context.Context, name string, lastknownrecord, lastknownblock int64) (*config.Header, error) { name = p.getSanitizedPath(ctx, name) - hdr, err := models.FindHeader(ctx, p.sqlite.DB, name) + hdr, err := models.Headers( + qm.Where(models.HeaderColumns.Name+" = ?", name), + qm.Where(models.HeaderColumns.Deleted+" != 1"), + ).One(ctx, p.sqlite.DB) if err != nil { return nil, err }