~ubuntu-branches/ubuntu/oneiric/partitionmanager/oneiric

« back to all changes in this revision

Viewing changes to src/core/partitiontable.cpp

  • Committer: Bazaar Package Importer
  • Author(s): Anthony Mercatante
  • Date: 2009-01-23 17:57:36 UTC
  • Revision ID: james.westby@ubuntu.com-20090123175736-2ltrhgg3m55dokbm
Tags: upstream-1.0.0~beta1a
ImportĀ upstreamĀ versionĀ 1.0.0~beta1a

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
/***************************************************************************
 
2
 *   Copyright (C) 2008 by Volker Lanz <vl@fidra.de>                       *
 
3
 *                                                                         *
 
4
 *   This program is free software; you can redistribute it and/or modify  *
 
5
 *   it under the terms of the GNU General Public License as published by  *
 
6
 *   the Free Software Foundation; either version 2 of the License, or     *
 
7
 *   (at your option) any later version.                                   *
 
8
 *                                                                         *
 
9
 *   This program is distributed in the hope that it will be useful,       *
 
10
 *   but WITHOUT ANY WARRANTY; without even the implied warranty of        *
 
11
 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the         *
 
12
 *   GNU General Public License for more details.                          *
 
13
 *                                                                         *
 
14
 *   You should have received a copy of the GNU General Public License     *
 
15
 *   along with this program; if not, write to the                         *
 
16
 *   Free Software Foundation, Inc.,                                       *
 
17
 *   51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA            *
 
18
 ***************************************************************************/
 
19
 
 
20
/** @file
 
21
*/
 
22
 
 
23
#include "core/partitiontable.h"
 
24
#include "core/partition.h"
 
25
#include "core/device.h"
 
26
 
 
27
#include "fs/filesystem.h"
 
28
#include "fs/filesystemfactory.h"
 
29
 
 
30
#include "util/globallog.h"
 
31
 
 
32
#include <kdebug.h>
 
33
#include <klocale.h>
 
34
 
 
35
/** Creates a new PartitionTable object with type MSDOS */
 
36
PartitionTable::PartitionTable() :
 
37
        PartitionNode(),
 
38
        m_MaxPrimaries(4),
 
39
        m_TypeName("msdos"),
 
40
        m_ReadOnly(false)
 
41
{
 
42
}
 
43
 
 
44
/** Destroys a PartitionTable object, destroying all children */
 
45
PartitionTable::~PartitionTable()
 
46
{
 
47
        clear();
 
48
}
 
49
 
 
50
void PartitionTable::clear()
 
51
{
 
52
        setMaxPrimaries(4);
 
53
        setTypeName("msdos");
 
54
        clearChildren();
 
55
}
 
56
 
 
57
/** Gets the number of free sectors before a given child Partition in this PartitionTable.
 
58
 
 
59
        @param p the Partition for which to get the free sectors before
 
60
        @returns the number of free sectors before the Partition
 
61
*/
 
62
qint64 PartitionTable::freeSectorsBefore(const Partition& p) const
 
63
{
 
64
        const Partition* pred = predecessor(p);
 
65
 
 
66
        if (pred && pred->roles().has(PartitionRole::Unallocated))
 
67
                return pred->length();
 
68
 
 
69
        return 0;
 
70
}
 
71
 
 
72
/** Gets the number of free sectors after a given child Partition in this PartitionTable.
 
73
 
 
74
        @param p the Partition for which to get the free sectors after
 
75
        @returns the number of free sectors after the Partition
 
76
*/
 
77
qint64 PartitionTable::freeSectorsAfter(const Partition& p) const
 
78
{
 
79
        const Partition* succ = successor(p);
 
80
 
 
81
        if (succ && succ->roles().has(PartitionRole::Unallocated))
 
82
                return succ->length();
 
83
 
 
84
        return 0;
 
85
}
 
86
 
 
87
/** @return true if the PartitionTable has an extended Partition */
 
88
bool PartitionTable::hasExtended() const
 
89
{
 
90
        for (int i = 0; i < children().size(); i++)
 
91
                if (children()[i]->roles().has(PartitionRole::Extended))
 
92
                        return true;
 
93
 
 
94
        return false;
 
95
}
 
96
 
 
97
/** @return pointer to the PartitionTable's extended Partition or NULL if none exists */
 
