1/*
2 * $Id: dbd_lookup.c,v 1.18 2010-01-19 14:57:11 franklahm Exp $
3 *
4 * Copyright (C) Joerg Lenneis 2003
5 * Copyright (C) Frank Lahm 2009
6 * All Rights Reserved.  See COPYING.
7 */
8
9/*
10CNID salvation spec:
11general rule: better safe then sorry, so we always delete CNIDs and assign
12new ones in case of a lookup mismatch. afpd also sends us the CNID found
13in the adouble file (for AFPVOL_CACHE volumes). In certain cases we can
14use this hint to determince the right CNID.
15
16
17The lines...
18
19Id  Did T   Dev Inode   Name
20============================
21a   b   c   d   e       name1
22-->
23f   g   h   i   h       name2
24
25...are the expected results of certain operations. (f) is the speced CNID, in some
26cases it's only intermediate as described in the text and is overridden by another
27spec.
28
291) UNIX rename (via mv) or inode reusage(!)
30-------------------------------------------
31Name is possibly changed (rename case) but inode is the same.
32We should try to keep the CNID, but we cant, because inode reusage is probably
33much to frequent.
34
35rename:
3615  2   f   1   1       file
37-->
3815  x   f   1   1       renamedfile
39
40inode reusage:
4115  2   f   1   1       file
42-->
4316  y   f   1   1       inodereusagefile
44
45Result in dbd_lookup (-: not found, +: found):
46+ devino
47- didname
48
49Possible solution:
50None. Delete old data, file gets new CNID in both cases (rename and inode).
51If we got a hint and hint matches the CNID from devino we keep it and update
52the record.
53
542) UNIX mv from one folder to another
55----------------------------------------
56Name is unchanged and inode stays the same, but DID is different.
57We should try to keep the CNID.
58
5915  2   f   1   1       file
60-->
6115  x   f   1   1       file
62
63Result in dbd_lookup:
64+ devino
65- didname
66
67Possible solution:
68strcmp names, if they match keep CNID. Unfortunately this also can't be
69distinguished from a new file with a reused inode. So me must assign
70a new CNID.
71If we got a hint and hint matches the CNID from devino we keep it and update
72the record.
73
743) Restore from backup ie change of inode number -- or emacs
75------------------------------------------------------------
76
7715  2   f   1   1       file
78-->
7915  2   f   1   2       file
80
81Result in dbd_lookup:
82- devino
83+ didname
84
85Possible fixup solution:
86test-suite test235 tests and ensures that the CNID is _changed_. The reason for
87this is somewhat lost in time, but nevertheless we believe our test suite.
88
89Similar things happen with emas: emacs uses a backup file (file~). When saving
90because of inode reusage of the fs, both files most likely exchange inodes.
91
9215  2   f   1   1       file
9316  2   f   1   2       file~
94--> this would be nice:
9515  2   f   1   2       file
9616  2   f   1   1       file~
97--> but for the reasons described above we must implement
9817  2   f   1   2       file
9918  2   f   1   1       file~
100
101Result in dbd_lookup for the emacs case:
102+ devino --> CNID: 16
103+ didname -> CNID: 15
104devino search and didname search result in different CNIDs !!
105
106Possible fixup solution:
107to be safe we must assign new CNIDs to both files.
108*/
109
110#ifdef HAVE_CONFIG_H
111#include "config.h"
112#endif /* HAVE_CONFIG_H */
113
114
115#include <stdio.h>
116#include <string.h>
117#include <sys/param.h>
118#include <errno.h>
119#include <netatalk/endian.h>
120#include <atalk/logger.h>
121#include <atalk/cnid_dbd_private.h>
122
123#include "pack.h"
124#include "dbif.h"
125#include "dbd.h"
126
127/*
128 *  This returns the CNID corresponding to a particular file.  It will also fix
129 *  up the database if there's a problem.
130 */
131
132int dbd_lookup(DBD *dbd, struct cnid_dbd_rqst *rqst, struct cnid_dbd_rply *rply, int roflag)
133{
134    unsigned char *buf;
135    DBT key, devdata, diddata;
136    int devino = 1, didname = 1;
137    int rc;
138    cnid_t id_devino, id_didname;
139    u_int32_t type_devino  = (unsigned)-1;
140    u_int32_t type_didname = (unsigned)-1;
141    int update = 0;
142
143    memset(&key, 0, sizeof(key));
144    memset(&diddata, 0, sizeof(diddata));
145    memset(&devdata, 0, sizeof(devdata));
146
147    rply->namelen = 0;
148    rply->cnid = 0;
149
150    LOG(log_maxdebug, logtype_cnid, "dbd_lookup(): START");
151
152    buf = pack_cnid_data(rqst);
153
154    /* Look for a CNID.  We have two options: dev/ino or did/name.  If we
155       only get a match in one of them, that means a file has moved. */
156    key.data = buf + CNID_DEVINO_OFS;
157    key.size = CNID_DEVINO_LEN;
158
159    if ((rc = dbif_get(dbd, DBIF_IDX_DEVINO, &key, &devdata, 0))  < 0) {
160        LOG(log_error, logtype_cnid, "dbd_lookup: Unable to get CNID %u, name %s",
161            ntohl(rqst->did), rqst->name);
162        rply->result = CNID_DBD_RES_ERR_DB;
163        return -1;
164    }
165    if (rc == 0) {
166        devino = 0;
167    }
168    else {
169        memcpy(&id_devino, devdata.data, sizeof(rply->cnid));
170        memcpy(&type_devino, (char *)devdata.data +CNID_TYPE_OFS, sizeof(type_devino));
171        type_devino = ntohl(type_devino);
172    }
173
174    key.data = buf + CNID_DID_OFS;
175    key.size = CNID_DID_LEN + rqst->namelen + 1;
176
177    if ((rc = dbif_get(dbd, DBIF_IDX_DIDNAME, &key, &diddata, 0))  < 0) {
178        LOG(log_error, logtype_cnid, "dbd_lookup: Unable to get CNID %u, name %s",
179            ntohl(rqst->did), rqst->name);
180        rply->result = CNID_DBD_RES_ERR_DB;
181        return -1;
182    }
183    if (rc == 0) {
184        didname = 0;
185    }
186    else {
187        memcpy(&id_didname, diddata.data, sizeof(rply->cnid));
188        memcpy(&type_didname, (char *)diddata.data +CNID_TYPE_OFS, sizeof(type_didname));
189        type_didname = ntohl(type_didname);
190    }
191
192    LOG(log_maxdebug, logtype_cnid, "dbd_lookup(name:'%s', did:%u, dev/ino:0x%llx/0x%llx) {devino: %u, didname: %u}",
193        rqst->name, ntohl(rqst->did), (unsigned long long)rqst->dev, (unsigned long long)rqst->ino, devino, didname);
194
195    /* Have we found anything at all ? */
196    if (!devino && !didname) {
197        /* nothing found */
198        LOG(log_debug, logtype_cnid, "dbd_lookup: name: '%s', did: %u, dev/ino: 0x%llx/0x%llx is not in the CNID database",
199            rqst->name, ntohl(rqst->did), (unsigned long long)rqst->dev, (unsigned long long)rqst->ino);
200        rply->result = CNID_DBD_RES_NOTFOUND;
201        return 1;
202    }
203
204    /* Check for type (file/dir) mismatch */
205    if ((devino && (type_devino != rqst->type)) || (didname && (type_didname != rqst->type))) {
206
207        if (devino && (type_devino != rqst->type)) {
208            /* one is a dir one is a file, remove from db */
209
210            LOG(log_debug, logtype_cnid, "dbd_lookup(name:'%s', did:%u, dev/ino:0x%llx/0x%llx): type mismatch for devino",
211                rqst->name, ntohl(rqst->did), (unsigned long long)rqst->dev, (unsigned long long)rqst->ino);
212
213            if (! roflag) {
214                rqst->cnid = id_devino;
215                rc = dbd_delete(dbd, rqst, rply, DBIF_CNID);
216                rc += dbd_delete(dbd, rqst, rply, DBIF_IDX_DEVINO);
217                rc += dbd_delete(dbd, rqst, rply, DBIF_IDX_DIDNAME);
218                if (rc < 0) {
219                    LOG(log_error, logtype_cnid, "dbd_lookup(name:'%s', did:%u, dev/ino:0x%llx/0x%llx): error deleting type mismatch for devino",
220                        rqst->name, ntohl(rqst->did), (unsigned long long)rqst->dev, (unsigned long long)rqst->ino);
221                    return -1;
222                }
223            }
224        }
225
226        if (didname && (type_didname != rqst->type)) {
227            /* same: one is a dir one is a file, remove from db */
228
229            LOG(log_debug, logtype_cnid, "dbd_lookup(name:'%s', did:%u, dev/ino:0x%llx/0x%llx): type mismatch for didname",
230                rqst->name, ntohl(rqst->did), (unsigned long long)rqst->dev, (unsigned long long)rqst->ino);
231
232            if (! roflag) {
233                rqst->cnid = id_didname;
234                rc = dbd_delete(dbd, rqst, rply, DBIF_CNID);
235                rc += dbd_delete(dbd, rqst, rply, DBIF_IDX_DEVINO);
236                rc += dbd_delete(dbd, rqst, rply, DBIF_IDX_DIDNAME);
237                if (rc < 0) {
238                    LOG(log_error, logtype_cnid, "dbd_lookup(name:'%s', did:%u, dev/ino:0x%llx/0x%llx): error deleting type mismatch for didname",
239                        rqst->name, ntohl(rqst->did), (unsigned long long)rqst->dev, (unsigned long long)rqst->ino);
240                    return -1;
241                }
242            }
243        }
244
245        rply->result = CNID_DBD_RES_NOTFOUND;
246        return 1;
247    }
248
249    if (devino && didname && id_devino == id_didname) {
250        /* everything is fine */
251        LOG(log_debug, logtype_cnid, "dbd_lookup(DID:%u/'%s',0x%llx/0x%llx): Got CNID: %u",
252            ntohl(rqst->did), rqst->name, (unsigned long long)rqst->dev, (unsigned long long)rqst->ino, htonl(id_didname));
253        rply->cnid = id_didname;
254        rply->result = CNID_DBD_RES_OK;
255        return 1;
256    }
257
258    if (devino && didname && id_devino != id_didname) {
259        /* CNIDs don't match, something of a worst case, or possibly 3) emacs! */
260        LOG(log_debug, logtype_cnid, "dbd_lookup: CNID mismatch: (DID:%u/'%s') --> %u , (0x%llx/0x%llx) --> %u",
261            ntohl(rqst->did), rqst->name, ntohl(id_didname),
262            (unsigned long long)rqst->dev, (unsigned long long)rqst->ino, ntohl(id_devino));
263
264        if (! roflag) {
265            rqst->cnid = id_devino;
266            if (dbd_delete(dbd, rqst, rply, DBIF_CNID) < 0)
267                return -1;
268
269            rqst->cnid = id_didname;
270            if (dbd_delete(dbd, rqst, rply, DBIF_CNID) < 0)
271                return -1;
272        }
273        rply->result = CNID_DBD_RES_NOTFOUND;
274        return 1;
275    }
276
277    if ( ! didname) {
278        LOG(log_debug, logtype_cnid, "dbd_lookup(CNID hint: %u, DID:%u, \"%s\", 0x%llx/0x%llx): CNID resolve problem: server side rename oder reused inode",
279            ntohl(rqst->cnid), ntohl(rqst->did), rqst->name, (unsigned long long)rqst->dev, (unsigned long long)rqst->ino);
280        if (rqst->cnid == id_devino) {
281            LOG(log_debug, logtype_cnid, "dbd_lookup: server side mv (with resource fork)");
282            update = 1;
283        } else {
284            if ( ! roflag) {
285                rqst->cnid = id_devino;
286                if (dbd_delete(dbd, rqst, rply, DBIF_CNID) < 0)
287                    return -1;
288            }
289            rply->result = CNID_DBD_RES_NOTFOUND;
290            return 1;
291        }
292    }
293
294    if ( ! devino) {
295        LOG(log_debug, logtype_cnid, "dbd_lookup(DID:%u/'%s',0x%llx/0x%llx): CNID resolve problem: changed dev/ino",
296            ntohl(rqst->did), rqst->name, (unsigned long long)rqst->dev, (unsigned long long)rqst->ino);
297        if ( ! roflag) {
298            rqst->cnid = id_didname;
299            if (dbd_delete(dbd, rqst, rply, DBIF_CNID) < 0)
300                return -1;
301        }
302        rply->result = CNID_DBD_RES_NOTFOUND;
303        return 1;
304    }
305
306    /* This is also a catch all if we've forgot to catch some possibility with the preceding ifs*/
307    if (!update || roflag) {
308        rply->result = CNID_DBD_RES_NOTFOUND;
309        return 1;
310    }
311
312    /* Fix up the database */
313    rc = dbd_update(dbd, rqst, rply);
314    if (rc >0) {
315        rply->cnid = rqst->cnid;
316    }
317
318    LOG(log_debug, logtype_cnid, "dbd_lookup(DID:%u/'%s',0x%llx/0x%llx): Got CNID (needed update): %u",
319        ntohl(rqst->did), rqst->name, (unsigned long long)rqst->dev, (unsigned long long)rqst->ino, ntohl(rply->cnid));
320
321    return rc;
322}
323