31 |
32 |
32 #include <sys/xattr.h> |
33 #include <sys/xattr.h> |
33 |
34 |
34 #include <relpipe/writer/typedefs.h> |
35 #include <relpipe/writer/typedefs.h> |
35 |
36 |
|
37 #include "Configuration.h" |
|
38 #include "AttributeFinder.h" |
|
39 #include "FileAttributeFinder.h" |
|
40 #include "XattrAttributeFinder.h" |
|
41 |
36 namespace relpipe { |
42 namespace relpipe { |
37 namespace in { |
43 namespace in { |
38 namespace filesystem { |
44 namespace filesystem { |
39 |
45 |
40 namespace fs = std::filesystem; |
46 namespace fs = std::filesystem; |
41 using namespace relpipe::writer; |
47 using namespace relpipe::writer; |
42 |
48 |
43 class FilesystemCommand { |
49 class FilesystemCommand { |
|
50 private: |
44 std::wstring_convert<codecvt_utf8<wchar_t>> convertor; // TODO: support also other encodings. |
51 std::wstring_convert<codecvt_utf8<wchar_t>> convertor; // TODO: support also other encodings. |
|
52 |
|
53 FileAttributeFinder fileAttributeFinder; |
|
54 XattrAttributeFinder xattrAttributeFinder; |
|
55 |
|
56 std::map<string_t, AttributeFinder*> attributeFinders{ |
|
57 {RequestedField::GROUP_FILE, &fileAttributeFinder}, |
|
58 {RequestedField::GROUP_XATTR, &xattrAttributeFinder}}; |
45 |
59 |
46 void reset(std::stringstream& stream) { |
60 void reset(std::stringstream& stream) { |
47 stream.str(""); |
61 stream.str(""); |
48 stream.clear(); |
62 stream.clear(); |
49 } |
63 } |
54 else originalName << ch; |
68 else originalName << ch; |
55 } |
69 } |
56 return originalName.tellp(); |
70 return originalName.tellp(); |
57 } |
71 } |
58 |
72 |
59 string_t getType(const fs::path& file) { |
|
60 // TODO: Use whole words? (letters are compatible with find -type) |
|
61 if (fs::is_regular_file(file)) return L"f"; |
|
62 else if (fs::is_symlink(file)) return L"l"; // symlinks to directories are both symlinks and directories |
|
63 else if (fs::is_directory(file)) return L"d"; |
|
64 else if (fs::is_fifo(file)) return L"p"; |
|
65 else if (fs::is_socket(file)) return L"s"; |
|
66 else if (fs::is_block_file(file)) return L"b"; |
|
67 else if (fs::is_character_file(file)) return L"c"; |
|
68 else return L"o"; |
|
69 } |
|
70 |
|
71 void fetchOwner(const fs::path& file, string_t& owner, string_t& group) { |
|
72 // TODO: throw exception on error |
|
73 // TODO: get user and group in C++ way? |
|
74 struct stat info; |
|
75 stat(file.c_str(), &info); |
|
76 /** |
|
77 * The return value may point to a static area, and may be |
|
78 * overwritten by subsequent calls to getpwent(3), getpw‐ |
|
79 * nam(), or getpwuid(). (Do not pass the returned pointer |
|
80 * to free(3).) |
|
81 */ |
|
82 struct passwd* pw = getpwuid(info.st_uid); |
|
83 struct group* gr = getgrgid(info.st_gid); |
|
84 owner = convertor.from_bytes(pw->pw_name); |
|
85 group = convertor.from_bytes(gr->gr_name); |
|
86 } |
|
87 |
|
88 string_t getXattr(const fs::path& file, const string_t& attributeName) { |
|
89 // TODO: review, test on multiple platforms |
|
90 // TODO: improve documentation in xattr.h in glibc |
|
91 // TODO: check XATTR_NAME_MAX in limits.h and if it is reasonably small on our system, allocate buffer for it instead of reading twice |
|
92 // TODO: avoid race condition (another process might change the attribute between reading its length and its value) |
|
93 ssize_t length = getxattr(file.c_str(), convertor.to_bytes(attributeName).c_str(), nullptr, 0); |
|
94 if (length > 0) { |
|
95 std::vector<char> buffer(length + 1); |
|
96 getxattr(file.c_str(), convertor.to_bytes(attributeName).c_str(), buffer.data(), buffer.size()); |
|
97 return convertor.from_bytes(buffer.data()); |
|
98 } else { |
|
99 return L""; |
|
100 } |
|
101 } |
|
102 |
|
103 public: |
73 public: |
104 |
74 |
105 void process(std::istream& input, std::ostream& output) { |
75 void process(std::istream& input, std::ostream& output, Configuration& configuration) { |
106 std::shared_ptr<RelationalWriter> writer(Factory::create(output)); |
76 std::shared_ptr<RelationalWriter> writer(Factory::create(output)); |
107 |
77 |
108 // TODO: redesign: parametrized from CLI + groups of information fetched together and used for multiple attributes |
|
109 bool enableName = true; |
|
110 bool enableExists = false; |
|
111 bool enableNameAbsolute = false; |
|
112 bool enableNameCanonical = false; |
|
113 bool enableType = true; |
|
114 bool enableSize = true; |
|
115 bool enableOwner = true; |
|
116 bool enableGroup = true; |
|
117 bool enableXattrUrl = true; // just a demo |
|
118 |
|
119 std::vector<AttributeMetadata> attributesMetadata; |
78 std::vector<AttributeMetadata> attributesMetadata; |
120 if (enableName) attributesMetadata.push_back({L"name", TypeId::STRING}); |
79 for (RequestedField field : configuration.fields) { |
121 if (enableExists) attributesMetadata.push_back({L"exists", TypeId::BOOLEAN}); |
80 AttributeFinder* finder = attributeFinders[field.group]; |
122 if (enableNameAbsolute) attributesMetadata.push_back({L"name_absolute", TypeId::STRING}); |
81 if (finder) for (AttributeMetadata m : finder->toMetadata(field)) attributesMetadata.push_back(m); |
123 if (enableNameCanonical) attributesMetadata.push_back({L"name_canonical", TypeId::STRING}); |
82 else throw RelpipeWriterException(L"Unsupported field group: " + field.group); |
124 if (enableType) attributesMetadata.push_back({L"type", TypeId::STRING}); |
83 } |
125 if (enableSize) attributesMetadata.push_back({L"size", TypeId::INTEGER}); |
|
126 if (enableOwner) attributesMetadata.push_back({L"owner", TypeId::STRING}); |
|
127 if (enableGroup) attributesMetadata.push_back({L"group", TypeId::STRING}); |
|
128 if (enableXattrUrl) attributesMetadata.push_back({L"xattr_url", TypeId::STRING}); |
|
129 |
84 |
130 writer->startRelation(L"filesystem", attributesMetadata, true); |
85 writer->startRelation(L"filesystem", attributesMetadata, true); |
131 |
86 |
|
87 |
132 for (std::stringstream originalName; readNext(input, originalName); reset(originalName)) { |
88 for (std::stringstream originalName; readNext(input, originalName); reset(originalName)) { |
133 if (enableName) writer->writeAttribute(convertor.from_bytes(originalName.str())); |
89 |
134 fs::path file(originalName.str()); |
90 fs::path file(originalName.str()); |
135 bool exists = false; |
91 bool exists = false; |
136 |
92 |
137 try { |
93 try { |
138 exists = fs::exists(file); |
94 exists = fs::exists(file); |
139 } catch (const fs::filesystem_error& e) { |
95 } catch (const fs::filesystem_error& e) { |
140 // we probably do not have permissions to given directory → pretend that the file does not exist |
96 // we probably do not have permissions to given directory → pretend that the file does not exist |
141 } |
97 } |
142 |
98 |
143 if (exists) { |
99 for (auto& finder : attributeFinders) finder.second->startFile(file); |
144 if (enableExists) writer->writeAttribute(L"true"); |
|
145 if (enableNameAbsolute) writer->writeAttribute(fs::absolute(file).wstring()); |
|
146 if (enableNameCanonical) writer->writeAttribute(fs::canonical(file).wstring()); |
|
147 if (enableType) writer->writeAttribute(getType(file)); |
|
148 if (enableSize) { |
|
149 integer_t size = fs::is_regular_file(file) ? fs::file_size(file) : 0; |
|
150 writer->writeAttribute(&size, typeid (size)); |
|
151 } |
|
152 |
100 |
153 if (enableOwner || enableGroup) { |
101 for (RequestedField field : configuration.fields) { |
154 string_t owner; |
102 AttributeFinder* finder = attributeFinders[field.group]; // should not be nullptr, because already checked while writing the relation metadata |
155 string_t group; |
|
156 fetchOwner(file, owner, group); |
|
157 |
103 |
158 if (enableOwner) writer->writeAttribute(owner); |
104 if (exists || (field.group == RequestedField::GROUP_FILE && field.name == FileAttributeFinder::FIELD_PATH_ORIGINAL)) { |
159 if (enableGroup) writer->writeAttribute(group); |
105 finder->writeField(writer.get(), field); |
160 } |
106 } else { |
161 |
107 finder->writeEmptyField(writer.get(), field); |
162 if (enableXattrUrl) writer->writeAttribute(getXattr(file, L"user.xdg.origin.url")); |
|
163 |
|
164 } else { |
|
165 // Only original name was written → write remaining attributes |
|
166 // TODO: null values (requires adding null support to the format specification) |
|
167 for (int i = 1; i < attributesMetadata.size(); i++) { |
|
168 switch (attributesMetadata[i].typeId) { |
|
169 case TypeId::BOOLEAN: writer->writeAttribute(L"false"); |
|
170 break; |
|
171 case TypeId::INTEGER: writer->writeAttribute(L"0"); |
|
172 break; |
|
173 case TypeId::STRING: writer->writeAttribute(L""); |
|
174 break; |
|
175 default: |
|
176 throw RelpipeWriterException(L"Unsupported data type."); |
|
177 |
|
178 } |
|
179 } |
108 } |
180 } |
109 } |
|
110 |
|
111 for (auto& finder : attributeFinders) finder.second->endFile(); |
181 } |
112 } |
182 |
|
183 } |
113 } |
184 }; |
114 }; |
185 |
115 |
186 } |
116 } |
187 } |
117 } |