98
Partition* PartitionTable::extended()
 
99
{
 
100
        for (int i = 0; i < children().size(); i++)
 
101
                if (children()[i]->roles().has(PartitionRole::Extended))
 
102
                        return children()[i];
 
103
 
 
104
        return NULL;
 
105
}
 
106
 
 
107
/** Gets valid PartitionRoles for a Partition
 
108
        @param p the Partition
 
109
        @return valid roles for the given Partition
 
110
*/
 
111
PartitionRole::Roles PartitionTable::childRoles(const Partition& p) const
 
112
{
 
113
        Q_ASSERT(p.parent());
 
114
 
 
115
        PartitionRole::Roles r = p.parent()->isRoot() ? PartitionRole::Primary : PartitionRole::Logical;
 
116
 
 
117
        if (r == PartitionRole::Primary && hasExtended() == false)
 
118
                r |= PartitionRole::Extended;
 
119
 
 
120
        return r;
 
121
}
 
122
 
 
123
/** @return the number of primaries in this PartitionTable */
 
124
int PartitionTable::numPrimaries() const
 
125
{
 
126
        int result = 0;
 
127
 
 
128
        foreach(const Partition* p, children())
 
129
                if (p->roles().has(PartitionRole::Primary) || p->roles().has(PartitionRole::Extended))
 
130
                        result++;
 
131
 
 
132
        return result;
 
133
}
 
134
 
 
135
/** Appends a Partition to this PartitionTable
 
136
        @param partition pointer of the partition to append. Must not be NULL.
 
137
*/
 
138
void PartitionTable::append(Partition* partition)
 
139
{
 
140
        children().append(partition);
 
141
}
 
142
 
 
143
/** @param f the flag to get the name for
 
144
        @returns the flags name or an empty QString if the flag is not known
 
145
*/
 
146
QString PartitionTable::flagName(Flag f)
 
147
{
 
148
        switch(f)
 
149
        {
 
150
                case PartitionTable::FlagBoot: return i18nc("@item partition flag", "boot");
 
151
                case PartitionTable::FlagRoot: return i18nc("@item partition flag", "root");
 
152
                case PartitionTable::FlagSwap: return i18nc("@item partition flag", "swap");
 
153
                case PartitionTable::FlagHidden: return i18nc("@item partition flag", "hidden");
 
154
                case PartitionTable::FlagRaid: return i18nc("@item partition flag", "raid");
 
155
                case PartitionTable::FlagLvm: return i18nc("@item partition flag", "lvm");
 
156
                case PartitionTable::FlagLba: return i18nc("@item partition flag", "lba");
 
157
                case PartitionTable::FlagHpService: return i18nc("@item partition flag", "hpservice");
 
158
                case PartitionTable::FlagPalo: return i18nc("@item partition flag", "palo");
 
159
                case PartitionTable::FlagPrep: return i18nc("@item partition flag", "prep");
 
160
                case PartitionTable::FlagMsftReserved: return i18nc("@item partition flag", "msft-reserved");
 
161
 
 
162
                default:
 
163
                        break;
 
164
        }
 
165
 
 
166
        return QString();
 
167
}
 
168
 
 
169
/** @return list of all flags */
 
170
QList<PartitionTable::Flag> PartitionTable::flagList()
 
171
{
 
172
        QList<PartitionTable::Flag> rval;
 
173
 
 
174
        rval.append(PartitionTable::FlagBoot);
 
175
        rval.append(PartitionTable::FlagRoot);
 
176
        rval.append(PartitionTable::FlagSwap);
 
177
        rval.append(PartitionTable::FlagHidden);
 
178
        rval.append(PartitionTable::FlagRaid);
 
179
        rval.append(PartitionTable::FlagLvm);
 
180
        rval.append(PartitionTable::FlagLba);
 
181
        rval.append(PartitionTable::FlagHpService);
 
182
        rval.append(PartitionTable::FlagPalo);
 
183
        rval.append(PartitionTable::FlagPrep);
 
184
        rval.append(PartitionTable::FlagMsftReserved);
 
185
 
 
186
        return rval;
 
187
}
 
188
 
 
189
/** @param flags the flags to get the names for
 
190
        @returns QStringList of the flags' names
 
191
*/
 
192
QStringList PartitionTable::flagNames(Flags flags)
 
193
{
 
194
        QStringList rval;
 
195
 
 
196
        int f = 1;
 
197
 
 
198
        QString s;
 
199
        while(!(s = flagName(static_cast<PartitionTable::Flag>(f))).isEmpty())
 
200
        {
 
201
                if (flags & f)
 
202
                        rval.append(s);
 
203
 
 
204
                f <<= 1;
 
205
        }
 
206
 
 
207
        return rval;
 
208
}
 
209
 
 
210
/** Checks if a given Partition on a given Device is snapped to cylinder boundaries.
 
211
 
 
212
        Will print warning messages to GlobalLog if the Partition's first sector is not snapped and
 
213
        another one if the last sector is not snapped.
 
214
 
 
215
        @see snap(), canSnapToSector()
 
216
 
 
217
        @param d the Device the Partition is on
 
218
        @param p the Partition to check
 
219
        @return true if snapped
 
220
*/
 
221
bool PartitionTable::isSnapped(const Device& d, const Partition& p)
 
222
{
 
223
        // don't bother with unallocated space here.
 
224
        if (p.roles().has(PartitionRole::Unallocated))
 
225
                return true;
 
226
 
 
227
        qint64 delta = 0;
 
228
 
 
229
        // There are some special cases for snapping partitions to cylinder boundaries, apparently.
 
230
        // 1) If an extended partition starts at the beginning of the device (that would be sector 63
 
231
        // on modern drives, equivalent to sectorsPerTrack() in any case), the first logical partition
 
232
        // at the beginning of this extended partition starts at 2 * sectorsPerTrack().
 
233
        // 2) If a primary or extended starts at the beginning of a device, it starts at sectorsPerTrack().
 
234
        // 3) Any logical partition is always preceded by the extended partition table entry in the
 
235
        // sectorsPerTrack() before it, so it's always sectorsPerTrack() "late"
 
236
        if (p.roles().has(PartitionRole::Logical) && p.firstSector() == 2 * d.sectorsPerTrack())
 
237
                delta = (p.firstSector() - (2 * d.sectorsPerTrack())) % d.cylinderSize();
 
238
        else if (p.roles().has(PartitionRole::Logical) || p.firstSector() == d.sectorsPerTrack())
 
239
                delta = (p.firstSector() - d.sectorsPerTrack()) % d.cylinderSize();
 
240
        else
 
241
                delta = p.firstSector() % d.cylinderSize();
 
242
 
 
243
        bool rval = true;
 
244
 
 
245
        if (delta)
 
246
        {
 
247
                log(log::warning) << i18nc("@info/plain", "Partition <filename>%1</filename> does not start at a cylinder boundary (first sector: %2, modulo: %3).", p.deviceNode(), p.firstSector(), delta);
 
248
                rval = false;
 
249
        }
 
250
 
 
251
        delta = (p.lastSector() + 1) % d.cylinderSize();
 
252
 
 
253
        if (delta)
 
254
        {
 
255
                log(log::warning) << i18nc("@info/plain", "Partition <filename>%1</filename> does not end at a cylinder boundary (last sector: %2, modulo: %3).", p.deviceNode(), p.lastSector(), delta);
 
256
                rval = false;
 
257
        }
 
258
 
 
259
        return rval;
 
260
}
 
261
 
 
262
/** Checks if a Partition can be snapped to a given sector on a given Device.
 
263
 
 
264
        @see PartitionTable::snap(), PartitionTable::isSnapped()
 
265
 
 
266
        @param d the Device the Partition is on
 
267
        @param p the Partition to snap
 
268
        @param s the sector to snap to
 
269
        @param originalPartition pointer to another Partition @p p has just been copied from or NULL
 
270
        @return true if snapping to @p s is possible
 
271
*/
 
272
static bool canSnapToSector(const Device& d, const Partition& p, qint64 s, const Partition* originalPartition)
 
273
{
 
274
        Q_ASSERT(d.partitionTable());
 
275
 
 
276
        if (s < d.sectorsPerTrack() || s >= d.totalSectors())
 
277
                return false;
 
278
 
 
279
        const Partition* other = d.partitionTable()->findPartitionBySector(s, PartitionRole(PartitionRole::Logical | PartitionRole::Primary | PartitionRole::Extended | PartitionRole::Unallocated));
 
280
 
 
281
        if (other && other->roles().has(PartitionRole::Unallocated))
 
282
                other = NULL;
 
283
 
 
284
        return other == NULL || other == &p || other == originalPartition;
 
285
}
 
286
 
 
287
/** Snaps the given Partition on the given Device to cylinder boundaries.
 
288
 
 
289
        Tries under all accounts to keep the Partition's length equal to the original length or
 
290
        to increase it, if that is not possible. Will print a warning message to GlobalLog if
 
291
        this is not possible.
 
292
 
 
293
        The parameter @p originalPartition is required for cases where a Partition has just been
 
294
        duplicated to resize or move it. This method needs to know the original because of course
 
295
        the original does not prevent snapping to any sector allocated by it.
 
296
 
 
297
        @see canSnapToSector(), isSnapped()
 
298
 
 
299
        @param d the Device the Partition is on
 
300
        @param p the Partition to snap
 
301
        @param originalPartition pointer to a Partition object @p p has just been copied from or NULL
 
302
        @return true if Partition is now snapped to cylinder boundaries
 
303
*/
 
304
bool PartitionTable::snap(const Device& d, Partition& p, const Partition* originalPartition)
 
305
{
 
306
        const qint64 originalLength = p.length();
 
307
        qint64 delta = 0;
 
308
        bool lengthIsSnapped = false;
 
309
 
 
310
        // This is the same as in isSnapped(), only we additionally have to remember if the
 
311
        // partition's _length_ is "snapped", so to speak (i.e., evenly divisable by
 
312
        // the cylinder size)
 
313
        if (p.roles().has(PartitionRole::Logical) && p.firstSector() == 2 * d.sectorsPerTrack())
 
314
        {
 
315
                delta = (p.firstSector() - (2 * d.sectorsPerTrack())) % d.cylinderSize();
 
316
                lengthIsSnapped = (p.length() + (2 * d.sectorsPerTrack())) % d.cylinderSize() == 0;
 
317
        }
 
318
        else if (p.roles().has(PartitionRole::Logical) || p.firstSector() == d.sectorsPerTrack())
 
319
        {
 
320
                delta = (p.firstSector() - d.sectorsPerTrack()) % d.cylinderSize();
 
321
                lengthIsSnapped = (p.length() + d.sectorsPerTrack()) % d.cylinderSize() == 0;
 
322
        }
 
323
        else
 
324
        {
 
325
                delta = p.firstSector() % d.cylinderSize();
 
326
                lengthIsSnapped = p.length() % d.cylinderSize() == 0;
 
327
        }
 
328
 
 
329
        if (delta)
 
330
        {
 
331
                /** @todo Don't assume we always want to snap to the front.
 
332
                        Always trying to snap to the front solves the problem that a partition does
 
333
                        get too small to take another one that's copied to it, but it introduces
 
334
                        a new bug: The user might create a partition aligned at the end of a device,
 
335
                        extended partition or at the start of the next one, but we snap to the back
 
336
                        and leave some space in between.
 
337
                */
 
338
                // We always want to make the partition larger, not smaller. Making it smaller
 
339
                // might, in case it's a partition that another is being copied to, mean the partition
 
340
                // ends up too small. So try to move the start to the front first.
 
341
                qint64 snappedFirst = p.firstSector() - delta;
 
342
 
 
343
                // Now if the cylinder boundary at the front is occupied...
 
344
                if (!canSnapToSector(d, p, p.firstSector() - delta, originalPartition))
 
345
                {
 
346
                        // ... move to the cylinder towards the end of the device ...
 
347
                        snappedFirst += d.cylinderSize();
 
348
 
 
349
                        // ... and move the end of the partition towards the end, too, if that is possible.
 
350
                        // By doing this, we still try to keep the length >= the original length. If the
 
351
                        // last sector ends up not being on a cylinder boundary by doing so, the code
 
352
                        // below will deal with that.
 
353
                        qint64 numTooShort = d.cylinderSize() - delta;
 
354
                        if (canSnapToSector(d, p, p.lastSector() + numTooShort, originalPartition))
 
355
                        {
 
356
                                p.setLastSector(p.lastSector() + numTooShort);
 
357
                                p.fileSystem().setLastSector(p.fileSystem().lastSector() + numTooShort);
 
358
                        }
 
359
                }
 
360
 
 
361
                p.setFirstSector(snappedFirst);
 
362
                p.fileSystem().setFirstSector(snappedFirst);
 
363
        }
 
364
 
 
365
        delta = (p.lastSector() + 1) % d.cylinderSize();
 
366
 
 
367
        if (delta)
 
368
        {
 
369
                // Try to snap to the back first...
 
370
                qint64 snappedLast = p.lastSector() + d.cylinderSize() - delta;
 
371
 
 
372
                // .. but if we can retain the partition length exactly by snapping to the front ...
 
373
                if (lengthIsSnapped && p.length() - originalLength == delta)
 
374
                        snappedLast -= d.cylinderSize();
 
375
                // ... or if there's something there already, snap to the front.
 
376
                else if (!canSnapToSector(d, p, snappedLast, originalPartition))
 
377
                        snappedLast -= d.cylinderSize();
 
378
 
 
379
                p.setLastSector(snappedLast);
 
380
                p.fileSystem().setLastSector(snappedLast);
 
381
        }
 
382
 
 
383
        // Now, did we make the partition too big for its file system?
 
384
        while (p.length() > originalLength && p.capacity() > p.fileSystem().maxCapacity() && canSnapToSector(d, p, p.lastSector() - d.cylinderSize(), originalPartition))
 
385
        {
 
386
                p.setLastSector(p.lastSector() - d.cylinderSize());
 
387
                p.fileSystem().setLastSector(p.fileSystem().lastSector() - d.cylinderSize());
 
388
        }
 
389
 
 
390
        if (p.length() < originalLength)
 
391
                log(log::warning) << i18nc("@info/plain", "The partition cannot be created with the requested length of %1 sectors and will instead only be %2 sectors long.", originalLength, p.length());
 
392
 
 
393
        // In an extended partition we also need to snap unallocated children at the beginning and at the end
 
394
        // (there should never be a need to snap non-unallocated children)
 
395
        if (p.roles().has(PartitionRole::Extended))
 
396
        {
 
397
                if (p.children().size() > 0)
 
398
                {
 
399
                        if (p.children().first()->roles().has(PartitionRole::Unallocated))
 
400
                        {
 
401
                                p.children().first()->setFirstSector(p.firstSector() + d.sectorsPerTrack());
 
402
                                p.children().first()->fileSystem().setFirstSector(p.fileSystem().firstSector() + d.sectorsPerTrack());
 
403
                        }
 
404
 
 
405
                        if (p.children().last()->roles().has(PartitionRole::Unallocated))
 
406
                        {
 
407
                                p.children().last()->setLastSector(p.lastSector());
 
408
                                p.children().last()->fileSystem().setLastSector(p.fileSystem().lastSector());
 
409
                        }
 
410
                }
 
411
        }
 
412
 
 
413
        return isSnapped(d, p);
 
414
}
 
415
 
 
416
/** Creates a new unallocated Partition on the given Device.
 
417
        @param device the Device to create the new Partition on
 
418
        @param parent the parent PartitionNode for the new Partition
 
419
        @param start the new Partition's start sector
 
420
        @param end the new Partition's end sector
 
421
        @return pointer to the newly created Partition object or NULL if the Partition could not be created
 
422
*/
 
423
Partition* createUnallocated(const Device& device, PartitionNode& parent, qint64 start, qint64 end)
 
424
{
 
425
        PartitionRole::Roles r = PartitionRole::Unallocated;
 
426
 
 
427
        if (!parent.isRoot())
 
428
        {
 
429
                Partition* extended = dynamic_cast<Partition*>(&parent);
 
430
 
 
431
                if (extended == NULL)
 
432
                {
 
433
                        kWarning() << "extended is null. start: " << start << ", end: " << end << ", device: " << device.deviceNode();
 
434
                        return NULL;
 
435
                }
 
436
 
 
437
                Q_ASSERT(extended);
 
438
 
 
439
                // Leave a track free at the start for a new partition's metadata
 
440
                start += device.sectorsPerTrack();
 
441
 
 
442
                // .. and also at the end for the metadata for a partition to follow us, if we're not
 
443
                // at the end of the extended partition
 
444
                if (end < extended->lastSector())
 
445
                        end -= device.sectorsPerTrack();
 
446
 
 
447
                r |= PartitionRole::Logical;
 
448
        }
 
449
 
 
450
        if (end - start + 1 < device.cylinderSize())
 
451
                return NULL;
 
452
 
 
453
        return new Partition(&parent, device, PartitionRole(r), FileSystemFactory::create(FileSystem::Unknown, start, end), start, end, -1);
 
454
}
 
455
 
 
456
/** Removes all unallocated children from a PartitionNode
 
457
        @param p pointer to the parent to remove unallocated children from
 
458
*/
 
459
void PartitionTable::removeUnallocated(PartitionNode* p)
 
460
{
 
461
        Q_ASSERT(p != NULL);
 
462
 
 
463
        qint32 i = 0;
 
464
 
 
465
        while (i < p->children().size())
 
466
        {
 
467
                Partition* child = p->children()[i];
 
468
 
 
469
                if (child->roles().has(PartitionRole::Unallocated))
 
470
                {
 
471
                        p->remove(child);
 
472
                        continue;
 
473
                }
 
474
 
 
475
                if (child->roles().has(PartitionRole::Extended))
 
476
                        removeUnallocated(child);
 
477
 
 
478
                i++;
 
479
        }
 
480
}
 
481
 
 
482
/**
 
483
        @overload
 
484
*/
 
485
void PartitionTable::removeUnallocated()
 
486
{
 
487
        removeUnallocated(this);
 
488
}
 
489
 
 
490
/** Inserts unallocated children for a Device's PartitionTable with the given parent.
 
491
 
 
492
        This method inserts unallocated Partitions for a parent, usually the Device this
 
493
        PartitionTable is on. It will also insert unallocated Partitions in any extended
 
494
        Partitions it finds.
 
495
 
 
496
        @warning This method assumes that no unallocated Partitions exist when it is called.
 
497
 
 
498
        @param d the Device this PartitionTable and @p p are on
 
499
        @param p the parent PartitionNode (may be this or an extended Partition)
 
500
        @param start the first sector to begin looking for free space
 
501
*/
 
502
void PartitionTable::insertUnallocated(const Device& d, PartitionNode* p, qint64 start) const
 
503
{
 
504
        Q_ASSERT(p != NULL);
 
505
 
 
506
        qint64 lastEnd = start;
 
507
 
 
508
        foreach (Partition* child, p->children())
 
509
        {
 
510
                p->insert(createUnallocated(d, *p, lastEnd, child->firstSector() - 1));
 
511
 
 
512
                if (child->roles().has(PartitionRole::Extended))
 
513
                        insertUnallocated(d, child, child->firstSector());
 
514
 
 
515
                lastEnd = child->lastSector() + 1;
 
516
        }
 
517
 
 
518
        // Take care of the free space between the end of the last child and the end
 
519
        // of the device or the extended partition.
 
520
        qint64 parentEnd = d.totalSectors() - 1;
 
521
 
 
522
        if (!p->isRoot())
 
523
        {
 
524
                Partition* extended = dynamic_cast<Partition*>(p);
 
525
                Q_ASSERT(extended != NULL);
 
526
                parentEnd = (extended != NULL) ? extended->lastSector() : -1;
 
527
        }
 
528
 
 
529
        if (parentEnd >= d.cylinderSize())
 
530
                p->insert(createUnallocated(d, *p, lastEnd, parentEnd));
 
531
}
 
532
 
 
533
/** Updates the unallocated Partitions for this PartitionTable.
 
534
        @param d the Device this PartitionTable is on
 
535
*/
 
536
void PartitionTable::updateUnallocated(const Device& d)
 
537
{
 
538
        removeUnallocated();
 
539
        insertUnallocated(d, this, d.sectorsPerTrack());
 
540
}
 
541
 
 
542
void PartitionTable::setTypeName(const QString& s)
 
543
{
 
544
        m_TypeName = s;
 
545
 
 
546
        /** @todo
 
547
                The application currently is not prepared to correctly handle any other disk label
 
548
                type than msdos. Until that situation changes, all disk labels but msdos are
 
549
                "read only".
 
550
        */
 
551
        setReadOnly(typeName() != "msdos");
 
552
}